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 }