unleashed-firmware/applications/examples/example_event_loop/example_event_loop_mutex.c
MX bf6c6c231f
[FL-3841] FuriEventLoop Pt.2 (#3703)
* Abstract primitive type from main logic in FuriEventLoop
* Remove message_queue_i.h
* Add stream buffer support for event loop
* Add semaphore support for event loop
* Add temporary unit test workaround
* Make the linter happy
* Add mutex support for event loop
* Implement event subscription and unsubscription while the event loop is running
* Implement edge events
* Fix leftover logical errors
* Add event loop timer example application
* Implement flag-based edge trigger and one-shot mode
* Add event loop mutex example application
* Only notify the event loop if stream buffer is at or above its trigger level
* Reformat comments
* Add event loop stream buffer example application
* Add event loop multiple elements example application
* Improve event loop flag names
* Remove redundant signal handler as it is already handled by the event loop
* Refactor Power service, improve ViewHolder
* Use ViewHolder instead of ViewDispatcher in About app
* Enable ViewDispatcher queue on construction, deprecate view_dispatcher_enable_queue()
* Remove all invocations of view_dispatcher_enable_queue()
* Remove app-scened-template
* Remove missing library from target.json
* Port Accessor app to ViewHolder
* Make the linter happy
* Add example_view_holder application, update ViewHolder docs
* Add example_view_dispatcher application, update ViewDispatcher docs
* Replace FuriSemaphore with FuriApiLock, remove workaround delay
* Fix logical error
* Fix another logical error
* Use the sources directive to speed up compilation
* Use constant define macro
* Improve FuriEventLoop documentation
* Improve FuriEventLoop documentation once more
* Bump API Version
* Gui: remove redundant checks from ViewDispatcher
* Gui: remove dead ifs from ViewDispatcher

Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>
Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: あく <alleteam@gmail.com>
2024-08-10 14:32:27 +03:00

141 lines
5.6 KiB
C

/**
* @file example_event_loop_mutex.c
* @brief Example application that demonstrates the FuriEventLoop and FuriMutex integration.
*
* This application simulates a use case where a time-consuming blocking operation is executed
* in a separate thread and a mutex is being used for synchronization. The application runs 10 iterations
* of the above mentioned simulated work and prints the results to the debug output each time, then exits.
*/
#include <furi.h>
#include <furi_hal_random.h>
#define TAG "ExampleEventLoopMutex"
#define WORKER_ITERATION_COUNT (10)
// We are interested in IN events (for the mutex, that means that the mutex has been released),
// using edge trigger mode (reacting only to changes in mutex state) and
// employing one-shot mode to automatically unsubscribe before the event is processed.
#define MUTEX_EVENT_AND_FLAGS \
(FuriEventLoopEventIn | FuriEventLoopEventFlagEdge | FuriEventLoopEventFlagOnce)
typedef struct {
FuriEventLoop* event_loop;
FuriThread* worker_thread;
FuriMutex* worker_mutex;
uint8_t worker_result;
} EventLoopMutexApp;
// This funciton is being run in a separate thread to simulate lenghty blocking operations
static int32_t event_loop_mutex_app_worker_thread(void* context) {
furi_assert(context);
EventLoopMutexApp* app = context;
FURI_LOG_I(TAG, "Worker thread started");
// Run 10 iterations of simulated work
for(uint32_t i = 0; i < WORKER_ITERATION_COUNT; ++i) {
FURI_LOG_I(TAG, "Doing work ...");
// Take the mutex so that no-one can access the worker_result variable
furi_check(furi_mutex_acquire(app->worker_mutex, FuriWaitForever) == FuriStatusOk);
// Simulate a blocking operation with a random delay between 900 and 1100 ms
const uint32_t work_time_ms = 900 + furi_hal_random_get() % 200;
furi_delay_ms(work_time_ms);
// Simulate a result with a random number between 0 and 255
app->worker_result = furi_hal_random_get() % 0xFF;
FURI_LOG_I(TAG, "Work done in %lu ms", work_time_ms);
// Release the mutex, which will notify the event loop that the result is ready
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
// Return control to the scheduler so that the event loop can take the mutex in its turn
furi_thread_yield();
}
FURI_LOG_I(TAG, "All work done, worker thread out!");
// Request the event loop to stop
furi_event_loop_stop(app->event_loop);
return 0;
}
// This function is being run each time when the mutex gets released
static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
EventLoopMutexApp* app = context;
furi_assert(object == app->worker_mutex);
// Take the mutex so that no-one can access the worker_result variable
// IMPORTANT: the wait time MUST be 0, i.e. the event loop event callbacks
// must NOT ever block. If it is possible that the mutex will be taken by
// others, then the event callback code must take it into account.
furi_check(furi_mutex_acquire(app->worker_mutex, 0) == FuriStatusOk);
// Access the worker_result variable and print it.
FURI_LOG_I(TAG, "Result available! Value: %u", app->worker_result);
// Release the mutex, enabling the worker thread to continue when it's ready
furi_check(furi_mutex_release(app->worker_mutex) == FuriStatusOk);
// Subscribe for the mutex release events again, since we were unsubscribed automatically
// before processing the event.
furi_event_loop_subscribe_mutex(
app->event_loop,
app->worker_mutex,
MUTEX_EVENT_AND_FLAGS,
event_loop_mutex_app_event_callback,
app);
return true;
}
static EventLoopMutexApp* event_loop_mutex_app_alloc(void) {
EventLoopMutexApp* app = malloc(sizeof(EventLoopMutexApp));
// Create an event loop instance.
app->event_loop = furi_event_loop_alloc();
// Create a worker thread instance.
app->worker_thread = furi_thread_alloc_ex(
"EventLoopMutexWorker", 1024, event_loop_mutex_app_worker_thread, app);
// Create a mutex instance.
app->worker_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
// Subscribe for the mutex release events.
// Note that since FuriEventLoopEventFlagOneShot is used, we will be automatically unsubscribed
// from events before entering the event processing callback. This is necessary in order to not
// trigger on events caused by releasing the mutex in the callback.
furi_event_loop_subscribe_mutex(
app->event_loop,
app->worker_mutex,
MUTEX_EVENT_AND_FLAGS,
event_loop_mutex_app_event_callback,
app);
return app;
}
static void event_loop_mutex_app_free(EventLoopMutexApp* app) {
// IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop.
// Failure to do so will result in a crash.
furi_event_loop_unsubscribe(app->event_loop, app->worker_mutex);
// Delete all instances
furi_thread_free(app->worker_thread);
furi_mutex_free(app->worker_mutex);
furi_event_loop_free(app->event_loop);
free(app);
}
static void event_loop_mutex_app_run(EventLoopMutexApp* app) {
furi_thread_start(app->worker_thread);
furi_event_loop_run(app->event_loop);
furi_thread_join(app->worker_thread);
}
// The application's entry point - referenced in application.fam
int32_t example_event_loop_mutex_app(void* arg) {
UNUSED(arg);
EventLoopMutexApp* app = event_loop_mutex_app_alloc();
event_loop_mutex_app_run(app);
event_loop_mutex_app_free(app);
return 0;
}