pyrolab/manager.py:584-598:
def shutdown_nameserver(self, nameserver: str) -> bool:
group = self.nameservers.pop(nameserver) # forgotten before it is dead
polling = group.process.msg_polling
group.msg_queue.put(None)
time.sleep(2 * polling) # fixed 2s, then assume success
return True # unconditional
(shutdown_daemon is identical.) There is no join(), no is_alive() check, no terminate() escalation, and no way for the return value to be False.
Impact. The kill signal is delivered over a queue that the child only reads in a polling timer thread. A daemon wedged inside a blocking C call -- a Kinesis DLL call, a VISA read, the unbounded while True in the Arduino driver -- never processes the message. The process survives, still holding its TCP port and its exclusive handle on the hardware, but the manager has already popped it from self.daemons and will never look at it again. It is orphaned until the machine is rebooted.
This is the one place that most needs a hard kill and it is the one place that has none. It also makes pyrolab down and pyrolab reload unreliable in exactly the situation where you reach for them.
Suggested fix: escalate and confirm, and only then forget the entry:
group.msg_queue.put(None)
group.process.join(timeout)
if group.process.is_alive():
log.warning("...did not exit, terminating")
group.process.terminate()
group.process.join(5)
if group.process.is_alive():
group.process.kill()
group.process.join()
alive = group.process.is_alive()
self.daemons.pop(daemon, None)
return not alive
The fixed time.sleep(2 * polling) also blocks the pyrolabd Pyro request thread for 2 seconds per entity, making pyrolab down slow in proportion to how much is running.
Found in a full-codebase audit at v0.4.0 (commit 1ce3146).
pyrolab/manager.py:584-598:(
shutdown_daemonis identical.) There is nojoin(), nois_alive()check, noterminate()escalation, and no way for the return value to beFalse.Impact. The kill signal is delivered over a queue that the child only reads in a polling timer thread. A daemon wedged inside a blocking C call -- a Kinesis DLL call, a VISA read, the unbounded
while Truein the Arduino driver -- never processes the message. The process survives, still holding its TCP port and its exclusive handle on the hardware, but the manager has already popped it fromself.daemonsand will never look at it again. It is orphaned until the machine is rebooted.This is the one place that most needs a hard kill and it is the one place that has none. It also makes
pyrolab downandpyrolab reloadunreliable in exactly the situation where you reach for them.Suggested fix: escalate and confirm, and only then forget the entry:
The fixed
time.sleep(2 * polling)also blocks thepyrolabdPyro request thread for 2 seconds per entity, makingpyrolab downslow in proportion to how much is running.Found in a full-codebase audit at v0.4.0 (commit 1ce3146).