diff --git a/src/runtime/uv/event_loop.cpp b/src/runtime/uv/event_loop.cpp index 600d17d596b5..0afd59534ee4 100644 --- a/src/runtime/uv/event_loop.cpp +++ b/src/runtime/uv/event_loop.cpp @@ -2,10 +2,21 @@ Copyright (c) 2024 Lean FRO, LLC. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Author: Sofia Rodrigues +Author: Sofia Rodrigues, Henrik Böving */ #include "runtime/uv/event_loop.h" + +/* +This file builds a thread safe event loop on top of the thread unsafe libuv event loop. +We achieve this by always having a `uv_async_t` associated with the libuv event loop. +As `uv_async_t` are a thread safe primitive it is safe to send a notification to it from another +thread. Once this notification arrives the event loop suspends its own execution and unlocks a mutex +that protects it. This mutex can then be taken by another thread that wants to work with the event +loop. After that work is done it signals a condition variable that the event loop is waiting on +to continue its execution. +*/ + namespace lean { #ifndef LEAN_EMSCRIPTEN using namespace std; @@ -26,8 +37,8 @@ void async_callback(uv_async_t * handle) { uv_stop(handle->loop); } -// Awakes the event loop and stops it so it can receive future requests. -void event_loop_wake(event_loop_t * event_loop) { +// Interrupts the event loop and stops it so it can receive future requests. +void event_loop_interrupt(event_loop_t * event_loop) { int result = uv_async_send(&event_loop->async); (void)result; lean_assert(result == 0); @@ -46,7 +57,7 @@ void event_loop_init(event_loop_t * event_loop) { void event_loop_lock(event_loop_t * event_loop) { if (uv_mutex_trylock(&event_loop->mutex) != 0) { event_loop->n_waiters++; - event_loop_wake(event_loop); + event_loop_interrupt(event_loop); uv_mutex_lock(&event_loop->mutex); event_loop->n_waiters--; } @@ -54,10 +65,10 @@ void event_loop_lock(event_loop_t * event_loop) { // Unlock event loop void event_loop_unlock(event_loop_t * event_loop) { - uv_mutex_unlock(&event_loop->mutex); if (event_loop->n_waiters == 0) { uv_cond_signal(&event_loop->cond_var); } + uv_mutex_unlock(&event_loop->mutex); } // Runs the loop and stops when it needs to register new requests. @@ -65,13 +76,16 @@ void event_loop_run_loop(event_loop_t * event_loop) { while (uv_loop_alive(event_loop->loop)) { uv_mutex_lock(&event_loop->mutex); - if (event_loop->n_waiters != 0) { + while (event_loop->n_waiters != 0) { uv_cond_wait(&event_loop->cond_var, &event_loop->mutex); } - if (event_loop->n_waiters == 0) { - uv_run(event_loop->loop, UV_RUN_ONCE); - } + uv_run(event_loop->loop, UV_RUN_ONCE); + /* + * We leave `uv_run` only when `uv_stop` is called as there is always the `uv_async_t` so + * we can never run out of things to wait on. `uv_stop` is only called from `async_callback` + * when another thread wants to work with the event loop so we need to give up the mutex. + */ uv_mutex_unlock(&event_loop->mutex); } diff --git a/src/runtime/uv/event_loop.h b/src/runtime/uv/event_loop.h index 87524b3328e3..42dba397e3fa 100644 --- a/src/runtime/uv/event_loop.h +++ b/src/runtime/uv/event_loop.h @@ -20,10 +20,10 @@ using namespace std; // Event loop structure for managing asynchronous events and synchronization across multiple threads. typedef struct { uv_loop_t * loop; // The libuv event loop. - uv_mutex_t mutex; // Mutex for protecting shared resources. - uv_cond_t cond_var; // Condition variable for thread synchronization. - uv_async_t async; // Async handle to notify the main event loop. - _Atomic(int) n_waiters; // Atomic counter for managing waiters. + uv_mutex_t mutex; // Mutex for protecting `loop`. + uv_cond_t cond_var; // Condition variable for signaling that `loop` is free. + uv_async_t async; // Async handle to interrupt `loop`. + _Atomic(int) n_waiters; // Atomic counter for managing waiters for `loop`. } event_loop_t; // The multithreaded event loop object for all tasks in the task manager. @@ -35,7 +35,6 @@ void event_loop_init(event_loop_t *event_loop); void event_loop_cleanup(event_loop_t *event_loop); void event_loop_lock(event_loop_t *event_loop); void event_loop_unlock(event_loop_t *event_loop); -void event_loop_wake(event_loop_t *event_loop); void event_loop_run_loop(event_loop_t *event_loop); #endif