Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add missing type hints to synapse.app. #11287

Merged
merged 13 commits into from
Nov 10, 2021
1 change: 0 additions & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ exclude = (?x)
|synapse/_scripts/register_new_matrix_user.py
|synapse/_scripts/review_recent_signups.py
|synapse/app/__init__.py
|synapse/app/_base.py
|synapse/storage/databases/__init__.py
|synapse/storage/databases/main/__init__.py
|synapse/storage/databases/main/account_data.py
Expand Down
138 changes: 86 additions & 52 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,24 @@
import sys
import traceback
import warnings
from typing import TYPE_CHECKING, Awaitable, Callable, Iterable
from typing import (
TYPE_CHECKING,
Any,
Awaitable,
Callable,
Dict,
Iterable,
List,
Tuple,
)

from cryptography.utils import CryptographyDeprecationWarning
from typing_extensions import NoReturn

import twisted
from twisted.internet import defer, error, reactor
from twisted.internet.interfaces import IOpenSSLContextFactory, IReactorSSL, IReactorTCP
from twisted.internet.protocol import ServerFactory
from twisted.internet.tcp import Port
from twisted.logger import LoggingFile, LogLevel
from twisted.protocols.tls import TLSMemoryBIOFactory
from twisted.python.threadpool import ThreadPool
Expand Down Expand Up @@ -60,30 +71,37 @@
logger = logging.getLogger(__name__)

# list of tuples of function, args list, kwargs dict
_sighup_callbacks = []
_sighup_callbacks: List[
Tuple[Callable[..., None], Tuple[Any, ...], Dict[str, Any]]
] = []


def register_sighup(func, *args, **kwargs):
def register_sighup(func: Callable[..., None], *args: Any, **kwargs: Any) -> None:
"""
Register a function to be called when a SIGHUP occurs.

Args:
func (function): Function to be called when sent a SIGHUP signal.
func: Function to be called when sent a SIGHUP signal.
*args, **kwargs: args and kwargs to be passed to the target function.
"""
_sighup_callbacks.append((func, args, kwargs))


def start_worker_reactor(appname, config, run_command=reactor.run):
def start_worker_reactor(
appname: str,
config: HomeServerConfig,
# mypy can't figure out what the default reactor is.
run_command: Callable[[], None] = reactor.run, # type: ignore[attr-defined]
clokep marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
"""Run the reactor in the main process

Daemonizes if necessary, and then configures some resources, before starting
the reactor. Pulls configuration from the 'worker' settings in 'config'.

Args:
appname (str): application name which will be sent to syslog
config (synapse.config.Config): config object
run_command (Callable[]): callable that actually runs the reactor
appname: application name which will be sent to syslog
config: config object
run_command: callable that actually runs the reactor
"""

logger = logging.getLogger(config.worker.worker_app)
Expand All @@ -101,32 +119,33 @@ def start_worker_reactor(appname, config, run_command=reactor.run):


def start_reactor(
appname,
soft_file_limit,
gc_thresholds,
pid_file,
daemonize,
print_pidfile,
logger,
run_command=reactor.run,
):
appname: str,
soft_file_limit: int,
gc_thresholds: Tuple[int, int, int],
pid_file: str,
daemonize: bool,
print_pidfile: bool,
logger: logging.Logger,
# mypy can't figure out what the default reactor is.
run_command: Callable[[], None] = reactor.run, # type: ignore[attr-defined]
) -> None:
"""Run the reactor in the main process

Daemonizes if necessary, and then configures some resources, before starting
the reactor

Args:
appname (str): application name which will be sent to syslog
soft_file_limit (int):
appname: application name which will be sent to syslog
soft_file_limit:
gc_thresholds:
pid_file (str): name of pid file to write to if daemonize is True
daemonize (bool): true to run the reactor in a background process
print_pidfile (bool): whether to print the pid file, if daemonize is True
logger (logging.Logger): logger instance to pass to Daemonize
run_command (Callable[]): callable that actually runs the reactor
pid_file: name of pid file to write to if daemonize is True
daemonize: true to run the reactor in a background process
print_pidfile: whether to print the pid file, if daemonize is True
logger: logger instance to pass to Daemonize
run_command: callable that actually runs the reactor
"""

def run():
def run() -> None:
logger.info("Running")
setup_jemalloc_stats()
change_resource_limit(soft_file_limit)
Expand All @@ -151,7 +170,7 @@ def run():
run()


def quit_with_error(error_string: str) -> NoReturn:
def quit_with_error(error_string: str) -> None:
clokep marked this conversation as resolved.
Show resolved Hide resolved
message_lines = error_string.split("\n")
line_length = min(max(len(line) for line in message_lines), 80) + 2
sys.stderr.write("*" * line_length + "\n")
Expand All @@ -161,7 +180,7 @@ def quit_with_error(error_string: str) -> NoReturn:
sys.exit(1)


def handle_startup_exception(e: Exception) -> NoReturn:
def handle_startup_exception(e: Exception) -> None:
clokep marked this conversation as resolved.
Show resolved Hide resolved
# Exceptions that occur between setting up the logging and forking or starting
# the reactor are written to the logs, followed by a summary to stderr.
logger.exception("Exception during startup")
Expand All @@ -185,7 +204,7 @@ def redirect_stdio_to_logs() -> None:
print("Redirected stdout/stderr to logs")


def register_start(cb: Callable[..., Awaitable], *args, **kwargs) -> None:
def register_start(cb: Callable[..., Awaitable], *args: Any, **kwargs: Any) -> None:
"""Register a callback with the reactor, to be called once it is running

This can be used to initialise parts of the system which require an asynchronous
Expand All @@ -195,7 +214,7 @@ def register_start(cb: Callable[..., Awaitable], *args, **kwargs) -> None:
will exit.
"""

async def wrapper():
async def wrapper() -> None:
try:
await cb(*args, **kwargs)
except Exception:
Expand All @@ -221,10 +240,11 @@ async def wrapper():
# on as normal.
os._exit(1)

reactor.callWhenRunning(lambda: defer.ensureDeferred(wrapper()))
# mypy can't figure out what the default reactor is.
reactor.callWhenRunning(lambda: defer.ensureDeferred(wrapper())) # type: ignore[attr-defined]


def listen_metrics(bind_addresses, port):
def listen_metrics(bind_addresses: Iterable[str], port: int) -> None:
"""
Start Prometheus metrics server.
"""
Expand All @@ -240,7 +260,7 @@ def listen_manhole(
port: int,
manhole_settings: ManholeConfig,
manhole_globals: dict,
):
) -> None:
# twisted.conch.manhole 21.1.0 uses "int_from_bytes", which produces a confusing
# warning. It's fixed by https://github.com/twisted/twisted/pull/1522), so
# suppress the warning for now.
Expand All @@ -259,12 +279,19 @@ def listen_manhole(
)


def listen_tcp(bind_addresses, port, factory, reactor=reactor, backlog=50):
def listen_tcp(
bind_addresses: Iterable[str],
port: int,
factory: ServerFactory,
# mypy can't figure out what the default reactor is.
reactor: IReactorTCP = reactor, # type: ignore[assignment]
backlog: int = 50,
) -> List[Port]:
"""
Create a TCP socket for a port and several addresses

Returns:
list[twisted.internet.tcp.Port]: listening for TCP connections
list of twisted.internet.tcp.Port listening for TCP connections
"""
r = []
for address in bind_addresses:
Expand All @@ -273,12 +300,20 @@ def listen_tcp(bind_addresses, port, factory, reactor=reactor, backlog=50):
except error.CannotListenError as e:
check_bind_error(e, address, bind_addresses)

return r
# IReactorTCP returns an object implementing IListeningPort from listenTCP,
# but we know it will be a Port instance.
return r # type: ignore[return-value]
Copy link
Member Author

@clokep clokep Nov 10, 2021

Choose a reason for hiding this comment

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

Maybe I should have used a cast here? This is vaguely unsafe in that the implementation, not the interface, defines this. Realistically this is deep in Twisted and I can't imagine this changing.

(This pretty much applies to listen_ssl too.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Given that there are not other lines that follow, I think a cast and a type-ignore are broadly equivalent.



def listen_ssl(
bind_addresses, port, factory, context_factory, reactor=reactor, backlog=50
):
bind_addresses: Iterable[str],
port: int,
factory: ServerFactory,
context_factory: IOpenSSLContextFactory,
# mypy can't figure out what the default reactor is.
reactor: IReactorSSL = reactor, # type: ignore[assignment]
backlog: int = 50,
) -> List[Port]:
"""
Create an TLS-over-TCP socket for a port and several addresses

Expand All @@ -294,10 +329,13 @@ def listen_ssl(
except error.CannotListenError as e:
check_bind_error(e, address, bind_addresses)

return r
# IReactorSSL incorrectly declares that an int is returned from listenSSL,
# it actually returns an object implementing IListeningPort, but we know it
# will be a Port instance.
Comment on lines +336 to +338
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

return r # type: ignore[return-value]


def refresh_certificate(hs: "HomeServer"):
def refresh_certificate(hs: "HomeServer") -> None:
"""
Refresh the TLS certificates that Synapse is using by re-reading them from
disk and updating the TLS context factories to use them.
Expand Down Expand Up @@ -329,7 +367,7 @@ def refresh_certificate(hs: "HomeServer"):
logger.info("Context factories updated.")


