From 5cc918e5c2e4228b51ecbf634a68d4182bd31013 Mon Sep 17 00:00:00 2001 From: Walter Boring Date: Thu, 14 May 2026 16:55:57 -0400 Subject: [PATCH] fix: add socket lock to prevent TX/RX race causing stream corruption on retransmits The RX reader thread sets setblocking(0) and the TX writer (via aprslib sendall) sets setblocking(1) on the same socket without synchronization. This race condition causes partial writes where other stations' APRS-IS stream data gets concatenated onto retransmitted packets. Add a shared _socket_lock between send() and _socket_readlines() so the socket blocking mode is never changed by one thread while the other is mid-operation. --- aprsd/client/drivers/lib/aprslib.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/aprsd/client/drivers/lib/aprslib.py b/aprsd/client/drivers/lib/aprslib.py index d72994d..254d932 100644 --- a/aprsd/client/drivers/lib/aprslib.py +++ b/aprsd/client/drivers/lib/aprslib.py @@ -44,6 +44,13 @@ class APRSLibClient(aprslib.IS): select_timeout = 1 lock = threading.Lock() + # Shared lock between RX (reader) and TX (writer) to prevent + # socket state races. The reader sets setblocking(0) and the + # writer's sendall (in aprslib) sets setblocking(1). Without + # synchronization, these can race causing partial writes and + # stream corruption on retransmits. + _socket_lock = threading.Lock() + def stop(self): self.thread_stop = True LOG.warning('Shutdown Aprsdis client.') @@ -54,8 +61,15 @@ class APRSLibClient(aprslib.IS): @wrapt.synchronized(lock) def send(self, packet: core.Packet): - """Send an APRS Message object.""" - self.sendall(packet.raw) + """Send an APRS Message object. + + Uses _socket_lock to prevent racing with the reader thread's + setblocking(0) call. The upstream aprslib sendall() sets + setblocking(1) before writing, which can corrupt in-progress + recv() calls if unsynchronized. + """ + with self._socket_lock: + self.sendall(packet.raw) def is_alive(self): """If the connection is alive or not.""" @@ -141,7 +155,13 @@ class APRSLibClient(aprslib.IS): continue try: - short_buf = self.sock.recv(4096) + with self._socket_lock: + # Re-ensure non-blocking mode inside the lock. + # The send() path (via aprslib sendall) sets + # setblocking(1), so we must restore non-blocking + # before recv() to avoid blocking indefinitely. + self.sock.setblocking(0) + short_buf = self.sock.recv(4096) # sock.recv returns empty if the connection drops if not short_buf: