Files
neovim/src/nvim/event/loop.c
zeertzjq 93c93a0e36 refactor: remove "once" argument of loop_uv_run() (#27841)
It is always set to true when used, and makes the code a bit confusing.
2024-03-13 11:27:04 +08:00

232 lines
6.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <uv.h>
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/os/time.h"
#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/loop.c.generated.h"
#endif
void loop_init(Loop *loop, void *data)
{
uv_loop_init(&loop->uv);
loop->recursive = 0;
loop->closing = false;
loop->uv.data = loop;
loop->children = kl_init(WatcherPtr);
loop->events = multiqueue_new_parent(loop_on_put, loop);
loop->fast_events = multiqueue_new_child(loop->events);
loop->thread_events = multiqueue_new_parent(NULL, NULL);
uv_mutex_init(&loop->mutex);
uv_async_init(&loop->uv, &loop->async, async_cb);
uv_signal_init(&loop->uv, &loop->children_watcher);
uv_timer_init(&loop->uv, &loop->children_kill_timer);
uv_timer_init(&loop->uv, &loop->poll_timer);
uv_timer_init(&loop->uv, &loop->exit_delay_timer);
loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag
}
/// Process `Loop.uv` events with a timeout.
///
/// @param loop
/// @param ms 0: non-blocking poll.
/// > 0: timeout after `ms`.
/// < 0: wait forever.
/// @return true if `ms` > 0 and was reached
static bool loop_uv_run(Loop *loop, int64_t ms)
{
if (loop->recursive++) {
abort(); // Should not re-enter uv_run
}
uv_run_mode mode = UV_RUN_ONCE;
bool *timeout_expired = loop->poll_timer.data;
*timeout_expired = false;
if (ms > 0) {
// This timer ensures UV_RUN_ONCE does not block indefinitely for I/O.
uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms);
} else if (ms == 0) {
// For ms == 0, do a non-blocking event poll.
mode = UV_RUN_NOWAIT;
}
uv_run(&loop->uv, mode);
if (ms > 0) {
uv_timer_stop(&loop->poll_timer);
}
loop->recursive--; // Can re-enter uv_run now
return *timeout_expired;
}
/// Processes one `Loop.uv` event (at most).
/// Processes all `Loop.fast_events` events.
/// Does NOT process `Loop.events`, that is an application-specific decision.
///
/// @param loop
/// @param ms 0: non-blocking poll.
/// > 0: timeout after `ms`.
/// < 0: wait forever.
/// @return true if `ms` > 0 and was reached
bool loop_poll_events(Loop *loop, int64_t ms)
{
bool timeout_expired = loop_uv_run(loop, ms);
multiqueue_process_events(loop->fast_events);
return timeout_expired;
}
/// Schedules a fast event from another thread.
///
/// @note Event is queued into `fast_events`, which is processed outside of the
/// primary `events` queue by loop_poll_events(). For `main_loop`, that
/// means `fast_events` is NOT processed in an "editor mode"
/// (VimState.execute), so redraw and other side effects are likely to be
/// skipped.
/// @see loop_schedule_deferred
void loop_schedule_fast(Loop *loop, Event event)
{
uv_mutex_lock(&loop->mutex);
multiqueue_put_event(loop->thread_events, event);
uv_async_send(&loop->async);
uv_mutex_unlock(&loop->mutex);
}
/// Schedules an event from another thread. Unlike loop_schedule_fast(), the
/// event is forwarded to `Loop.events`, instead of being processed immediately.
///
/// @see loop_schedule_fast
void loop_schedule_deferred(Loop *loop, Event event)
{
Event *eventp = xmalloc(sizeof(*eventp));
*eventp = event;
loop_schedule_fast(loop, event_create(loop_deferred_event, loop, eventp));
}
static void loop_deferred_event(void **argv)
{
Loop *loop = argv[0];
Event *eventp = argv[1];
multiqueue_put_event(loop->events, *eventp);
xfree(eventp);
}
void loop_on_put(MultiQueue *queue, void *data)
{
Loop *loop = data;
// Sometimes libuv will run pending callbacks (timer for example) before
// blocking for a poll. If this happens and the callback pushes a event to one
// of the queues, the event would only be processed after the poll
// returns (user hits a key for example). To avoid this scenario, we call
// uv_stop when a event is enqueued.
uv_stop(&loop->uv);
}
#if !defined(EXITFREE)
static void loop_walk_cb(uv_handle_t *handle, void *arg)
{
if (!uv_is_closing(handle)) {
uv_close(handle, NULL);
}
}
#endif
/// Closes `loop` and its handles, and frees its structures.
///
/// @param loop Loop to destroy
/// @param wait Wait briefly for handles to deref
///
/// @returns false if the loop could not be closed gracefully
bool loop_close(Loop *loop, bool wait)
{
bool rv = true;
loop->closing = true;
uv_mutex_destroy(&loop->mutex);
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb);
uv_close((uv_handle_t *)&loop->exit_delay_timer, NULL);
uv_close((uv_handle_t *)&loop->async, NULL);
uint64_t start = wait ? os_hrtime() : 0;
bool didstop = false;
while (true) {
// Run the loop to tickle close-callbacks (which should then free memory).
// Use UV_RUN_NOWAIT to avoid a hang. #11820
uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) {
break;
}
uint64_t elapsed_s = (os_hrtime() - start) / 1000000000; // seconds
if (elapsed_s >= 2) {
// Some libuv resource was not correctly deref'd. Log and bail.
rv = false;
ELOG("uv_loop_close() hang?");
log_uv_handles(&loop->uv);
break;
}
#if defined(EXITFREE)
(void)didstop;
#else
if (!didstop) {
// Loop wont block for I/O after this.
uv_stop(&loop->uv);
// XXX: Close all (lua/luv!) handles. But loop_walk_cb() does not call
// resource-specific close-callbacks, so this leaks memory...
uv_walk(&loop->uv, loop_walk_cb, NULL);
didstop = true;
}
#endif
}
multiqueue_free(loop->fast_events);
multiqueue_free(loop->thread_events);
multiqueue_free(loop->events);
kl_destroy(WatcherPtr, loop->children);
return rv;
}
void loop_purge(Loop *loop)
{
uv_mutex_lock(&loop->mutex);
multiqueue_purge_events(loop->thread_events);
multiqueue_purge_events(loop->fast_events);
uv_mutex_unlock(&loop->mutex);
}
size_t loop_size(Loop *loop)
{
uv_mutex_lock(&loop->mutex);
size_t rv = multiqueue_size(loop->thread_events);
uv_mutex_unlock(&loop->mutex);
return rv;
}
static void async_cb(uv_async_t *handle)
{
Loop *l = handle->loop->data;
uv_mutex_lock(&l->mutex);
// Flush thread_events to fast_events for processing on main loop.
while (!multiqueue_empty(l->thread_events)) {
Event ev = multiqueue_get(l->thread_events);
multiqueue_put_event(l->fast_events, ev);
}
uv_mutex_unlock(&l->mutex);
}
static void timer_cb(uv_timer_t *handle)
{
bool *timeout_expired = handle->data;
*timeout_expired = true;
}
static void timer_close_cb(uv_handle_t *handle)
{
xfree(handle->data);
}