Compare commits

...

6 Commits

Author SHA1 Message Date
Eric Eastwood
603746877f Drop unrelated docstring 2025-10-31 16:54:11 -05:00
Eric Eastwood
206e287f34 Add changelog 2025-10-31 16:18:00 -05:00
Eric Eastwood
07eb4c5e6c Fill in docstring 2025-10-31 16:11:43 -05:00
Eric Eastwood
ecff16ddf6 Note that we shouldn't be calling exit(1) in our composable functions 2025-10-31 16:09:09 -05:00
Eric Eastwood
f753a2a60b Fix lints 2025-10-31 16:01:26 -05:00
Eric Eastwood
b117866941 Refactor app start
Move `register_start` out of `setup`

Align all entrypoints (main, worker, `admin_cmd`)
2025-10-31 12:20:03 -05:00
15 changed files with 224 additions and 129 deletions

1
changelog.d/19121.misc Normal file
View File

@@ -0,0 +1 @@
Refactor and align app entrypoints (avoid `exit(1)` in our composable functions).

View File

@@ -602,7 +602,7 @@ def setup_sighup_handling() -> None:
_already_setup_sighup_handling = True
async def start(hs: "HomeServer", freeze: bool = True) -> None:
async def start(hs: "HomeServer", *, freeze: bool = True) -> None:
"""
Start a Synapse server or worker.

View File

@@ -64,7 +64,7 @@ from synapse.storage.databases.main.state import StateGroupWorkerStore
from synapse.storage.databases.main.stream import StreamWorkerStore
from synapse.storage.databases.main.tags import TagsWorkerStore
from synapse.storage.databases.main.user_erasure_store import UserErasureWorkerStore
from synapse.types import JsonMapping, StateMap
from synapse.types import ISynapseReactor, JsonMapping, StateMap
from synapse.util.logcontext import LoggingContext
logger = logging.getLogger("synapse.app.admin_cmd")
@@ -289,7 +289,21 @@ def load_config(argv_options: list[str]) -> tuple[HomeServerConfig, argparse.Nam
return config, args
def start(config: HomeServerConfig, args: argparse.Namespace) -> None:
def create_homeserver(
config: HomeServerConfig,
reactor: Optional[ISynapseReactor] = None,
) -> AdminCmdServer:
"""
Create a homeserver instance for the Synapse admin command process.
Args:
config: The configuration for the homeserver.
reactor: Optionally provide a reactor to use. Can be useful in different
scenarios that you want control over the reactor, such as tests.
Returns:
A homeserver instance.
"""
if config.worker.worker_app is not None:
assert config.worker.worker_app == "synapse.app.admin_cmd"
@@ -315,30 +329,54 @@ def start(config: HomeServerConfig, args: argparse.Namespace) -> None:
ss = AdminCmdServer(
config.server.server_name,
config=config,
reactor=reactor,
)
setup_logging(ss, config, use_worker_options=True)
return ss
def setup(ss: AdminCmdServer) -> None:
"""
Setup a `AdminCmdServer` instance.
Args:
ss: The homeserver to setup.
"""
setup_logging(ss, ss.config, use_worker_options=True)
ss.setup()
# We use task.react as the basic run command as it correctly handles tearing
# down the reactor when the deferreds resolve and setting the return value.
# We also make sure that `_base.start` gets run before we actually run the
# command.
async def run() -> None:
with LoggingContext(name="command", server_name=config.server.server_name):
await _base.start(ss)
await args.func(ss, args)
async def start(ss: AdminCmdServer, args: argparse.Namespace) -> None:
"""
Should be called once the reactor is running.
_base.start_worker_reactor(
"synapse-admin-cmd",
config,
run_command=lambda: task.react(lambda _reactor: defer.ensureDeferred(run())),
)
Args:
ss: The homeserver to setup.
args: Command line arguments.
"""
await _base.start(ss)
await args.func(ss, args)
def main() -> None:
homeserver_config, args = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
ss = create_homeserver(homeserver_config)
setup(ss)
_base.start_worker_reactor(
"synapse-admin-cmd",
ss.config,
# We use task.react as the basic run command as it correctly handles tearing
# down the reactor when the deferreds resolve and setting the return value.
# We also make sure that `_base.start` gets run before we actually run the
# command.
run_command=lambda: task.react(
lambda _reactor: defer.ensureDeferred(start(ss, args))
),
)
if __name__ == "__main__":
homeserver_config, args = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config, args)
main()

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -18,16 +18,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -21,6 +21,7 @@
#
import logging
import sys
from typing import Optional
from twisted.web.resource import Resource
@@ -111,6 +112,7 @@ from synapse.storage.databases.main.transactions import TransactionWorkerStore
from synapse.storage.databases.main.ui_auth import UIAuthWorkerStore
from synapse.storage.databases.main.user_directory import UserDirectoryStore
from synapse.storage.databases.main.user_erasure_store import UserErasureWorkerStore
from synapse.types import ISynapseReactor
from synapse.util.httpresourcetree import create_resource_tree
logger = logging.getLogger("synapse.app.generic_worker")
@@ -332,7 +334,30 @@ def load_config(argv_options: list[str]) -> HomeServerConfig:
return config
def start(config: HomeServerConfig) -> None:
def create_homeserver(
config: HomeServerConfig,
reactor: Optional[ISynapseReactor] = None,
) -> GenericWorkerServer:
"""
Create a homeserver instance for the Synapse worker process.
Our composable functions (`create_homeserver`, `setup`, `start`) should not exit the
Python process (call `exit(...)`) and instead raise exceptions which can be handled
by the caller as desired. This doesn't matter for the normal case of one Synapse
instance running in the Python process (as we're only affecting ourselves), but is
important when we have multiple Synapse homeserver tenants running in the same
Python process (c.f. Synapse Pro for small hosts) as we don't want some problem from
one tenant stopping the rest of the tenants.
Args:
config: The configuration for the homeserver.
reactor: Optionally provide a reactor to use. Can be useful in different
scenarios that you want control over the reactor, such as tests.
Returns:
A homeserver instance.
"""
# For backwards compatibility let any of the old app names.
assert config.worker.worker_app in (
"synapse.app.appservice",
@@ -357,9 +382,29 @@ def start(config: HomeServerConfig) -> None:
hs = GenericWorkerServer(
config.server.server_name,
config=config,
reactor=reactor,
)
setup_logging(hs, config, use_worker_options=True)
return hs
def setup(hs: GenericWorkerServer) -> None:
"""
Setup a `GenericWorkerServer` (worker) instance.
Our composable functions (`create_homeserver`, `setup`, `start`) should not exit the
Python process (call `exit(...)`) and instead raise exceptions which can be handled
by the caller as desired. This doesn't matter for the normal case of one Synapse
instance running in the Python process (as we're only affecting ourselves), but is
important when we have multiple Synapse homeserver tenants running in the same
Python process (c.f. Synapse Pro for small hosts) as we don't want some problem from
one tenant stopping the rest of the tenants.
Args:
hs: The homeserver to setup.
"""
setup_logging(hs, hs.config, use_worker_options=True)
# Start the tracer
init_tracer(hs) # noqa
@@ -373,22 +418,52 @@ def start(config: HomeServerConfig) -> None:
except Exception as e:
handle_startup_exception(e)
async def start() -> None:
await _base.start(hs)
register_start(hs, start)
async def start(
hs: GenericWorkerServer,
*,
freeze: bool = True,
) -> None:
"""
Should be called once the reactor is running.
# redirect stdio to the logs, if configured.
if not hs.config.logging.no_redirect_stdio:
redirect_stdio_to_logs()
Our composable functions (`create_homeserver`, `setup`, `start`) should not exit the
Python process (call `exit(...)`) and instead raise exceptions which can be handled
by the caller as desired. This doesn't matter for the normal case of one Synapse
instance running in the Python process (as we're only affecting ourselves), but is
important when we have multiple Synapse homeserver tenants running in the same
Python process (c.f. Synapse Pro for small hosts) as we don't want some problem from
one tenant stopping the rest of the tenants.
_base.start_worker_reactor("synapse-generic-worker", config)
Args:
hs: The homeserver to setup.
freeze: whether to freeze the homeserver base objects in the garbage collector.
May improve garbage collection performance by marking objects with an effectively
static lifetime as frozen so they don't need to be considered for cleanup.
If you ever want to `shutdown` the homeserver, this needs to be
False otherwise the homeserver cannot be garbage collected after `shutdown`.
"""
await _base.start(hs, freeze=freeze)
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
# Create a logging context as soon as possible so we can start associating
# everything with this homeserver.
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
# redirect stdio to the logs, if configured.
if not homeserver_config.logging.no_redirect_stdio:
redirect_stdio_to_logs()
hs = create_homeserver(homeserver_config)
setup(hs)
# Register a callback to be invoked once the reactor is running
register_start(hs, lambda: start(hs))
_base.start_worker_reactor("synapse-generic-worker", homeserver_config)
if __name__ == "__main__":

View File

@@ -71,7 +71,6 @@ from synapse.rest.well_known import well_known_resource
from synapse.server import HomeServer
from synapse.storage import DataStore
from synapse.types import ISynapseReactor
from synapse.util.check_dependencies import check_requirements
from synapse.util.httpresourcetree import create_resource_tree
from synapse.util.module_loader import load_module
@@ -356,6 +355,14 @@ def create_homeserver(
"""
Create a homeserver instance for the Synapse main process.
Our composable functions (`create_homeserver`, `setup`, `start`) should not exit the
Python process (call `exit(...)`) and instead raise exceptions which can be handled
by the caller as desired. This doesn't matter for the normal case of one Synapse
instance running in the Python process (as we're only affecting ourselves), but is
important when we have multiple Synapse homeserver tenants running in the same
Python process (c.f. Synapse Pro for small hosts) as we don't want some problem from
one tenant stopping the rest of the tenants.
Args:
config: The configuration for the homeserver.
reactor: Optionally provide a reactor to use. Can be useful in different
@@ -388,22 +395,20 @@ def create_homeserver(
def setup(
hs: SynapseHomeServer,
*,
freeze: bool = True,
) -> None:
"""
Setup a Synapse homeserver instance given a configuration.
Setup a `SynapseHomeServer` (main) instance.
Our composable functions (`create_homeserver`, `setup`, `start`) should not exit the
Python process (call `exit(...)`) and instead raise exceptions which can be handled
by the caller as desired. This doesn't matter for the normal case of one Synapse
instance running in the Python process (as we're only affecting ourselves), but is
important when we have multiple Synapse homeserver tenants running in the same
Python process (c.f. Synapse Pro for small hosts) as we don't want some problem from
one tenant stopping the rest of the tenants.
Args:
hs: The homeserver to setup.
freeze: whether to freeze the homeserver base objects in the garbage collector.
May improve garbage collection performance by marking objects with an effectively
static lifetime as frozen so they don't need to be considered for cleanup.
If you ever want to `shutdown` the homeserver, this needs to be
False otherwise the homeserver cannot be garbage collected after `shutdown`.
Returns:
A homeserver instance.
"""
setup_logging(hs, hs.config, use_worker_options=False)
@@ -419,22 +424,44 @@ def setup(
except Exception as e:
handle_startup_exception(e)
async def _start_when_reactor_running() -> None:
# TODO: Feels like this should be moved somewhere else.
#
# Load the OIDC provider metadatas, if OIDC is enabled.
if hs.config.oidc.oidc_enabled:
oidc = hs.get_oidc_handler()
# Loading the provider metadata also ensures the provider config is valid.
await oidc.load_metadata()
await _base.start(hs, freeze)
async def start(
hs: SynapseHomeServer,
*,
freeze: bool = True,
) -> None:
"""
Should be called once the reactor is running.
# TODO: Feels like this should be moved somewhere else.
hs.get_datastores().main.db_pool.updates.start_doing_background_updates()
Our composable functions (`create_homeserver`, `setup`, `start`) should not exit the
Python process (call `exit(...)`) and instead raise exceptions which can be handled
by the caller as desired. This doesn't matter for the normal case of one Synapse
instance running in the Python process (as we're only affecting ourselves), but is
important when we have multiple Synapse homeserver tenants running in the same
Python process (c.f. Synapse Pro for small hosts) as we don't want some problem from
one tenant stopping the rest of the tenants.
# Register a callback to be invoked once the reactor is running
register_start(hs, _start_when_reactor_running)
Args:
hs: The homeserver to setup.
freeze: whether to freeze the homeserver base objects in the garbage collector.
May improve garbage collection performance by marking objects with an effectively
static lifetime as frozen so they don't need to be considered for cleanup.
If you ever want to `shutdown` the homeserver, this needs to be
False otherwise the homeserver cannot be garbage collected after `shutdown`.
"""
# TODO: Feels like this should be moved somewhere else.
#
# Load the OIDC provider metadatas, if OIDC is enabled.
if hs.config.oidc.oidc_enabled:
oidc = hs.get_oidc_handler()
# Loading the provider metadata also ensures the provider config is valid.
await oidc.load_metadata()
await _base.start(hs, freeze=freeze)
# TODO: Feels like this should be moved somewhere else.
hs.get_datastores().main.db_pool.updates.start_doing_background_updates()
def start_reactor(
@@ -460,15 +487,18 @@ def start_reactor(
def main() -> None:
homeserver_config = load_or_generate_config(sys.argv[1:])
# Create a logging context as soon as possible so we can start associating
# everything with this homeserver.
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
# check base requirements
check_requirements()
# redirect stdio to the logs, if configured.
if not homeserver_config.logging.no_redirect_stdio:
redirect_stdio_to_logs()
hs = create_homeserver(homeserver_config)
setup(hs)
# redirect stdio to the logs, if configured.
if not hs.config.logging.no_redirect_stdio:
redirect_stdio_to_logs()
# Register a callback to be invoked once the reactor is running
register_start(hs, lambda: start(hs))
start_reactor(homeserver_config)

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -19,16 +19,11 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":

View File

@@ -19,16 +19,12 @@
#
#
import sys
from synapse.app.generic_worker import load_config, start
from synapse.util.logcontext import LoggingContext
from synapse.app.generic_worker import main as worker_main
def main() -> None:
homeserver_config = load_config(sys.argv[1:])
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
start(homeserver_config)
worker_main()
if __name__ == "__main__":