Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ea6c587
Remove sentinel context from `start_reactor` (`daemonize_process`/`run`)
MadLittleMods Aug 26, 2025
bf21b4a
Simplify stop/start context pattern in `daemonize_process`
MadLittleMods Aug 27, 2025
a677d9b
Remove sentinel context from `atexit` daemonize logs
MadLittleMods Aug 26, 2025
fce59b2
DEBUG: Logging contexts getting lost
MadLittleMods Aug 27, 2025
af1944b
Revert "DEBUG: Logging contexts getting lost"
MadLittleMods Aug 27, 2025
675d94a
Fix `run_as_background_process` not following Synapse log context rules
MadLittleMods Aug 27, 2025
1bf7049
Merge branch 'develop' into madlittlemods/remove-sentinel-context
MadLittleMods Aug 28, 2025
7938e8c
DEBUG: lost `main` context after request
MadLittleMods Aug 28, 2025
0a865fd
Revert "Fix `run_as_background_process` not following Synapse log con…
MadLittleMods Sep 4, 2025
b97b2dd
Debug: Eliminate variables
MadLittleMods Sep 5, 2025
301a714
DEBUG: test
MadLittleMods Sep 6, 2025
c1996b6
WIP: Pair with @erikjohnston
MadLittleMods Sep 8, 2025
d8f68d0
Explain the `sentinel` context better
MadLittleMods Sep 8, 2025
71cd3c4
Clarify when to use the `sentinel` context
MadLittleMods Sep 8, 2025
0e990e6
Explain logging context in `run_as_background_process`
MadLittleMods Sep 8, 2025
6a98096
Add potential future simplication of `run_as_background_process`
MadLittleMods Sep 8, 2025
ea5a841
Add log context explanation to `run_in_background`
MadLittleMods Sep 8, 2025
4b2e88f
Merge branch 'madlittlemods/remove-sentinel-context' of github.com:el…
MadLittleMods Sep 8, 2025
1f4b391
Revert "Add log context explanation to `run_in_background`"
MadLittleMods Sep 9, 2025
bbe1ee7
Revert "Add potential future simplication of `run_as_background_proce…
MadLittleMods Sep 9, 2025
b5ec2da
Revert "Explain logging context in `run_as_background_process`"
MadLittleMods Sep 9, 2025
4aa0aa0
Revert "Clarify when to use the `sentinel` context"
MadLittleMods Sep 9, 2025
37a388c
Revert "Explain the `sentinel` context better"
MadLittleMods Sep 9, 2025
4b61570
Revert "DEBUG: test"
MadLittleMods Sep 9, 2025
ae8055a
Revert "Debug: Eliminate variables"
MadLittleMods Sep 9, 2025
5c05a0b
Revert "DEBUG: lost `main` context after request"
MadLittleMods Sep 9, 2025
b8c0857
Re-establish logcontext at `start`
MadLittleMods Sep 9, 2025
74ab47f
Add context for "Shutting down..." log
MadLittleMods Sep 9, 2025
e741daf
Simplify `atexit` logcontext
MadLittleMods Sep 9, 2025
fab546d
Add logcontext to database profiling loop
MadLittleMods Sep 9, 2025
e626f09
Add changelog
MadLittleMods Sep 9, 2025
dcf5fca
Merge branch 'develop' into madlittlemods/remove-sentinel-context
MadLittleMods Sep 10, 2025
6bbd0bf
Revise changelog
MadLittleMods Sep 10, 2025
9c74f69
Fix lints
MadLittleMods Sep 10, 2025
e6685cc
Remove db profiling `looping_call` change
MadLittleMods Sep 10, 2025
1f95a3b
Prefer `PreserveLoggingContext()` defaults
MadLittleMods Sep 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 9 additions & 18 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
from synapse.events.presence_router import load_legacy_presence_router
from synapse.handlers.auth import load_legacy_password_auth_providers
from synapse.http.site import SynapseSite
from synapse.logging.context import PreserveLoggingContext
from synapse.logging.opentracing import init_tracer
from synapse.metrics import install_gc_manager, register_threadpool
from synapse.metrics.background_process_metrics import run_as_background_process
Expand Down Expand Up @@ -185,23 +184,15 @@ def run() -> None:
install_gc_manager()
run_command()

# make sure that we run the reactor with the sentinel log context,
# otherwise other PreserveLoggingContext instances will get confused
# and complain when they see the logcontext arbitrarily swapping
# between the sentinel and `run` logcontexts.
Comment on lines -188 to -191
Copy link
Contributor Author

@MadLittleMods MadLittleMods Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment was added in 067b00d#diff-6e21d6a61b2f6b6f2d4ce961991ba7f27e83605f927eaa4b19d2c46a975a96c1R460-R463 (matrix-org/synapse#2027)

I can't tell exactly what's it's referring to but the one spot I was seeing Expected logging context sentinel but found main around run_as_background_process(...) usage has been fixed (#18870 (comment)).

Reproduction:

  1. poetry run synapse_homeserver --config-path homeserver.yaml
  2. Ctrl + C to stop the server
  3. Notice LoggingContext: Expected logging context main was lost in the logs

