Browse Source

proc: avoid stdio deadlocks

When a request handler accepting post data is too slow in consuming stdin,
uhttpd might deadlock with the master process stuck in a blocking write()
to the child and the child stuck with a blocking write() to the master.

Avoid this issue by putting the master side write end of the child pipe
into nonblocking mode right away and by raising the data_blocked flag
when attempts to write to the child yield EAGAIN.

Setting the flag ensures that client_poll_post_data() does not immediately
trigger a write attempt again, which effectively yields the master write
cycle so that the relay ustream has a chance to consume output of the
client process, thus solving the deadlock.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Jo-Philipp Wich 6 years ago
parent
commit
ccd9717ba5
1 changed files with 5 additions and 1 deletions
  1. 5 1
      proc.c

+ 5 - 1
proc.c

@@ -265,6 +265,7 @@ static void proc_write_cb(struct uloop_fd *fd, unsigned int events)
 	struct client *cl = container_of(fd, struct client, dispatch.proc.wrfd);
 
 	client_poll_post_data(cl);
+	cl->dispatch.data_blocked = false;
 }
 
 static void proc_relay_write_cb(struct client *cl)
@@ -291,8 +292,10 @@ static int proc_data_send(struct client *cl, const char *data, int len)
 			if (errno == EINTR)
 				continue;
 
-			if (errno == EAGAIN || errno == EWOULDBLOCK)
+			if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				cl->dispatch.data_blocked = true;
 				break;
+			}
 
 			/* consume all data */
 			ret = len;
@@ -366,6 +369,7 @@ bool uh_create_process(struct client *cl, struct path_info *pi, char *url,
 
 	proc->wrfd.fd = wfd[1];
 	uh_relay_open(cl, &proc->r, rfd[0], pid);
+	uloop_fd_add(&proc->wrfd, ULOOP_WRITE);
 
 	d->free = proc_free;
 	d->close_fds = proc_close_fds;