Skip to content

Commit 715cc5e

Browse files
Split homeserver creation and setup (#19015)
### Background As part of Element's plan to support a light form of vhosting (virtual host) (multiple instances of Synapse in the same Python process), we're currently diving into the details and implications of running multiple instances of Synapse in the same Python process. "Clean tenant provisioning" tracked internally by element-hq/synapse-small-hosts#221 ### Partial startup problem In the context of Synapse Pro for Small Hosts, since the Twisted reactor is already running (from the `multi_synapse` shard process itself), when provisioning a homeserver tenant, the `reactor.callWhenRunning(...)` callbacks will be invoked immediately. This includes the Synapse's [`start`](https://github.com/element-hq/synapse/blob/0615b64bb49684b846110465052642a46fd27028/synapse/app/homeserver.py#L418-L429) callback which sets up everything (including listeners, background tasks, etc). If we encounter an error at this point, we are partially setup but the exception will [bubble back to us](https://github.com/element-hq/synapse-small-hosts/blob/8be122186bf1acb8c0426d84eb3abded25d682b7/multi_synapse/app/shard.py#L114-L121) without us having a handle to the homeserver yet so we can't call `hs.shutdown()` and clean everything up. ### What does this PR do? Structures Synapse so we split creating the homeserver instance from setting everything up. This way we have access to `hs` if anything goes wrong during setup and can subsequently `hs.shutdown()` to clean everything up.
1 parent d440cfc commit 715cc5e

File tree

4 files changed

+73
-26
lines changed

4 files changed

+73
-26
lines changed

changelog.d/19015.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Split homeserver creation (`create_homeserver`) and setup (`setup`).

synapse/app/homeserver.py

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ def gz_wrap(r: Resource) -> Resource:
8383

8484

8585
class SynapseHomeServer(HomeServer):
86+
"""
87+
Homeserver class for the main Synapse process.
88+
"""
89+
8690
DATASTORE_CLASS = DataStore
8791

8892
def _listener_http(
@@ -345,23 +349,17 @@ def load_or_generate_config(argv_options: List[str]) -> HomeServerConfig:
345349
return config
346350

347351

348-
def setup(
352+
def create_homeserver(
349353
config: HomeServerConfig,
350354
reactor: Optional[ISynapseReactor] = None,
351-
freeze: bool = True,
352355
) -> SynapseHomeServer:
353356
"""
354-
Create and setup a Synapse homeserver instance given a configuration.
357+
Create a homeserver instance for the Synapse main process.
355358
356359
Args:
357360
config: The configuration for the homeserver.
358361
reactor: Optionally provide a reactor to use. Can be useful in different
359362
scenarios that you want control over the reactor, such as tests.
360-
freeze: whether to freeze the homeserver base objects in the garbage collector.
361-
May improve garbage collection performance by marking objects with an effectively
362-
static lifetime as frozen so they don't need to be considered for cleanup.
363-
If you ever want to `shutdown` the homeserver, this needs to be
364-
False otherwise the homeserver cannot be garbage collected after `shutdown`.
365363
366364
Returns:
367365
A homeserver instance.
@@ -372,7 +370,6 @@ def setup(
372370
"You have specified `worker_app` in the config but are attempting to start a non-worker "
373371
"instance. Please use `python -m synapse.app.generic_worker` instead (or remove the option if this is the main process)."
374372
)
375-
sys.exit(1)
376373

377374
events.USE_FROZEN_DICTS = config.server.use_frozen_dicts
378375
synapse.util.caches.TRACK_MEMORY_USAGE = config.caches.track_memory_usage
@@ -397,24 +394,50 @@ def setup(
397394
)
398395

399396
hs = SynapseHomeServer(
400-
config.server.server_name,
397+
hostname=config.server.server_name,
401398
config=config,
402399
reactor=reactor,
403400
)
404401

405-
setup_logging(hs, config, use_worker_options=False)
402+
return hs
406403

407-
# Start the tracer
408-
init_tracer(hs) # noqa
409404

405+
def setup(
406+
hs: SynapseHomeServer,
407+
*,
408+
freeze: bool = True,
409+
) -> None:
410+
"""
411+
Setup a Synapse homeserver instance given a configuration.
412+
413+
Args:
414+
hs: The homeserver to setup.
415+
freeze: whether to freeze the homeserver base objects in the garbage collector.
416+
May improve garbage collection performance by marking objects with an effectively
417+
static lifetime as frozen so they don't need to be considered for cleanup.
418+
If you ever want to `shutdown` the homeserver, this needs to be
419+
False otherwise the homeserver cannot be garbage collected after `shutdown`.
420+
421+
Returns:
422+
A homeserver instance.
423+
"""
424+
425+
setup_logging(hs, hs.config, use_worker_options=False)
426+
427+
# Log after we've configured logging.
410428
logger.info("Setting up server")
411429

430+
# Start the tracer
431+
init_tracer(hs) # noqa
432+
412433
try:
413434
hs.setup()
414435
except Exception as e:
415436
handle_startup_exception(e)
416437

417-
async def start() -> None:
438+
async def _start_when_reactor_running() -> None:
439+
# TODO: Feels like this should be moved somewhere else.
440+
#
418441
# Load the OIDC provider metadatas, if OIDC is enabled.
419442
if hs.config.oidc.oidc_enabled:
420443
oidc = hs.get_oidc_handler()
@@ -423,21 +446,31 @@ async def start() -> None:
423446

424447
await _base.start(hs, freeze)
425448

449+
# TODO: This should be moved to `SynapseHomeServer.start_background_tasks` (not
450+
# `HomeServer.start_background_tasks`) (this way it matches the behavior of only
451+
# running on `main`)
426452
hs.get_datastores().main.db_pool.updates.start_doing_background_updates()
427453

428-
register_start(hs, start)
454+
# Register a callback to be invoked once the reactor is running
455+
register_start(hs, _start_when_reactor_running)
429456

430-
return hs
431457

458+
def start_reactor(
459+
config: HomeServerConfig,
460+
) -> None:
461+
"""
462+
Start the reactor (Twisted event-loop).
432463
433-
def run(hs: HomeServer) -> None:
464+
Args:
465+
config: The configuration for the homeserver.
466+
"""
434467
_base.start_reactor(
435468
"synapse-homeserver",
436-
soft_file_limit=hs.config.server.soft_file_limit,
437-
gc_thresholds=hs.config.server.gc_thresholds,
438-
pid_file=hs.config.server.pid_file,
439-
daemonize=hs.config.server.daemonize,
440-
print_pidfile=hs.config.server.print_pidfile,
469+
soft_file_limit=config.server.soft_file_limit,
470+
gc_thresholds=config.server.gc_thresholds,
471+
pid_file=config.server.pid_file,
472+
daemonize=config.server.daemonize,
473+
print_pidfile=config.server.print_pidfile,
441474
logger=logger,
442475
)
443476

@@ -448,13 +481,14 @@ def main() -> None:
448481
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
449482
# check base requirements
450483
check_requirements()
451-
hs = setup(homeserver_config)
484+
hs = create_homeserver(homeserver_config)
485+
setup(hs)
452486

453487
# redirect stdio to the logs, if configured.
454488
if not hs.config.logging.no_redirect_stdio:
455489
redirect_stdio_to_logs()
456490

457-
run(hs)
491+
start_reactor(homeserver_config)
458492

459493

460494
if __name__ == "__main__":

tests/app/test_homeserver_start.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ def test_wrong_start_caught(self) -> None:
3737
self.add_lines_to_config([" main:", " host: 127.0.0.1", " port: 1234"])
3838
# Ensure that starting master process with worker config raises an exception
3939
with self.assertRaises(ConfigError):
40+
# Do a normal homeserver creation and setup
4041
homeserver_config = synapse.app.homeserver.load_or_generate_config(
4142
["-c", self.config_file]
4243
)
43-
synapse.app.homeserver.setup(homeserver_config)
44+
# XXX: The error will be raised at this point
45+
hs = synapse.app.homeserver.create_homeserver(homeserver_config)
46+
# Continue with the setup. We don't expect this to run because we raised
47+
# earlier, but in the future, the code could be refactored to raise the
48+
# error in a different place.
49+
synapse.app.homeserver.setup(hs)

tests/config/test_registration_config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,13 @@ def test_refuse_to_start_if_open_registration_and_no_verification(self) -> None:
112112

113113
# Test that allowing open registration without verification raises an error
114114
with self.assertRaises(ConfigError):
115+
# Do a normal homeserver creation and setup
115116
homeserver_config = synapse.app.homeserver.load_or_generate_config(
116117
["-c", self.config_file]
117118
)
118-
synapse.app.homeserver.setup(homeserver_config)
119+
# XXX: The error will be raised at this point
120+
hs = synapse.app.homeserver.create_homeserver(homeserver_config)
121+
# Continue with the setup. We don't expect this to run because we raised
122+
# earlier, but in the future, the code could be refactored to raise the
123+
# error in a different place.
124+
synapse.app.homeserver.setup(hs)

0 commit comments

Comments
 (0)