This repository was archived by the owner on Apr 26, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Improve startup times in Complement test runs against workers, particularly in CPU-constrained environments. #13127
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
71e515b
Add a fork()-based launcher for Synapse for use in Complement test runs
reivilibre 7b4b517
Don't pick out the run method from the reactor at import time
reivilibre 47be453
Update supervisor config to use the forking launcher when we want to
reivilibre a8308cc
Add worker names to the log lines when using fork launcher
reivilibre 089f4b3
Control use of fork()-based launcher with an env var
reivilibre 757d66c
Newsfile
reivilibre 8669ff3
Test against workers in CI every time! :-)
reivilibre 37bf9d0
Add comments about run() not being bound early
reivilibre caab855
Remove leading underscore from complement_fork_starter
reivilibre f3dc1c4
Rename ___install to _install_real_reactor
reivilibre 8f7e96e
Move comment to where we actually use fork()
reivilibre c475b00
Use argparse to do the first split of arguments and to provide help text
reivilibre d677e52
Remove stray #
reivilibre 821b473
Update synapse/app/_base.py
reivilibre 3bd858f
Pull out things from `ns` at point of use
reivilibre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Improve startup times in Complement test runs against workers, particularly in CPU-constrained environments. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# Copyright 2022 The Matrix.org Foundation C.I.C. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# ## What this script does | ||
# | ||
# This script spawns multiple workers, whilst only going through the code loading | ||
# process once. The net effect is that start-up time for a swarm of workers is | ||
# reduced, particularly in CPU-constrained environments. | ||
# | ||
# Before the workers are spawned, the database is prepared in order to avoid the | ||
# workers racing. | ||
# | ||
# ## Stability | ||
# | ||
# This script is only intended for use within the Synapse images for the | ||
# Complement test suite. | ||
# There are currently no stability guarantees whatsoever; especially not about: | ||
# - whether it will continue to exist in future versions; | ||
# - the format of its command-line arguments; or | ||
# - any details about its behaviour or principles of operation. | ||
# | ||
# ## Usage | ||
# | ||
# The first argument should be the path to the database configuration, used to | ||
# set up the database. The rest of the arguments are used as follows: | ||
# Each worker is specified as an argument group (each argument group is | ||
# separated by '--'). | ||
# The first argument in each argument group is the Python module name of the application | ||
# to start. Further arguments are then passed to that module as-is. | ||
# | ||
# ## Example | ||
# | ||
# python -m synapse.app.complement_fork_starter path_to_db_config.yaml \ | ||
# synapse.app.homeserver [args..] -- \ | ||
# synapse.app.generic_worker [args..] -- \ | ||
# ... | ||
# synapse.app.generic_worker [args..] | ||
# | ||
import argparse | ||
import importlib | ||
import itertools | ||
import multiprocessing | ||
import sys | ||
from typing import Any, Callable, List | ||
|
||
from twisted.internet.main import installReactor | ||
|
||
|
||
class ProxiedReactor: | ||
""" | ||
Twisted tracks the 'installed' reactor as a global variable. | ||
(Actually, it does some module trickery, but the effect is similar.) | ||
|
||
The default EpollReactor is buggy if it's created before a process is | ||
forked, then used in the child. | ||
See https://twistedmatrix.com/trac/ticket/4759#comment:17. | ||
|
||
However, importing certain Twisted modules will automatically create and | ||
install a reactor if one hasn't already been installed. | ||
It's not normally possible to re-install a reactor. | ||
|
||
Given the goal of launching workers with fork() to only import the code once, | ||
this presents a conflict. | ||
Our work around is to 'install' this ProxiedReactor which prevents Twisted | ||
from creating and installing one, but which lets us replace the actual reactor | ||
in use later on. | ||
""" | ||
|
||
def __init__(self) -> None: | ||
self.___reactor_target: Any = None | ||
|
||
def _install_real_reactor(self, new_reactor: Any) -> None: | ||
""" | ||
Install a real reactor for this ProxiedReactor to forward lookups onto. | ||
|
||
This method is specific to our ProxiedReactor and should not clash with | ||
any names used on an actual Twisted reactor. | ||
""" | ||
self.___reactor_target = new_reactor | ||
|
||
def __getattr__(self, attr_name: str) -> Any: | ||
return getattr(self.___reactor_target, attr_name) | ||
|
||
|
||
def _worker_entrypoint( | ||
func: Callable[[], None], proxy_reactor: ProxiedReactor, args: List[str] | ||
) -> None: | ||
""" | ||
Entrypoint for a forked worker process. | ||
|
||
We just need to set up the command-line arguments, create our real reactor | ||
and then kick off the worker's main() function. | ||
""" | ||
|
||
sys.argv = args | ||
|
||
from twisted.internet.epollreactor import EPollReactor | ||
|
||
proxy_reactor._install_real_reactor(EPollReactor()) | ||
func() | ||
|
||
|
||
def main() -> None: | ||
""" | ||
Entrypoint for the forking launcher. | ||
""" | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("db_config", help="Path to database config file") | ||
parser.add_argument( | ||
"args", | ||
nargs="...", | ||
help="Argument groups separated by `--`. " | ||
"The first argument of each group is a Synapse app name. " | ||
"Subsequent arguments are passed through.", | ||
) | ||
ns = parser.parse_args() | ||
|
||
# Split up the subsequent arguments into each workers' arguments; | ||
# `--` is our delimiter of choice. | ||
args_by_worker: List[List[str]] = [ | ||
list(args) | ||
for cond, args in itertools.groupby(ns.args, lambda ele: ele != "--") | ||
if cond and args | ||
] | ||
|
||
# Prevent Twisted from installing a shared reactor that all the workers will | ||
# inherit when we fork(), by installing our own beforehand. | ||
proxy_reactor = ProxiedReactor() | ||
installReactor(proxy_reactor) | ||
|
||
# Import the entrypoints for all the workers. | ||
worker_functions = [] | ||
for worker_args in args_by_worker: | ||
worker_module = importlib.import_module(worker_args[0]) | ||
worker_functions.append(worker_module.main) | ||
|
||
# We need to prepare the database first as otherwise all the workers will | ||
# try to create a schema version table and some will crash out. | ||
from synapse._scripts import update_synapse_database | ||
|
||
update_proc = multiprocessing.Process( | ||
target=_worker_entrypoint, | ||
args=( | ||
update_synapse_database.main, | ||
proxy_reactor, | ||
[ | ||
"update_synapse_database", | ||
"--database-config", | ||
ns.db_config, | ||
"--run-background-updates", | ||
], | ||
), | ||
) | ||
print("===== PREPARING DATABASE =====", file=sys.stderr) | ||
update_proc.start() | ||
update_proc.join() | ||
print("===== PREPARED DATABASE =====", file=sys.stderr) | ||
|
||
# At this point, we've imported all the main entrypoints for all the workers. | ||
# Now we basically just fork() out to create the workers we need. | ||
# Because we're using fork(), all the workers get a clone of this launcher's | ||
# memory space and don't need to repeat the work of loading the code! | ||
# Instead of using fork() directly, we use the multiprocessing library, | ||
# which uses fork() on Unix platforms. | ||
processes = [] | ||
for (func, worker_args) in zip(worker_functions, args_by_worker): | ||
process = multiprocessing.Process( | ||
target=_worker_entrypoint, args=(func, proxy_reactor, worker_args) | ||
) | ||
process.start() | ||
processes.append(process) | ||
|
||
# Be a good parent and wait for our children to die before exiting. | ||
for process in processes: | ||
process.join() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.