Download raw body.
responsive watch wip
The read_result function blocks until the child exits. This makes
watch unresponsive. Try it with a short shell script:
echo hello; sleep 5; echo goodbye
You can't even quit by pressing q because it's stuck reading lines.
This diff rearranges the whole fork and read pipeline to use libevent.
Now user commands and input can be interleaved with the command. You
can press q at any time, partial updates are displayed, etc.
I left all the old code intact for reference, because there's more work
to do. There's still a bunch of calls to read_result.
Needs mbstowc in the child_input function. (Should probably record
its progress instead of relooping every read, but in the common case
this will only happen a few times.)
We might lose track of which child exited? Not sure how much this
matters. This is where I would use kevent, because I don't know how
to get the exiting pid out of libevent.
I think the pause event handling is currently backwards? See below.
Index: watch.c
===================================================================
RCS file: /home/cvs/src/usr.bin/watch/watch.c,v
diff -u -p -r1.28 watch.c
--- watch.c 31 May 2025 08:26:26 -0000 1.28
+++ watch.c 10 Jun 2025 03:22:01 -0000
@@ -83,6 +83,15 @@ static char *cmdstr;
static size_t cmdlen;
static char **cmdv;
+struct child {
+ char buf[65000];
+ size_t bufsiz;
+ size_t pos;
+ pid_t pid;
+ int fd;
+ struct event evin;
+};
+
typedef wchar_t BUFFER[MAXLINE][MAXCOLUMN + 1];
BUFFER buf0, buf1;
BUFFER *cur_buf, *prev_buf;
@@ -98,12 +107,18 @@ kbd_result_t kbd_command(int);
void show_help(void);
void untabify(wchar_t *, int);
void on_signal(int, short, void *);
+void on_sigchild(int, short, void *);
void timer(int, short, void *);
void input(int, short, void *);
void quit(void);
void usage(void);
void swap_buffers(void);
+struct child *running_child;
+void start_child();
+void kill_child(struct child *);
+void child_input(int, short, void *);
+
int
set_interval(const char *str)
{
@@ -124,6 +139,7 @@ int
main(int argc, char *argv[])
{
struct event ev_sigint, ev_sighup, ev_sigterm, ev_sigwinch, ev_stdin;
+ struct event ev_sigchild;
size_t len, rem;
int i, ch;
char *p;
@@ -216,10 +232,12 @@ main(int argc, char *argv[])
signal_set(&ev_sighup, SIGHUP, on_signal, NULL);
signal_set(&ev_sigterm, SIGTERM, on_signal, NULL);
signal_set(&ev_sigwinch, SIGWINCH, on_signal, NULL);
+ signal_set(&ev_sigchild, SIGCHLD, on_sigchild, NULL);
signal_add(&ev_sigint, NULL);
signal_add(&ev_sighup, NULL);
signal_add(&ev_sigterm, NULL);
signal_add(&ev_sigwinch, NULL);
+ signal_add(&ev_sigchild, NULL);
event_set(&ev_stdin, STDIN_FILENO, EV_READ | EV_PERSIST, input, NULL);
event_add(&ev_stdin, NULL);
@@ -227,8 +245,7 @@ main(int argc, char *argv[])
evtimer_add(&ev_timer, &opt_interval.tv);
cur_buf = &buf0; prev_buf = &buf1;
- read_result(cur_buf);
- display(cur_buf, prev_buf, highlight_mode);
+ start_child();
event_dispatch();
@@ -379,6 +396,109 @@ display(BUFFER * cur, BUFFER * prev, hig
}
void
+start_child()
+{
+ struct child *child;
+ int fds[2];
+
+ child = calloc(1, sizeof(*child));
+ child->bufsiz = sizeof(child->buf);
+
+ if (pipe(fds) == -1)
+ err(1, "pipe()");
+
+ child->pid = vfork();
+ if (child->pid == -1)
+ err(1, "vfork");
+ if (child->pid == 0) {
+ close(fds[0]);
+ if (fds[1] != STDOUT_FILENO) {
+ dup2(fds[1], STDOUT_FILENO);
+ close(fds[1]);
+ }
+ if (xflag)
+ execvp(cmdv[0], cmdv);
+ else
+ execl(_PATH_BSHELL, _PATH_BSHELL, "-c", cmdstr, NULL);
+
+ /* use warn(3) + _exit(2) not to call exit(3) */
+ warn("exec(%s)", cmdstr);
+ _exit(1);
+ /* NOTREACHED */
+ }
+ close(fds[1]);
+ child->fd = fds[0];
+
+ event_set(&child->evin, child->fd, EV_READ | EV_PERSIST, child_input, child);
+ event_add(&child->evin, NULL);
+
+ running_child = child;
+}
+
+void
+kill_child(struct child *child)
+{
+
+ event_del(&child->evin);
+ close(child->fd);
+ kill(child->pid, SIGTERM);
+ free(child);
+ if (running_child == child)
+ running_child = NULL;
+}
+
+void
+on_sigchild(int sig, short event, void *arg)
+{
+ pid_t pid;
+ int st;
+
+ do {
+ pid = waitpid(WAIT_ANY, &st, 0);
+ } while (pid == -1 && errno == EINTR);
+ if (running_child && running_child->pid == pid)
+ kill_child(running_child);
+
+ /* Remember update time */
+ time(&lastupdate);
+
+ if (WIFEXITED(st))
+ last_exitcode = WEXITSTATUS(st);
+ if (pause_on_error && last_exitcode)
+ paused = 1;
+}
+
+void
+child_input(int sig, short event, void *arg)
+{
+ struct child *child = arg;
+ ssize_t n;
+
+ n = read(child->fd, child->buf + child->pos, child->bufsiz - child->pos);
+ if (n == -1)
+ return;
+ child->pos += n;
+
+ size_t l = 0, c = 0;
+ BUFFER *buf = cur_buf;
+ memset(*buf, 0, sizeof(*buf));
+ for (size_t i = 0; i < child->pos; i++) {
+ char ch = child->buf[i];
+ if (ch == '\n') {
+ l++;
+ c = 0;
+ if (l == MAXLINE)
+ break;
+ continue;
+ }
+ if (c == MAXCOLUMN)
+ continue;
+ (*buf)[l][c++] = ch;
+ }
+ display(buf, prev_buf, highlight_mode);
+}
+
+void
read_result(BUFFER *buf)
{
FILE *fp;
@@ -534,11 +654,11 @@ kbd_command(int ch)
case 'p':
if (paused == 1) {
paused = 0;
- evtimer_del(&ev_timer);
+ evtimer_add(&ev_timer, &opt_interval.tv);
return (RSLT_REDRAW);
} else {
paused = 1;
- evtimer_add(&ev_timer, &opt_interval.tv);
+ evtimer_del(&ev_timer);
return (RSLT_UPDATE);
}
@@ -687,11 +807,11 @@ on_signal(int sig, short event, void *ar
void
timer(int sig, short event, void *arg)
{
+ if (running_child)
+ kill_child(running_child);
swap_buffers();
- read_result(cur_buf);
- display(cur_buf, prev_buf, highlight_mode);
- if (!paused)
- evtimer_add(&ev_timer, &opt_interval.tv);
+ start_child();
+ evtimer_add(&ev_timer, &opt_interval.tv);
}
void
responsive watch wip