This is an automated email from the git hooks/post-receive script.

git pushed a commit to reference refs/pull/36/head
in repository terminology.

View the commit online.

commit 8e861c3cc89f7afd58ce08a6311f5dc67c37f4d0
Author: [email protected] <[email protected]>
AuthorDate: Wed Mar 18 20:46:41 2026 -0600

    fix: make session socket non-blocking to prevent hang on window close
    
    When closing a Terminology window in session mode, the application hung
    indefinitely. The root cause was a blocking read() in the Ecore fd
    handler callback that froze the entire main loop, preventing X11/Wayland
    window close events from being processed.
    
    The fix converts the session socket to non-blocking mode after the
    synchronous handshake completes, then updates termsrv_msg_recv to handle
    non-blocking reads correctly:
    
    - MSG_DONTWAIT on the header read returns 0 (EAGAIN) when no data is
      available yet, signaling the Ecore callback to reschedule
    - _recv_exact helper tolerates short reads from non-blocking sockets
      with a tight retry loop (bounded at 100 iterations for safety)
    - Updated documentation for the new return semantics
    
    The socket remains blocking only during the initial ATTACH/ATTACHED
    handshake exchange, which is inherently synchronous and must complete
    before the fd handler is registered.
    
    Fixes the session mode hang regression from tymux integration.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 src/bin/termsession.c | 15 ++++++++++
 src/bin/termsrv.c     | 76 +++++++++++++++++++++++++++++++++++++++++++++------
 src/bin/termsrv.h     |  5 +++-
 3 files changed, 87 insertions(+), 9 deletions(-)

diff --git a/src/bin/termsession.c b/src/bin/termsession.c
index cff9c6bf..c092465b 100644
--- a/src/bin/termsession.c
+++ b/src/bin/termsession.c
@@ -195,6 +195,11 @@ _cb_session_data(void *data, Ecore_Fd_Handler *handler EINA_UNUSED)
    int          type;
 
    type = termsrv_msg_recv(ts->sock_fd, &payload, &size);
