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.