async def start(hs: "HomeServer"):
async def start(hs: "HomeServer") -> None:
"""
Start a Synapse server or worker.

Expand Down Expand Up @@ -360,7 +398,7 @@ async def start(hs: "HomeServer"):
if hasattr(signal, "SIGHUP"):

@wrap_as_background_process("sighup")
def handle_sighup(*args, **kwargs):
def handle_sighup(*args: Any, **kwargs: Any) -> None:
# Tell systemd our state, if we're using it. This will silently fail if
# we're not using systemd.
sdnotify(b"RELOADING=1")
Expand All @@ -373,7 +411,7 @@ def handle_sighup(*args, **kwargs):
# We defer running the sighup handlers until next reactor tick. This
# is so that we're in a sane state, e.g. flushing the logs may fail
# if the sighup happens in the middle of writing a log entry.
def run_sighup(*args, **kwargs):
def run_sighup(*args: Any, **kwargs: Any) -> None:
# `callFromThread` should be "signal safe" as well as thread
# safe.
reactor.callFromThread(handle_sighup, *args, **kwargs)
Expand Down Expand Up @@ -436,12 +474,8 @@ def run_sighup(*args, **kwargs):
atexit.register(gc.freeze)


def setup_sentry(hs: "HomeServer"):
"""Enable sentry integration, if enabled in configuration

Args:
hs
"""
def setup_sentry(hs: "HomeServer") -> None:
"""Enable sentry integration, if enabled in configuration"""

if not hs.config.metrics.sentry_enabled:
return
Expand All @@ -466,7 +500,7 @@ def setup_sentry(hs: "HomeServer"):
scope.set_tag("worker_name", name)


def setup_sdnotify(hs: "HomeServer"):
def setup_sdnotify(hs: "HomeServer") -> None:
"""Adds process state hooks to tell systemd what we are up to."""

# Tell systemd our state, if we're using it. This will silently fail if
Expand All @@ -481,7 +515,7 @@ def setup_sdnotify(hs: "HomeServer"):
sdnotify_sockaddr = os.getenv("NOTIFY_SOCKET")


def sdnotify(state):
def sdnotify(state: bytes) -> None:
"""
Send a notification to systemd, if the NOTIFY_SOCKET env var is set.

Expand All @@ -490,7 +524,7 @@ def sdnotify(state):
package which many OSes don't include as a matter of principle.

Args:
state (bytes): notification to send
state: notification to send
"""
if not isinstance(state, bytes):
raise TypeError("sdnotify should be called with a bytes")
Expand Down
2 changes: 2 additions & 0 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ def _listener_http(
)

if tls:
# refresh_certificate should have been called before this.
assert self.tls_server_context_factory is not None
ports = listen_ssl(
bind_addresses,
port,
Expand Down
4 changes: 2 additions & 2 deletions synapse/replication/tcp/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from prometheus_client import Counter

from twisted.internet.protocol import Factory
from twisted.internet.protocol import ServerFactory

from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.replication.tcp.commands import PositionCommand
Expand All @@ -38,7 +38,7 @@
logger = logging.getLogger(__name__)


class ReplicationStreamProtocolFactory(Factory):
class ReplicationStreamProtocolFactory(ServerFactory):
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the motivation here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh, this explains it. Nice spot!

"""Factory for new replication connections."""

def __init__(self, hs: "HomeServer"):
Expand Down
6 changes: 3 additions & 3 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
cast,
)

import twisted.internet.tcp
from twisted.internet.interfaces import IOpenSSLContextFactory
from twisted.internet.tcp import Port
from twisted.web.iweb import IPolicyForHTTPS
from twisted.web.resource import Resource

Expand Down Expand Up @@ -207,7 +207,7 @@ class HomeServer(metaclass=abc.ABCMeta):

Attributes:
config (synapse.config.homeserver.HomeserverConfig):
_listening_services (list[twisted.internet.tcp.Port]): TCP ports that
_listening_services (list[Port]): TCP ports that
we are listening on to provide HTTP services.
"""

Expand Down Expand Up @@ -250,7 +250,7 @@ def __init__(
# the key we use to sign events and requests
self.signing_key = config.key.signing_key[0]
self.config = config
self._listening_services: List[twisted.internet.tcp.Port] = []
self._listening_services: List[Port] = []
self.start_time: Optional[int] = None

self._instance_id = random_string(5)
Expand Down
2 changes: 2 additions & 0 deletions synapse/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from twisted.internet.interfaces import (
IReactorCore,
IReactorPluggableNameResolver,
IReactorSSL,
IReactorTCP,
IReactorThreads,
IReactorTime,
Expand Down Expand Up @@ -66,6 +67,7 @@
# for mypy-zope to realize it is an interface.
class ISynapseReactor(
IReactorTCP,
IReactorSSL,
IReactorPluggableNameResolver,
IReactorTime,
IReactorCore,
Expand Down
Loading