+   if (type == 0)
+     {
+        /* EAGAIN — no complete message available yet, wait for next wakeup */
+        return ECORE_CALLBACK_RENEW;
+     }
    if (type < 0)
      {
         ERR("termsession: recv error, assuming daemon exit");
@@ -450,6 +455,16 @@ termsession_attach(const char *session_name,
    _sync_shadow(ts);
    ts->last_seq = atomic_load_explicit(&shm->write_seq, memory_order_acquire);
 
+   /* Switch to non-blocking mode before handing fd to Ecore.  The
+    * synchronous handshake (connect/ATTACH/ATTACHED) is already done;
+    * from here on _cb_session_data must never block the main loop. */
+   {
+      int flags = fcntl(sock_fd, F_GETFL, 0);
+      if (flags < 0 || fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) < 0)
+        WRN("termsession: could not set O_NONBLOCK on socket: %s",
+            strerror(errno));
+   }
+
    /* Register Ecore fd handler for incoming messages */
    ts->handler = ecore_main_fd_handler_add(sock_fd,
                                            ECORE_FD_READ | ECORE_FD_ERROR,
diff --git a/src/bin/termsrv.c b/src/bin/termsrv.c
index deda63f8..69adb7d9 100644
--- a/src/bin/termsrv.c
+++ b/src/bin/termsrv.c
@@ -63,11 +63,6 @@ termsrv_ensure_sessions_dir(void)
     return 0;
 }
 
-/* NOTE: write()/read() calls below assume the socket is used in blocking
- * mode and that messages are small enough to complete atomically.  For
- * payloads below PIPE_BUF (4096 B on Linux) this holds on AF_UNIX.
- * Larger payloads would require a write/read loop for full robustness. */
-
 int
 termsrv_msg_send(int fd, TermSrvMsgType type,
                  const void *payload, uint32_t size)
@@ -83,6 +78,59 @@ termsrv_msg_send(int fd, TermSrvMsgType type,
     return (n < 0) ? -1 : 0;
 }
 
+/*
+ * Read exactly @need bytes from @fd into @buf, tolerating short reads
+ * that can occur on non-blocking sockets.  Spins with EAGAIN up to a
+ * tight retry limit — on AF_UNIX the peer always sends header+payload
+ * atomically via sendmsg/iovec, so the bytes arrive together; any
+ * EAGAIN after a partial read is an extremely transient condition.
+ *
+ * Returns 0 on success, -1 on EOF or unrecoverable error.
+ */
+static int
+_recv_exact(int fd, void *buf, size_t need)
+{
+    size_t got = 0;
+    int    retries = 0;
+
+    while (got < need)
+      {
+         ssize_t r = recv(fd, (char *)buf + got, need - got, 0);
+         if (r > 0)
+           {
+              got += (size_t)r;
+              retries = 0;
+           }
+         else if (r == 0)
+           {
+              return -1; /* EOF */
+           }
+         else
+           {
+              if (errno == EINTR) continue;
+              if ((errno == EAGAIN || errno == EWOULDBLOCK) && retries < 100)
+                {
+                   /* Payload bytes haven't arrived yet — spin briefly */
+                   retries++;
+                   continue;
+                }
+              return -1;
+           }
+      }
+    return 0;
+}
+
+/*
+ * Receive one tymux message from @fd.
+ *
+ * On success returns the message type (> 0); *payload_out is malloc'd
+ * and *size_out is the payload length (caller must free *payload_out).
+ *
+ * Returns  0 if the fd is non-blocking and no data is available yet
+ *            (EAGAIN/EWOULDBLOCK on the header read) — caller should
+ *            return ECORE_CALLBACK_RENEW and wait for the next wakeup.
+ * Returns -1 on EOF or protocol error.
+ */
 int
 termsrv_msg_recv(int fd, void **payload_out, uint32_t *size_out)
 {
@@ -90,9 +138,21 @@ termsrv_msg_recv(int fd, void **payload_out, uint32_t *size_out)
     *payload_out = NULL;
     *size_out    = 0;
 
-    ssize_t r = read(fd, &hdr, sizeof(hdr));
+    ssize_t r = recv(fd, &hdr, sizeof(hdr), MSG_DONTWAIT);
     if (r == 0) return -1;  /* EOF */
-    if (r != (ssize_t)sizeof(hdr)) return -1;
+    if (r < 0)
+      {
+         if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; /* no data */
+         if (errno == EINTR) return 0;
+         return -1;
+      }
+    if (r != (ssize_t)sizeof(hdr))
+      {
+         /* Partial header: the rest must be in the kernel buffer already
+          * (same sendmsg atomicity guarantee).  Retrieve the remainder. */
+         if (_recv_exact(fd, (char *)&hdr + r, sizeof(hdr) - (size_t)r) < 0)
+           return -1;
+      }
 
     if (hdr.size > TERMSRV_MAX_MSG_PAYLOAD) return -1;
 
@@ -100,7 +160,7 @@ termsrv_msg_recv(int fd, void **payload_out, uint32_t *size_out)
       {
          void *buf = malloc(hdr.size);
          if (!buf) return -1;
-         if (read(fd, buf, hdr.size) != (ssize_t)hdr.size)
+         if (_recv_exact(fd, buf, hdr.size) < 0)
            { free(buf); return -1; }
          *payload_out = buf;
       }
diff --git a/src/bin/termsrv.h b/src/bin/termsrv.h
index 0ba4ee9b..7ba18a55 100644
--- a/src/bin/termsrv.h
+++ b/src/bin/termsrv.h
@@ -121,7 +121,10 @@ int termsrv_msg_send(int fd, TermSrvMsgType type,
                      const void *payload, uint32_t size);
 
 /* Receive a message. *payload_out is malloc'd; caller must free().
- * Returns msg type (>0) on success, -1 on EOF/error.
+ * Returns msg type (>0) on success.
+ * Returns 0 if the fd is non-blocking and no data is available yet
+ *   (EAGAIN/EWOULDBLOCK) — caller should retry on the next fd-ready event.
+ * Returns -1 on EOF or protocol error.
  * *payload_out is NULL and *size_out is 0 when there is no payload. */
 int termsrv_msg_recv(int fd, void **payload_out, uint32_t *size_out);
 

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to