Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit d6b85a2

Browse files
authored
Complement image: propagate SIGTERM to all workers (#13914)
This should mean that logs from worker processes are flushed before shutdown. When a test completes, Complement stops the docker container, which means that synapse will receive a SIGTERM. Currently, the `complement_fork_starter` exits immediately (without notifying the worker processes), which means that the workers never get a chance to flush their logs before the whole container is vaped. We can fix this by propagating the SIGTERM to the children.
1 parent 2fae1a3 commit d6b85a2

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

changelog.d/13914.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Complement image: propagate SIGTERM to all workers.

synapse/app/complement_fork_starter.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,18 @@
5151
import importlib
5252
import itertools
5353
import multiprocessing
54+
import os
55+
import signal
5456
import sys
55-
from typing import Any, Callable, List
57+
from types import FrameType
58+
from typing import Any, Callable, List, Optional
5659

5760
from twisted.internet.main import installReactor
5861

62+
# a list of the original signal handlers, before we installed our custom ones.
63+
# We restore these in our child processes.
64+
_original_signal_handlers: dict[int, Any] = {}
65+
5966

6067
class ProxiedReactor:
6168
"""
@@ -105,6 +112,11 @@ def _worker_entrypoint(
105112

106113
sys.argv = args
107114

115+
# reset the custom signal handlers that we installed, so that the children start
116+
# from a clean slate.
117+
for sig, handler in _original_signal_handlers.items():
118+
signal.signal(sig, handler)
119+
108120
from twisted.internet.epollreactor import EPollReactor
109121

110122
proxy_reactor._install_real_reactor(EPollReactor())
@@ -167,13 +179,29 @@ def main() -> None:
167179
update_proc.join()
168180
print("===== PREPARED DATABASE =====", file=sys.stderr)
169181

182+
processes: List[multiprocessing.Process] = []
183+
184+
# Install signal handlers to propagate signals to all our children, so that they
185+
# shut down cleanly. This also inhibits our own exit, but that's good: we want to
186+
# wait until the children have exited.
187+
def handle_signal(signum: int, frame: Optional[FrameType]) -> None:
188+
print(
189+
f"complement_fork_starter: Caught signal {signum}. Stopping children.",
190+
file=sys.stderr,
191+
)
192+
for p in processes:
193+
if p.pid:
194+
os.kill(p.pid, signum)
195+
196+
for sig in (signal.SIGINT, signal.SIGTERM):
197+
_original_signal_handlers[sig] = signal.signal(sig, handle_signal)
198+
170199
# At this point, we've imported all the main entrypoints for all the workers.
171200
# Now we basically just fork() out to create the workers we need.
172201
# Because we're using fork(), all the workers get a clone of this launcher's
173202
# memory space and don't need to repeat the work of loading the code!
174203
# Instead of using fork() directly, we use the multiprocessing library,
175204
# which uses fork() on Unix platforms.
176-
processes = []
177205
for (func, worker_args) in zip(worker_functions, args_by_worker):
178206
process = multiprocessing.Process(
179207
target=_worker_entrypoint, args=(func, proxy_reactor, worker_args)

0 commit comments

Comments
 (0)