I am using the tty subsystem of Linux to emulate serial ports. The emulated serial ports are used by an application that expects a physical serial port. In my case, the application is running under Docker, but I don't think Docker is relevant to this question. However, I am having an issue in which the application that manages the emulated serial ports is locking up; I believe it is blocking on a call to write
. I'd like to figure out if there's a way to make the call not block, or to detect when it would block.
I have a Python application that manages the emulated serial ports and performs broadcasting between them (i.e. if one application writes to its serial port, then all other serial ports in the network receive the written message). The application looks something like this:
#!/usr/bin/env python3
import os
import pty
import selectors
import signal
import tty
# read the configuration
all_apps = ...
selector = selectors.DefaultSelector()
# create the emulated serial ports
master_fds = { }
for app in all_apps:
app_master, app_slave = pty.openpty()
tty.setraw(app_master)
master_fds[app] = app_master
selector.register(app_master, selectors.EVENT_READ, app_master)
# make the symlink that gets picked up by the application
os.symlink(os.ttyname(app_slave), "/run/my-app/{}/ttyS0".format(app))
# exit on SIGINT or SIGTERM
run_flag = True
def signal_handler(sig, stack):
global run_flag
run_flag = False
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# main loop
while run_flag:
events = selector.select(timeout = 1)
for key,mask in events:
fd = key.data
# read up to 1024 bytes of data from the app that sent the message
data = os.read(fd, 1024)
for app_fd in master_fds.values():
# don't broadcast to the application that sent the message
if fd == app_fd:
continue
# send the message to each application
os.write(app_fd, data)
# cleanup
for key in master_fds.keys():
os.remove("/run/my-app/{}/ttyS0".format(key))
os.close(master_fds[key])
Generally this script works well - when the applications start, they pick up the pty slave as a serial port, and messages are broadcasted between the applications. However, at some point, the script may hang - I believe it is on a call to os.write
, which is only ever called when sending an application to one of the pty masters. In this state, the script doesn't respond to signals (since run_flag is only checked when the loop runs, but os.write
is blocking the loop).
The only explanation that makes sense to me is that at least one of the applications isn't properly reading from its slave end of the pty. If this is true, then I can imagine that some buffer in the kernel which is backing the pty is becoming full, and since the call to write
would overflow the buffer, the call blocks until the buffer is drained enough (which never happens).
I found that I am sometimes able to drain the emulated serial port by running cat
on the slave end of the port. However, I need the application to be reliable enough to not block when the call to os.write
would normally block, without human intervention.
Is there a way to measure (for example, in my Python script) the remaining capacity of the buffer backing a pty, to avoid a call to write
from blocking on a pty master?