Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
1 change: 1 addition & 0 deletions changelog.d/18886.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Start background tasks after we fork the process (daemonize).
7 changes: 7 additions & 0 deletions synapse/_scripts/update_synapse_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ def main() -> None:
# DB.
hs.setup()

# This will cause all of the relevant storage classes to be instantiated and call
# `register_background_update_handler(...)`,
# `register_background_index_update(...)`,
# `register_background_validate_constraint(...)`, etc so they are available to use
# if we are asked to run those background updates.
hs.get_storage_controllers()
Comment on lines +123 to +128
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously this was handled in an extremely tenuous fashion because previously hs.setup() used to call hs.start_background_tasks() which instantiated some handlers which ended up instantiating the storage controllers at some point.

Now we just explicitly call this


if args.run_background_updates:
run_background_updates(hs)

Expand Down
22 changes: 20 additions & 2 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,10 +609,28 @@ def run_sighup(*args: Any, **kwargs: Any) -> None:
setup_sentry(hs)
setup_sdnotify(hs)

# If background tasks are running on the main process or this is the worker in
# charge of them, start collecting the phone home stats and shared usage metrics.
# Register background tasks required by this server. This must be done
# somewhat manually due to the background tasks not being registered
# unless handlers are instantiated.
#
# While we could "start" these before the reactor runs, nothing will happen until
# the reactor is running, so we may as well do it here in `start`.
#
# Additionally, this means we also start them after we daemonize and fork the
# process which means we can avoid any potential problems with cputime metrics
# getting confused about the per-thread resource usage appearing to go backwards
# because we're comparing the resource usage (`rusage`) from the original process to
# the forked process.
if hs.config.worker.run_background_tasks:
hs.start_background_tasks()

# TODO: This should be moved to same pattern we use for other background tasks:
# Add to `REQUIRED_ON_BACKGROUND_TASK_STARTUP` and rely on
# `start_background_tasks` to start it.
await hs.get_common_usage_metrics_manager().setup()

# TODO: This feels like another pattern that should refactored as one of the
# `REQUIRED_ON_BACKGROUND_TASK_STARTUP`
start_phone_stats_home(hs)
Comment on lines +627 to 634
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are future refactors to do.

But given we're already starting a few snowflake background tasks in start(), this is just is more evidence why it makes sense to start background tasks here vs in setup()


# We now freeze all allocated objects in the hopes that (almost)
Expand Down
8 changes: 1 addition & 7 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,6 @@ def setup(self) -> None:
self.datastores = Databases(self.DATASTORE_CLASS, self)
logger.info("Finished setting up.")

# Register background tasks required by this server. This must be done
# somewhat manually due to the background tasks not being registered
# unless handlers are instantiated.
if self.config.worker.run_background_tasks:
self.setup_background_tasks()
Comment on lines -369 to -373
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In terms of where we moved this code from and to:

Relevant starting point is here:

def main() -> None:
with LoggingContext("main"):
# check base requirements
check_requirements()
hs = setup(sys.argv[1:])
# redirect stdio to the logs, if configured.
if not hs.config.logging.no_redirect_stdio:
redirect_stdio_to_logs()
run(hs)

The main thing to see here is setup() vs run(). We fork the process in run() and then start the reactor after.

Previously, we started the background tasks in the setup() phase.

Now we start the background tasks in start which happens "once the reactor is running".


def __del__(self) -> None:
"""
Called when an the homeserver is garbage collected.
Expand Down Expand Up @@ -410,7 +404,7 @@ def start_listening(self) -> None: # noqa: B027 (no-op by design)
appropriate listeners.
"""

def setup_background_tasks(self) -> None:
def start_background_tasks(self) -> None:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a rename to align with it's new home in start(). I think either could fit but this might be more straightforward to understand.

"""
Some handlers have side effects on instantiation (like registering
background updates). This function causes them to be fetched, and
Expand Down
10 changes: 10 additions & 0 deletions tests/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,16 @@ def setup_test_homeserver(
with patch("synapse.storage.database.make_pool", side_effect=make_fake_db_pool):
hs.setup()

# Register background tasks required by this server. This must be done
# somewhat manually due to the background tasks not being registered
# unless handlers are instantiated.
#
# Since, we don't have to worry about `daemonize` (forking the process) in tests, we
# can just start the background tasks straight away after `hs.setup`. (compare this
# with where we call `hs.start_background_tasks()` outside of the test environment).
if hs.config.worker.run_background_tasks:
hs.start_background_tasks()

# Since we've changed the databases to run DB transactions on the same
# thread, we need to stop the event fetcher hogging that one thread.
hs.get_datastores().main.USE_DEDICATED_DB_THREADS_FOR_EVENT_FETCHING = False
Expand Down
Loading