aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/event
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/event')
-rw-r--r--src/nvim/event/defs.h26
-rw-r--r--src/nvim/event/libuv_process.c2
-rw-r--r--src/nvim/event/loop.h64
-rw-r--r--src/nvim/event/process.c100
-rw-r--r--src/nvim/event/process.h2
-rw-r--r--src/nvim/event/pty_process.c240
-rw-r--r--src/nvim/event/pty_process.h30
-rw-r--r--src/nvim/event/rstream.c8
-rw-r--r--src/nvim/event/socket.c2
-rw-r--r--src/nvim/event/stream.c1
-rw-r--r--src/nvim/event/stream.h1
-rw-r--r--src/nvim/event/time.c5
-rw-r--r--src/nvim/event/time.h1
13 files changed, 137 insertions, 345 deletions
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
index b802866a3d..e5335d9f25 100644
--- a/src/nvim/event/defs.h
+++ b/src/nvim/event/defs.h
@@ -14,19 +14,19 @@ typedef struct message {
} Event;
typedef void(*event_scheduler)(Event event, void *data);
-#define VA_EVENT_INIT(event, p, h, a) \
- do { \
- assert(a <= EVENT_HANDLER_MAX_ARGC); \
- (event)->priority = p; \
- (event)->handler = h; \
- if (a) { \
- va_list args; \
- va_start(args, a); \
- for (int i = 0; i < a; i++) { \
- (event)->argv[i] = va_arg(args, void *); \
- } \
- va_end(args); \
- } \
+#define VA_EVENT_INIT(event, p, h, a) \
+ do { \
+ assert(a <= EVENT_HANDLER_MAX_ARGC); \
+ (event)->priority = p; \
+ (event)->handler = h; \
+ if (a) { \
+ va_list args; \
+ va_start(args, a); \
+ for (int i = 0; i < a; i++) { \
+ (event)->argv[i] = va_arg(args, void *); \
+ } \
+ va_end(args); \
+ } \
} while (0)
static inline Event event_create(int priority, argv_callback cb, int argc, ...)
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 9ef3468284..a68badcc8f 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -25,7 +25,7 @@ bool libuv_process_spawn(LibuvProcess *uvproc)
uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
}
uvproc->uvopts.exit_cb = exit_cb;
- uvproc->uvopts.cwd = NULL;
+ uvproc->uvopts.cwd = proc->cwd;
uvproc->uvopts.env = NULL;
uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3;
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index 0c1fcb5ed9..407aa4245f 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -26,43 +26,43 @@ typedef struct loop {
int recursive;
} Loop;
-#define CREATE_EVENT(queue, handler, argc, ...) \
- do { \
- if (queue) { \
- queue_put((queue), (handler), argc, __VA_ARGS__); \
- } else { \
- void *argv[argc] = {__VA_ARGS__}; \
- (handler)(argv); \
- } \
+#define CREATE_EVENT(queue, handler, argc, ...) \
+ do { \
+ if (queue) { \
+ queue_put((queue), (handler), argc, __VA_ARGS__); \
+ } else { \
+ void *argv[argc] = { __VA_ARGS__ }; \
+ (handler)(argv); \
+ } \
} while (0)
// Poll for events until a condition or timeout
-#define LOOP_PROCESS_EVENTS_UNTIL(loop, queue, timeout, condition) \
- do { \
- int remaining = timeout; \
- uint64_t before = (remaining > 0) ? os_hrtime() : 0; \
- while (!(condition)) { \
- LOOP_PROCESS_EVENTS(loop, queue, remaining); \
- if (remaining == 0) { \
- break; \
- } else if (remaining > 0) { \
- uint64_t now = os_hrtime(); \
- remaining -= (int) ((now - before) / 1000000); \
- before = now; \
- if (remaining <= 0) { \
- break; \
- } \
- } \
- } \
+#define LOOP_PROCESS_EVENTS_UNTIL(loop, queue, timeout, condition) \
+ do { \
+ int remaining = timeout; \
+ uint64_t before = (remaining > 0) ? os_hrtime() : 0; \
+ while (!(condition)) { \
+ LOOP_PROCESS_EVENTS(loop, queue, remaining); \
+ if (remaining == 0) { \
+ break; \
+ } else if (remaining > 0) { \
+ uint64_t now = os_hrtime(); \
+ remaining -= (int) ((now - before) / 1000000); \
+ before = now; \
+ if (remaining <= 0) { \
+ break; \
+ } \
+ } \
+ } \
} while (0)
-#define LOOP_PROCESS_EVENTS(loop, queue, timeout) \
- do { \
- if (queue && !queue_empty(queue)) { \
- queue_process_events(queue); \
- } else { \
- loop_poll_events(loop, timeout); \
- } \
+#define LOOP_PROCESS_EVENTS(loop, queue, timeout) \
+ do { \
+ if (queue && !queue_empty(queue)) { \
+ queue_process_events(queue); \
+ } else { \
+ loop_poll_events(loop, timeout); \
+ } \
} while (0)
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 9bb62891c7..317e40e43a 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -9,7 +9,7 @@
#include "nvim/event/wstream.h"
#include "nvim/event/process.h"
#include "nvim/event/libuv_process.h"
-#include "nvim/event/pty_process.h"
+#include "nvim/os/pty_process.h"
#include "nvim/globals.h"
#include "nvim/log.h"
@@ -22,11 +22,11 @@
#define TERM_TIMEOUT 1000000000
#define KILL_TIMEOUT (TERM_TIMEOUT * 2)
-#define CLOSE_PROC_STREAM(proc, stream) \
- do { \
- if (proc->stream && !proc->stream->closed) { \
- stream_close(proc->stream, NULL); \
- } \
+#define CLOSE_PROC_STREAM(proc, stream) \
+ do { \
+ if (proc->stream && !proc->stream->closed) { \
+ stream_close(proc->stream, NULL); \
+ } \
} while (0)
static bool process_is_tearing_down = false;
@@ -116,23 +116,20 @@ void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
process_is_tearing_down = true;
kl_iter(WatcherPtr, loop->children, current) {
Process *proc = (*current)->data;
- if (proc->detach) {
+ if (proc->detach || proc->type == kProcessTypePty) {
// Close handles to process without killing it.
CREATE_EVENT(loop->events, process_close_handles, 1, proc);
} else {
- if (proc->type == kProcessTypeUv) {
- uv_kill(proc->pid, SIGTERM);
- proc->term_sent = true;
- process_stop(proc);
- } else { // kProcessTypePty
- process_close_streams(proc);
- pty_process_close_master((PtyProcess *)proc);
- }
+ uv_kill(proc->pid, SIGTERM);
+ proc->term_sent = true;
+ process_stop(proc);
}
}
- // Wait until all children exit
- LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1, kl_empty(loop->children));
+ // Wait until all children exit and all close events are processed.
+ LOOP_PROCESS_EVENTS_UNTIL(
+ loop, loop->events, -1,
+ kl_empty(loop->children) && queue_empty(loop->events));
pty_process_teardown(loop);
}
@@ -315,8 +312,10 @@ static void decref(Process *proc)
static void process_close(Process *proc)
FUNC_ATTR_NONNULL_ARG(1)
{
- if (process_is_tearing_down && proc->detach && proc->closed) {
- // If a detached process dies while tearing down it might get closed twice.
+ if (process_is_tearing_down && (proc->detach || proc->type == kProcessTypePty)
+ && proc->closed) {
+ // If a detached/pty process dies while tearing down it might get closed
+ // twice.
return;
}
assert(!proc->closed);
@@ -333,9 +332,61 @@ static void process_close(Process *proc)
}
}
+/// Flush output stream.
+///
+/// @param proc Process, for which an output stream should be flushed.
+/// @param stream Stream to flush.
+static void flush_stream(Process *proc, Stream *stream)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (!stream || stream->closed) {
+ return;
+ }
+
+ // Maximal remaining data size of terminated process is system
+ // buffer size.
+ // Also helps with a child process that keeps the output streams open. If it
+ // keeps sending data, we only accept as much data as the system buffer size.
+ // Otherwise this would block cleanup/teardown.
+ int system_buffer_size = 0;
+ int err = uv_recv_buffer_size((uv_handle_t *)&stream->uv.pipe,
+ &system_buffer_size);
+ if (err) {
+ system_buffer_size = (int)rbuffer_capacity(stream->buffer);
+ }
+
+ size_t max_bytes = stream->num_bytes + (size_t)system_buffer_size;
+
+ // Read remaining data.
+ while (!stream->closed && stream->num_bytes < max_bytes) {
+ // Remember number of bytes before polling
+ size_t num_bytes = stream->num_bytes;
+
+ // Poll for data and process the generated events.
+ loop_poll_events(proc->loop, 0);
+ if (proc->events) {
+ queue_process_events(proc->events);
+ }
+
+ // Stream can be closed if it is empty.
+ if (num_bytes == stream->num_bytes) {
+ if (stream->read_cb) {
+ // Stream callback could miss EOF handling if a child keeps the stream
+ // open.
+ stream->read_cb(stream, stream->buffer, 0, stream->data, true);
+ }
+ break;
+ }
+ }
+}
+
static void process_close_handles(void **argv)
{
Process *proc = argv[0];
+
+ flush_stream(proc, proc->out);
+ flush_stream(proc, proc->err);
+
process_close_streams(proc);
process_close(proc);
}
@@ -350,11 +401,12 @@ static void on_process_exit(Process *proc)
uv_timer_stop(&loop->children_kill_timer);
}
- // Process handles are closed in the next event loop tick. This is done to
- // give libuv more time to read data from the OS after the process exits(If
- // process_close_streams is called with data still in the OS buffer, we lose
- // it)
- CREATE_EVENT(proc->events, process_close_handles, 1, proc);
+ // Process has terminated, but there could still be data to be read from the
+ // OS. We are still in the libuv loop, so we cannot call code that polls for
+ // more data directly. Instead delay the reading after the libuv loop by
+ // queueing process_close_handles() as an event.
+ Queue *queue = proc->events ? proc->events : loop->events;
+ CREATE_EVENT(queue, process_close_handles, 1, proc);
}
static void on_process_stream_close(Stream *stream, void *data)
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index e23c8ea60f..a4c6e7eeb2 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -21,6 +21,7 @@ struct process {
int pid, status, refcount;
// set to the hrtime of when process_stop was called for the process.
uint64_t stopped_time;
+ char *cwd;
char **argv;
Stream *in, *out, *err;
process_exit_cb cb;
@@ -40,6 +41,7 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
.status = 0,
.refcount = 0,
.stopped_time = 0,
+ .cwd = NULL,
.argv = NULL,
.in = NULL,
.out = NULL,
diff --git a/src/nvim/event/pty_process.c b/src/nvim/event/pty_process.c
deleted file mode 100644
index 8eef72f12f..0000000000
--- a/src/nvim/event/pty_process.c
+++ /dev/null
@@ -1,240 +0,0 @@
-// Some of the code came from pangoterm and libuv
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <unistd.h>
-#include <termios.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/ioctl.h>
-
-// forkpty is not in POSIX, so headers are platform-specific
-#if defined(__FreeBSD__)
-# include <libutil.h>
-#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
-# include <util.h>
-#else
-# include <pty.h>
-#endif
-
-#include <uv.h>
-
-#include "nvim/lib/klist.h"
-
-#include "nvim/event/loop.h"
-#include "nvim/event/rstream.h"
-#include "nvim/event/wstream.h"
-#include "nvim/event/process.h"
-#include "nvim/event/pty_process.h"
-#include "nvim/log.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/pty_process.c.generated.h"
-#endif
-
-bool pty_process_spawn(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
- static struct termios termios;
- if (!termios.c_cflag) {
- init_termios(&termios);
- }
-
- Process *proc = (Process *)ptyproc;
- assert(!proc->err);
- uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
- ptyproc->winsize = (struct winsize){ptyproc->height, ptyproc->width, 0, 0};
- uv_disable_stdio_inheritance();
- int master;
- int pid = forkpty(&master, NULL, &termios, &ptyproc->winsize);
-
- if (pid < 0) {
- ELOG("forkpty failed: %s", strerror(errno));
- return false;
- } else if (pid == 0) {
- init_child(ptyproc);
- abort();
- }
-
- // make sure the master file descriptor is non blocking
- int master_status_flags = fcntl(master, F_GETFL);
- if (master_status_flags == -1) {
- ELOG("Failed to get master descriptor status flags: %s", strerror(errno));
- goto error;
- }
- if (fcntl(master, F_SETFL, master_status_flags | O_NONBLOCK) == -1) {
- ELOG("Failed to make master descriptor non-blocking: %s", strerror(errno));
- goto error;
- }
-
- if (proc->in && !set_duplicating_descriptor(master, &proc->in->uv.pipe)) {
- goto error;
- }
- if (proc->out && !set_duplicating_descriptor(master, &proc->out->uv.pipe)) {
- goto error;
- }
-
- ptyproc->tty_fd = master;
- proc->pid = pid;
- return true;
-
-error:
- close(master);
- kill(pid, SIGKILL);
- waitpid(pid, NULL, 0);
- return false;
-}
-
-void pty_process_resize(PtyProcess *ptyproc, uint16_t width,
- uint16_t height)
- FUNC_ATTR_NONNULL_ALL
-{
- ptyproc->winsize = (struct winsize){height, width, 0, 0};
- ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
-}
-
-void pty_process_close(PtyProcess *ptyproc)
- FUNC_ATTR_NONNULL_ALL
-{
- pty_process_close_master(ptyproc);
- Process *proc = (Process *)ptyproc;
- if (proc->internal_close_cb) {
- proc->internal_close_cb(proc);
- }
-}
-
-void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
-{
- if (ptyproc->tty_fd >= 0) {
- close(ptyproc->tty_fd);
- ptyproc->tty_fd = -1;
- }
-}
-
-void pty_process_teardown(Loop *loop)
-{
- uv_signal_stop(&loop->children_watcher);
-}
-
-static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
-{
- unsetenv("COLUMNS");
- unsetenv("LINES");
- unsetenv("TERMCAP");
- unsetenv("COLORTERM");
- unsetenv("COLORFGBG");
-
- signal(SIGCHLD, SIG_DFL);
- signal(SIGHUP, SIG_DFL);
- signal(SIGINT, SIG_DFL);
- signal(SIGQUIT, SIG_DFL);
- signal(SIGTERM, SIG_DFL);
- signal(SIGALRM, SIG_DFL);
-
- setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1);
- execvp(ptyproc->process.argv[0], ptyproc->process.argv);
- fprintf(stderr, "execvp failed: %s\n", strerror(errno));
-}
-
-static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
-{
- // Taken from pangoterm
- termios->c_iflag = ICRNL|IXON;
- termios->c_oflag = OPOST|ONLCR;
-#ifdef TAB0
- termios->c_oflag |= TAB0;
-#endif
- termios->c_cflag = CS8|CREAD;
- termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
-
- cfsetspeed(termios, 38400);
-
-#ifdef IUTF8
- termios->c_iflag |= IUTF8;
-#endif
-#ifdef NL0
- termios->c_oflag |= NL0;
-#endif
-#ifdef CR0
- termios->c_oflag |= CR0;
-#endif
-#ifdef BS0
- termios->c_oflag |= BS0;
-#endif
-#ifdef VT0
- termios->c_oflag |= VT0;
-#endif
-#ifdef FF0
- termios->c_oflag |= FF0;
-#endif
-#ifdef ECHOCTL
- termios->c_lflag |= ECHOCTL;
-#endif
-#ifdef ECHOKE
- termios->c_lflag |= ECHOKE;
-#endif
-
- termios->c_cc[VINTR] = 0x1f & 'C';
- termios->c_cc[VQUIT] = 0x1f & '\\';
- termios->c_cc[VERASE] = 0x7f;
- termios->c_cc[VKILL] = 0x1f & 'U';
- termios->c_cc[VEOF] = 0x1f & 'D';
- termios->c_cc[VEOL] = _POSIX_VDISABLE;
- termios->c_cc[VEOL2] = _POSIX_VDISABLE;
- termios->c_cc[VSTART] = 0x1f & 'Q';
- termios->c_cc[VSTOP] = 0x1f & 'S';
- termios->c_cc[VSUSP] = 0x1f & 'Z';
- termios->c_cc[VREPRINT] = 0x1f & 'R';
- termios->c_cc[VWERASE] = 0x1f & 'W';
- termios->c_cc[VLNEXT] = 0x1f & 'V';
- termios->c_cc[VMIN] = 1;
- termios->c_cc[VTIME] = 0;
-}
-
-static bool set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
- FUNC_ATTR_NONNULL_ALL
-{
- int fd_dup = dup(fd);
- if (fd_dup < 0) {
- ELOG("Failed to dup descriptor %d: %s", fd, strerror(errno));
- return false;
- }
- int uv_result = uv_pipe_open(pipe, fd_dup);
- if (uv_result) {
- ELOG("Failed to set pipe to descriptor %d: %s",
- fd_dup, uv_strerror(uv_result));
- close(fd_dup);
- return false;
- }
- return true;
-}
-
-static void chld_handler(uv_signal_t *handle, int signum)
-{
- int stat = 0;
- int pid;
-
- do {
- pid = waitpid(-1, &stat, WNOHANG);
- } while (pid < 0 && errno == EINTR);
-
- if (pid <= 0) {
- return;
- }
-
- Loop *loop = handle->loop->data;
-
- kl_iter(WatcherPtr, loop->children, current) {
- Process *proc = (*current)->data;
- if (proc->pid == pid) {
- if (WIFEXITED(stat)) {
- proc->status = WEXITSTATUS(stat);
- } else if (WIFSIGNALED(stat)) {
- proc->status = WTERMSIG(stat);
- }
- proc->internal_exit_cb(proc);
- break;
- }
- }
-}
diff --git a/src/nvim/event/pty_process.h b/src/nvim/event/pty_process.h
deleted file mode 100644
index 446d7fd3c8..0000000000
--- a/src/nvim/event/pty_process.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef NVIM_EVENT_PTY_PROCESS_H
-#define NVIM_EVENT_PTY_PROCESS_H
-
-#include <sys/ioctl.h>
-
-#include "nvim/event/process.h"
-
-typedef struct pty_process {
- Process process;
- char *term_name;
- uint16_t width, height;
- struct winsize winsize;
- int tty_fd;
-} PtyProcess;
-
-static inline PtyProcess pty_process_init(Loop *loop, void *data)
-{
- PtyProcess rv;
- rv.process = process_init(loop, kProcessTypePty, data);
- rv.term_name = NULL;
- rv.width = 80;
- rv.height = 24;
- rv.tty_fd = -1;
- return rv;
-}
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/pty_process.h.generated.h"
-#endif
-#endif // NVIM_EVENT_PTY_PROCESS_H
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index 9f3fbc25ff..a520143064 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -100,6 +100,10 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
{
Stream *stream = uvstream->data;
+ if (cnt > 0) {
+ stream->num_bytes += (size_t)cnt;
+ }
+
if (cnt <= 0) {
if (cnt != UV_ENOBUFS
// cnt == 0 means libuv asked for a buffer and decided it wasn't needed:
@@ -185,10 +189,6 @@ static void read_event(void **argv)
static void invoke_read_cb(Stream *stream, size_t count, bool eof)
{
- if (stream->closed) {
- return;
- }
-
// Don't let the stream be closed before the event is processed.
stream->pending_reqs++;
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index 93cc592683..cdaf40849b 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -103,7 +103,7 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
// Libuv converts ENOENT to EACCES for Windows compatibility, but if
// the parent directory does not exist, ENOENT would be more accurate.
*path_tail((char_u *)watcher->addr) = NUL;
- if (!os_file_exists((char_u *)watcher->addr)) {
+ if (!os_path_exists((char_u *)watcher->addr)) {
result = -ENOENT;
}
}
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 71582ab357..33404158cf 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -71,6 +71,7 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream,
stream->closed = false;
stream->buffer = NULL;
stream->events = NULL;
+ stream->num_bytes = 0;
}
void stream_close(Stream *stream, stream_close_cb on_stream_close)
diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h
index c6baac0db7..ad4e24775b 100644
--- a/src/nvim/event/stream.h
+++ b/src/nvim/event/stream.h
@@ -49,6 +49,7 @@ struct stream {
size_t curmem;
size_t maxmem;
size_t pending_reqs;
+ size_t num_bytes;
void *data, *internal_data;
bool closed;
Queue *events;
diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c
index 7bf333bcea..f68a66345f 100644
--- a/src/nvim/event/time.c
+++ b/src/nvim/event/time.c
@@ -17,6 +17,7 @@ void time_watcher_init(Loop *loop, TimeWatcher *watcher, void *data)
watcher->uv.data = watcher;
watcher->data = data;
watcher->events = loop->fast_events;
+ watcher->blockable = false;
}
void time_watcher_start(TimeWatcher *watcher, time_cb cb, uint64_t timeout,
@@ -50,6 +51,10 @@ static void time_watcher_cb(uv_timer_t *handle)
FUNC_ATTR_NONNULL_ALL
{
TimeWatcher *watcher = handle->data;
+ if (watcher->blockable && !queue_empty(watcher->events)) {
+ // the timer blocked and there already is an unprocessed event waiting
+ return;
+ }
CREATE_EVENT(watcher->events, time_event, 1, watcher);
}
diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h
index 7882b2b627..14df176ea3 100644
--- a/src/nvim/event/time.h
+++ b/src/nvim/event/time.h
@@ -13,6 +13,7 @@ struct time_watcher {
void *data;
time_cb cb, close_cb;
Queue *events;
+ bool blockable;
};
#ifdef INCLUDE_GENERATED_DECLARATIONS