mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 01:00:18 +02:00
Server: a stop signal no longer crashes it (SIGABRT) while the app holds stdin
The --exit-on-eof watcher read stdin with a buffered read, which holds stdin's lock. When SIGTERM stopped the server first, Python aborted at exit trying to take that lock back, and the app showed "The server stopped unexpectedly (SIGABRT)". It now uses os.read. The startup line is printed inside the try, so a signal that arrives while it's printed still runs the cleanup. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
fa6d4fd81b
commit
1a5f089e57
2 files changed
+19
-3
No files matched your search
@@ -279,6 +279,18 @@ class OneServer(unittest.TestCase):
|
|||||||
r = conn.getresponse()
|
r = conn.getresponse()
|
||||||
self.assertEqual(r.status, 403, r.read())
|
self.assertEqual(r.status, 403, r.read())
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "no SIGTERM on Windows")
|
||||||
|
def test_sigterm_while_the_app_holds_stdin_exits_cleanly(self):
|
||||||
|
"""The app keeps stdin open; a stop signal used to abort Python (SIGABRT) at exit."""
|
||||||
|
env = {**os.environ, "FRAME_CONTROL_DATA_DIR": tempfile.mkdtemp(prefix="frame-one-server-"),
|
||||||
|
"FRAME_ALIAS": "frame-control-test.invalid"}
|
||||||
|
proc = subprocess.Popen([sys.executable, str(ROOT / "ui" / "server.py"), "--port", "0", "--exit-on-eof"],
|
||||||
|
env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||||
|
self.addCleanup(lambda: (proc.stdin.close(), proc.stdout.close()))
|
||||||
|
self.assertIn("Frame Control on", proc.stdout.readline())
|
||||||
|
proc.terminate()
|
||||||
|
self.assertEqual(proc.wait(30), 0, proc.stdout.read())
|
||||||
|
|
||||||
|
|
||||||
class ArtworkSettings(unittest.TestCase):
|
class ArtworkSettings(unittest.TestCase):
|
||||||
"""The settings panel's endpoints, with and without the page's X-Frame-UI key."""
|
"""The settings panel's endpoints, with and without the page's X-Frame-UI key."""
|
||||||
|
|||||||
+7
-3
@@ -2656,12 +2656,16 @@ def main():
|
|||||||
signal.signal(signal.SIGTERM, lambda *_: (_ for _ in ()).throw(KeyboardInterrupt))
|
signal.signal(signal.SIGTERM, lambda *_: (_ for _ in ()).throw(KeyboardInterrupt))
|
||||||
if args.exit_on_eof:
|
if args.exit_on_eof:
|
||||||
def watch_stdin():
|
def watch_stdin():
|
||||||
sys.stdin.buffer.read()
|
# os.read, not sys.stdin.buffer.read: a buffered read holds stdin's lock,
|
||||||
|
# and if a signal stops the server first, Python aborts (SIGABRT) at exit
|
||||||
|
# when it can't take that lock back from this thread.
|
||||||
|
while os.read(0, 4096):
|
||||||
|
pass
|
||||||
threading.Thread(target=httpd.shutdown, daemon=True).start()
|
threading.Thread(target=httpd.shutdown, daemon=True).start()
|
||||||
threading.Thread(target=watch_stdin, daemon=True).start()
|
threading.Thread(target=watch_stdin, daemon=True).start()
|
||||||
# The real port, which --port 0 leaves to the system (the iPhone app reads it from here).
|
|
||||||
print(f"Frame Control on http://127.0.0.1:{httpd.server_address[1]} (alias: {FRAME}; Ctrl-C to stop)", flush=True)
|
|
||||||
try:
|
try:
|
||||||
|
# The real port, which --port 0 leaves to the system (the iPhone app reads it from here).
|
||||||
|
print(f"Frame Control on http://127.0.0.1:{httpd.server_address[1]} (alias: {FRAME}; Ctrl-C to stop)", flush=True)
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in new issue
Block a user