neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

loop.c (6900B)


      1 #include <stdbool.h>
      2 #include <stdint.h>
      3 #include <stdlib.h>
      4 #include <uv.h>
      5 
      6 #include "nvim/event/loop.h"
      7 #include "nvim/event/multiqueue.h"
      8 #include "nvim/log.h"
      9 #include "nvim/memory.h"
     10 #include "nvim/os/time.h"
     11 #include "nvim/types_defs.h"
     12 
     13 #include "event/loop.c.generated.h"
     14 
     15 void loop_init(Loop *loop, void *data)
     16 {
     17  uv_loop_init(&loop->uv);
     18  loop->recursive = 0;
     19  loop->closing = false;
     20  loop->uv.data = loop;
     21  kv_init(loop->children);
     22  loop->events = multiqueue_new(loop_on_put, loop);
     23  loop->fast_events = multiqueue_new_child(loop->events);
     24  loop->thread_events = multiqueue_new(NULL, NULL);
     25  uv_mutex_init(&loop->mutex);
     26  uv_async_init(&loop->uv, &loop->async, async_cb);
     27  uv_signal_init(&loop->uv, &loop->children_watcher);
     28  uv_timer_init(&loop->uv, &loop->children_kill_timer);
     29  uv_timer_init(&loop->uv, &loop->poll_timer);
     30  uv_timer_init(&loop->uv, &loop->exit_delay_timer);
     31  loop->poll_timer.data = xmalloc(sizeof(bool));  // "timeout expired" flag
     32 }
     33 
     34 /// Process `Loop.uv` events with a timeout.
     35 ///
     36 /// @param loop
     37 /// @param ms  0: non-blocking poll.
     38 ///            > 0: timeout after `ms`.
     39 ///            < 0: wait forever.
     40 /// @return  true if `ms` > 0 and was reached
     41 static bool loop_uv_run(Loop *loop, int64_t ms)
     42 {
     43  if (loop->recursive++) {
     44    abort();  // Should not re-enter uv_run
     45  }
     46 
     47  uv_run_mode mode = UV_RUN_ONCE;
     48  bool *timeout_expired = loop->poll_timer.data;
     49  *timeout_expired = false;
     50 
     51  if (ms > 0) {
     52    // This timer ensures UV_RUN_ONCE does not block indefinitely for I/O.
     53    uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms);
     54  } else if (ms == 0) {
     55    // For ms == 0, do a non-blocking event poll.
     56    mode = UV_RUN_NOWAIT;
     57  }
     58 
     59  uv_run(&loop->uv, mode);
     60 
     61  if (ms > 0) {
     62    uv_timer_stop(&loop->poll_timer);
     63  }
     64 
     65  loop->recursive--;  // Can re-enter uv_run now
     66  return *timeout_expired;
     67 }
     68 
     69 /// Processes one `Loop.uv` event (at most).
     70 /// Processes all `Loop.fast_events` events.
     71 /// Does NOT process `Loop.events`, that is an application-specific decision.
     72 ///
     73 /// @param loop
     74 /// @param ms  0: non-blocking poll.
     75 ///            > 0: timeout after `ms`.
     76 ///            < 0: wait forever.
     77 /// @return  true if `ms` > 0 and was reached
     78 bool loop_poll_events(Loop *loop, int64_t ms)
     79 {
     80  bool timeout_expired = loop_uv_run(loop, ms);
     81  multiqueue_process_events(loop->fast_events);
     82  return timeout_expired;
     83 }
     84 
     85 /// Schedules a fast event from another thread.
     86 ///
     87 /// @note Event is queued into `fast_events`, which is processed outside of the
     88 ///       primary `events` queue by loop_poll_events(). For `main_loop`, that
     89 ///       means `fast_events` is NOT processed in an "editor mode"
     90 ///       (VimState.execute), so redraw and other side effects are likely to be
     91 ///       skipped.
     92 /// @see loop_schedule_deferred
     93 void loop_schedule_fast(Loop *loop, Event event)
     94 {
     95  uv_mutex_lock(&loop->mutex);
     96  multiqueue_put_event(loop->thread_events, event);
     97  uv_async_send(&loop->async);
     98  uv_mutex_unlock(&loop->mutex);
     99 }
    100 
    101 /// Schedules an event from another thread. Unlike loop_schedule_fast(), the
    102 /// event is forwarded to `Loop.events`, instead of being processed immediately.
    103 ///
    104 /// @see loop_schedule_fast
    105 void loop_schedule_deferred(Loop *loop, Event event)
    106 {
    107  Event *eventp = xmalloc(sizeof(*eventp));
    108  *eventp = event;
    109  loop_schedule_fast(loop, event_create(loop_deferred_event, loop, eventp));
    110 }
    111 static void loop_deferred_event(void **argv)
    112 {
    113  Loop *loop = argv[0];
    114  Event *eventp = argv[1];
    115  multiqueue_put_event(loop->events, *eventp);
    116  xfree(eventp);
    117 }
    118 
    119 void loop_on_put(MultiQueue *queue, void *data)
    120 {
    121  Loop *loop = data;
    122  // Sometimes libuv will run pending callbacks (timer for example) before
    123  // blocking for a poll. If this happens and the callback pushes a event to one
    124  // of the queues, the event would only be processed after the poll
    125  // returns (user hits a key for example). To avoid this scenario, we call
    126  // uv_stop when a event is enqueued.
    127  // Only call uv_stop() when the loop is actually running, otherwise it will
    128  // instead make the next uv_run() stop immediately.
    129  if (loop->recursive) {
    130    uv_stop(&loop->uv);
    131  }
    132 }
    133 
    134 #if !defined(EXITFREE)
    135 static void loop_walk_cb(uv_handle_t *handle, void *arg)
    136 {
    137  if (!uv_is_closing(handle)) {
    138    uv_close(handle, NULL);
    139  }
    140 }
    141 #endif
    142 
    143 /// Closes `loop` and its handles, and frees its structures.
    144 ///
    145 /// @param loop  Loop to destroy
    146 /// @param wait  Wait briefly for handles to deref
    147 ///
    148 /// @returns false if the loop could not be closed gracefully
    149 bool loop_close(Loop *loop, bool wait)
    150 {
    151  bool rv = true;
    152  loop->closing = true;
    153  uv_mutex_destroy(&loop->mutex);
    154  uv_close((uv_handle_t *)&loop->children_watcher, NULL);
    155  uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
    156  uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb);
    157  uv_close((uv_handle_t *)&loop->exit_delay_timer, NULL);
    158  uv_close((uv_handle_t *)&loop->async, NULL);
    159  uint64_t start = wait ? os_hrtime() : 0;
    160  bool didstop = false;
    161  while (true) {
    162    // Run the loop to tickle close-callbacks (which should then free memory).
    163    // Use UV_RUN_NOWAIT to avoid a hang. #11820
    164    uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
    165    if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) {
    166      break;
    167    }
    168    uint64_t elapsed_s = (os_hrtime() - start) / 1000000000;  // seconds
    169    if (elapsed_s >= 2) {
    170      // Some libuv resource was not correctly deref'd. Log and bail.
    171      rv = false;
    172      ELOG("uv_loop_close() hang?");
    173      log_uv_handles(&loop->uv);
    174      break;
    175    }
    176 #if defined(EXITFREE)
    177    (void)didstop;
    178 #else
    179    if (!didstop) {
    180      // Loop won’t block for I/O after this.
    181      uv_stop(&loop->uv);
    182      // XXX: Close all (lua/luv!) handles. But loop_walk_cb() does not call
    183      // resource-specific close-callbacks, so this leaks memory...
    184      uv_walk(&loop->uv, loop_walk_cb, NULL);
    185      didstop = true;
    186    }
    187 #endif
    188  }
    189  multiqueue_free(loop->fast_events);
    190  multiqueue_free(loop->thread_events);
    191  multiqueue_free(loop->events);
    192  kv_destroy(loop->children);
    193  return rv;
    194 }
    195 
    196 void loop_purge(Loop *loop)
    197 {
    198  uv_mutex_lock(&loop->mutex);
    199  multiqueue_purge_events(loop->thread_events);
    200  multiqueue_purge_events(loop->fast_events);
    201  uv_mutex_unlock(&loop->mutex);
    202 }
    203 
    204 size_t loop_size(Loop *loop)
    205 {
    206  uv_mutex_lock(&loop->mutex);
    207  size_t rv = multiqueue_size(loop->thread_events);
    208  uv_mutex_unlock(&loop->mutex);
    209  return rv;
    210 }
    211 
    212 static void async_cb(uv_async_t *handle)
    213 {
    214  Loop *l = handle->loop->data;
    215  uv_mutex_lock(&l->mutex);
    216  // Flush thread_events to fast_events for processing on main loop.
    217  multiqueue_move_events(l->fast_events, l->thread_events);
    218  uv_mutex_unlock(&l->mutex);
    219 }
    220 
    221 static void timer_cb(uv_timer_t *handle)
    222 {
    223  bool *timeout_expired = handle->data;
    224  *timeout_expired = true;
    225 }
    226 
    227 static void timer_close_cb(uv_handle_t *handle)
    228 {
    229  xfree(handle->data);
    230 }