Hi Jeremy,

I glanced the patch and it looks good.

Do you perhaps also have a way to easily reproduce this deadlock condition?

On Thu, Jun 27, 2024 at 06:51:28PM -0700, Jeremy Bobbin wrote:
> because underlying programs can request info from the tty, there was a
> potential recursion in ttyread:
>       1. ttyread
>       2. tty program wants window size
>       3. ttywrite the window size
> 
> this assumes the ttyfd is both ready for read & write.
> 
> if, for example, the tty wants st to report the cursor position & the
> write pipe is already cloggled, then this could cause a deadlock.
> 
> this was initialy addressed with 2 selects:
> 
> one in x.c which scanned for { display events & tty for reading }.
> another in st.c which scanned for { read & write }-ability of the tty.
> 
> this patch, instead, buffers what we write to the TTY, and then
> conditionally selects over tty readability, tty writability & display
> events.
> 
> in addition to simplifying the ttywrite function, this will make fewer
> calls to select & should make fewer calls to write.
> ---
>  st.c | 92 ++++++++++++++++++++++++++++--------------------------------
>  st.h |  3 ++
>  x.c  |  9 +++++-
>  3 files changed, 54 insertions(+), 50 deletions(-)
> 
> diff --git a/st.c b/st.c
> index 57c6e96..030c294 100644
> --- a/st.c
> +++ b/st.c
> @@ -152,6 +152,12 @@ typedef struct {
>       int narg;              /* nb of args */
>  } STREscape;
>  
> +typedef struct {
> +     char *buf;             /* allocated raw string */
> +     size_t siz;            /* allocation size */
> +     size_t len;            /* raw string length */
> +} TBuffer;
> +
>  static void execsh(char *, char **);
>  static void stty(char **);
>  static void sigchld(int);
> @@ -223,6 +229,7 @@ static Term term;
>  static Selection sel;
>  static CSIEscape csiescseq;
>  static STREscape strescseq;
> +static TBuffer tbuf;
>  static int iofd = 1;
>  static int cmdfd;
>  static pid_t pid;
> @@ -752,6 +759,35 @@ stty(char **args)
>               perror("Couldn't call stty");
>  }
>  
> +void
> +tbufnew(size_t siz) {
> +     tbuf.buf = (char *)xmalloc(siz);
> +     tbuf.siz = siz;
> +     tbuf.len = 0;
> +}
> +
> +size_t
> +tbuflen(void) {
> +     return tbuf.len;
> +}
> +
> +size_t
> +tbufwrite(void) {
> +     size_t lim = 256;
> +     ssize_t r;
> +
> +     /*
> +      * Migrate the world to Plan 9.
> +      */
> +     if ((r = write(cmdfd, &tbuf.buf[0], MIN(tbuf.len, lim))) < 0)
> +             die("write error on tty: %s\n", strerror(errno));
> +
> +     tbuf.len -= r;
> +     memmove(tbuf.buf, &tbuf.buf[r], tbuf.len);
> +
> +     return r;
> +}
> +
>  int
>  ttynew(const char *line, char *cmd, const char *out, char **args)
>  {
> @@ -868,61 +904,19 @@ ttywrite(const char *s, size_t n, int may_echo)
>       }
>  }
>  
> +
>  void
>  ttywriteraw(const char *s, size_t n)
>  {
> -     fd_set wfd, rfd;
> -     ssize_t r;
> -     size_t lim = 256;
> -
> -     /*
> -      * Remember that we are using a pty, which might be a modem line.
> -      * Writing too much will clog the line. That's why we are doing this
> -      * dance.
> -      * FIXME: Migrate the world to Plan 9.
> -      */
> -     while (n > 0) {
> -             FD_ZERO(&wfd);
> -             FD_ZERO(&rfd);
> -             FD_SET(cmdfd, &wfd);
> -             FD_SET(cmdfd, &rfd);
> -
> -             /* Check if we can write. */
> -             if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
> -                     if (errno == EINTR)
> -                             continue;
> -                     die("select failed: %s\n", strerror(errno));
> -             }
> -             if (FD_ISSET(cmdfd, &wfd)) {
> -                     /*
> -                      * Only write the bytes written by ttywrite() or the
> -                      * default of 256. This seems to be a reasonable value
> -                      * for a serial line. Bigger values might clog the I/O.
> -                      */
> -                     if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
> -                             goto write_error;
> -                     if (r < n) {
> -                             /*
> -                              * We weren't able to write out everything.
> -                              * This means the buffer is getting full
> -                              * again. Empty it.
> -                              */
> -                             if (n < lim)
> -                                     lim = ttyread();
> -                             n -= r;
> -                             s += r;
> -                     } else {
> -                             /* All bytes have been written. */
> -                             break;
> -                     }
> -             }
> -             if (FD_ISSET(cmdfd, &rfd))
> -                     lim = ttyread();
> +     if (tbuf.siz - tbuf.len < n) {
> +             tbuf.siz *= 2;
> +             tbuf.buf = xrealloc(tbuf.buf, tbuf.siz);
>       }
> +     memcpy(&tbuf.buf[tbuf.len], s, n);
> +     tbuf.len += n;
> +
>       return;
>  
> -write_error:
> -     die("write error on tty: %s\n", strerror(errno));
>  }
>  
>  void
> diff --git a/st.h b/st.h
> index fd3b0d8..2b810b8 100644
> --- a/st.h
> +++ b/st.h
> @@ -87,6 +87,9 @@ void sendbreak(const Arg *);
>  void toggleprinter(const Arg *);
>  
>  int tattrset(int);
> +void tbufnew(size_t);
> +size_t tbuflen(void);
> +size_t tbufwrite(void);
>  void tnew(int, int);
>  void tresize(int, int);
>  void tsetdirtattr(int);
> diff --git a/x.c b/x.c
> index bd23686..f1ac1a9 100644
> --- a/x.c
> +++ b/x.c
> @@ -1922,7 +1922,7 @@ run(void)
>  {
>       XEvent ev;
>       int w = win.w, h = win.h;
> -     fd_set rfd;
> +     fd_set rfd, wfd;
>       int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
>       struct timespec seltv, *tv, now, lastblink, trigger;
>       double timeout;
> @@ -1948,8 +1948,11 @@ run(void)
>  
>       for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
>               FD_ZERO(&rfd);
> +             FD_ZERO(&wfd);
>               FD_SET(ttyfd, &rfd);
>               FD_SET(xfd, &rfd);
> +             if (tbuflen() > 0)
> +                     FD_SET(ttyfd, &wfd);
>  
>               if (XPending(xw.dpy))
>                       timeout = 0;  /* existing events might not set xfd */
> @@ -1968,6 +1971,9 @@ run(void)
>               if (FD_ISSET(ttyfd, &rfd))
>                       ttyread();
>  
> +             if (FD_ISSET(ttyfd, &wfd))
> +                     tbufwrite();
> +
>               xev = 0;
>               while (XPending(xw.dpy)) {
>                       xev = 1;
> @@ -2095,6 +2101,7 @@ run:
>       XSetLocaleModifiers("");
>       cols = MAX(cols, 1);
>       rows = MAX(rows, 1);
> +     tbufnew(256);
>       tnew(cols, rows);
>       xinit(cols, rows);
>       xsetenv();
> -- 
> 2.45.2
> 
> 

-- 
Kind regards,
Hiltjo

Reply via email to