#
# We also need to drop the logcontext before forking if we're daemonizing,
# otherwise the cputime metrics get confused about the per-thread resource usage
# appearing to go backwards.
Comment on lines -193 to -195
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment was added in matrix-org/synapse#5609 and is accurately describing a real problem.

But we can be way more precise about what do to here. We only need to stop the current log context right before forking the process and start it again right after. This can be accomplished with the PreserveLoggingContext being moved strictly around the os.fork() call (search for os.fork() below).


Previously, this caused the sentinel context to be used for a whole bunch of logs (basically anything outside of a request). We now get the main context used 💪

with PreserveLoggingContext():
if daemonize:
assert pid_file is not None

if print_pidfile:
print(pid_file)

daemonize_process(pid_file, logger)
run()
if daemonize:
assert pid_file is not None

if print_pidfile:
print(pid_file)

daemonize_process(pid_file, logger)

run()


def quit_with_error(error_string: str) -> NoReturn:
Expand Down
14 changes: 5 additions & 9 deletions synapse/metrics/background_process_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from synapse.logging.context import (
ContextResourceUsage,
LoggingContext,
PreserveLoggingContext,
make_deferred_yieldable,
)
from synapse.logging.opentracing import SynapseTags, start_active_span
from synapse.metrics import SERVER_NAME_LABEL
Expand Down Expand Up @@ -223,10 +223,9 @@ def run_as_background_process(
This should be used to wrap processes which are fired off to run in the
background, instead of being associated with a particular request.

It returns a Deferred which completes when the function completes, but it doesn't
follow the synapse logcontext rules, which makes it appropriate for passing to
clock.looping_call and friends (or for firing-and-forgetting in the middle of a
normal synapse async function).
It returns a Deferred which completes when the function completes, which makes it
appropriate for passing to clock.looping_call and friends (or for
firing-and-forgetting in the middle of a normal synapse async function).

Args:
desc: a description for this background process type
Expand All @@ -241,8 +240,6 @@ def run_as_background_process(

Returns:
Deferred which returns the result of func, or `None` if func raises.
Note that the returned Deferred does not follow the synapse logcontext
rules.
"""

async def run() -> Optional[R]:
Expand Down Expand Up @@ -280,10 +277,9 @@ async def run() -> Optional[R]:
name=desc, **{SERVER_NAME_LABEL: server_name}
).dec()

with PreserveLoggingContext():
# Note that we return a Deferred here so that it can be used in a
# looping_call and other places that expect a Deferred.
return defer.ensureDeferred(run())
return make_deferred_yieldable(defer.ensureDeferred(run()))


P = ParamSpec("P")
Expand Down
44 changes: 39 additions & 5 deletions synapse/util/daemonize.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
from types import FrameType, TracebackType
from typing import NoReturn, Optional, Type

from synapse.logging.context import (
SENTINEL_CONTEXT,
LoggingContext,
PreserveLoggingContext,
)


def daemonize_process(pid_file: str, logger: logging.Logger, chdir: str = "/") -> None:
"""daemonize the current process
Expand Down Expand Up @@ -64,8 +70,14 @@ def daemonize_process(pid_file: str, logger: logging.Logger, chdir: str = "/") -
pid_fh.write(old_pid)
sys.exit(1)

# Fork, creating a new process for the child.
process_id = os.fork()
# Stop the existing context *before* we fork the process. Otherwise the cputime
# metrics get confused about the per-thread resource usage appearing to go backwards
# because we're comparing the resource usage from the original process to the forked
# process. `PreserveLoggingContext` already takes care of restarting the original
# context *after* the block.
with PreserveLoggingContext(SENTINEL_CONTEXT):
# Fork, creating a new process for the child.
process_id = os.fork()

if process_id != 0:
# parent process: exit.
Expand Down Expand Up @@ -138,11 +150,33 @@ def sigterm(signum: int, frame: Optional[FrameType]) -> NoReturn:

signal.signal(signal.SIGTERM, sigterm)

# Create a logging context that we can use later as these `atexit` handlers will run
# after the `with LoggingContext("main")` context manager finishes and we still want
# some context here to know which server is logging.
#
# We're using `PreserveLoggingContext(SENTINEL_CONTEXT)` so our new `LoggingContext`
# ends up with `LoggingContext.previous_context = SENTINEL_CONTEXT` so that when the
# `LoggingContext` exits and restores the previous context, we don't leak some
# context into the reactor that would be erroneously be picked up by something else
# down the line.
with PreserveLoggingContext(SENTINEL_CONTEXT):
exit_logging_context = LoggingContext(
"atexit",
# TODO: In the future, we will want
# `server_name=calling_context.server_name` so we know which server this log
# pertains to, https://github.com/element-hq/synapse/pull/18868
#
# No parent_context as we don't want to attribute the metrics/traces to the
# calling context. `atexit` is completely out-of-band from our application
# so it doesn't make sense to associate it back.
)

# Cleanup pid file at exit.
def exit() -> None:
logger.warning("Stopping daemon.")
os.remove(pid_file)
sys.exit(0)
with PreserveLoggingContext(exit_logging_context):
logger.warning("Stopping daemon.")
os.remove(pid_file)
sys.exit(0)

atexit.register(exit)

Expand Down
Loading