fix: force heater/stirrer safe on SIGTERM, not just SIGINT
systemctl stop/restart send SIGTERM, whose default disposition kills the process immediately - skipping the shutdown finally: block that cancels tasks and forces both actors safe via their activate(False). Route SIGTERM through the same KeyboardInterrupt path SIGINT already uses, and add stirrer.activate(False) alongside the existing heater.close() as a belt-and-suspenders fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvgC7oy9MxaA4ZQxXqkNdS
This commit is contained in:
+16
-4
@@ -3,6 +3,7 @@ import asyncio
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
@@ -202,23 +203,34 @@ if __name__ == '__main__':
|
||||
threading.Thread(target=http_server.serve_forever, daemon=True).start()
|
||||
print("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port))
|
||||
|
||||
# systemd's `stop`/`restart` send SIGTERM, whose default disposition is to
|
||||
# kill the process immediately - skipping the finally: block below
|
||||
# entirely, leaving the heater/stirrer at whatever they were last set to.
|
||||
# Routing it through the same KeyboardInterrupt path Ctrl-C (SIGINT)
|
||||
# already uses gets it the same graceful, actor-safing shutdown.
|
||||
signal.signal(signal.SIGTERM, signal.default_int_handler)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nShutting down...")
|
||||
finally:
|
||||
# Cancel all tasks so their finally blocks (e.g. HeaterTask's
|
||||
# `with device.open()`) get a chance to run before we close the port.
|
||||
# Cancel all tasks so their finally blocks (e.g. HeaterTask's/
|
||||
# StirrerTask's `with device.open()`) get a chance to run before we
|
||||
# close the port - this is what actually forces the heater to 0 W
|
||||
# (AHeater.activate(False)) and the stirrer to a stopped state
|
||||
# (AStirrer.activate(False)) on any exit that reaches here.
|
||||
pending = asyncio.all_tasks(loop)
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
if pending:
|
||||
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
||||
server_log_task.write()
|
||||
# Belt-and-suspenders: explicitly close the heater connection even if
|
||||
# the task cleanup above failed to reach HeaterHendi.activate(False).
|
||||
# Belt-and-suspenders: explicitly deactivate both actors even if the
|
||||
# task cleanup above failed to reach their own activate(False).
|
||||
heater.close()
|
||||
stirrer.activate(False)
|
||||
http_server.shutdown()
|
||||
loop.close()
|
||||
print("Server stopped.")
|
||||
|
||||
Reference in New Issue
Block a user