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

Commit 7308bb6

Browse files
realtyemMadLittleModsreivilibre
authored andcommitted
Unix Sockets for HTTP Replication (matrix-org#15708)
Unix socket support for `federation` and `client` Listeners has existed now for a little while(since [1.81.0](matrix-org#15353)), but there was one last hold out before it could be complete: HTTP Replication communication. This should finish it up. The Listeners would have always worked, but would have had no way to be talked to/at. --------- Co-authored-by: Eric Eastwood <[email protected]> Co-authored-by: Olivier Wilkinson (reivilibre) <[email protected]> Co-authored-by: Eric Eastwood <[email protected]>
1 parent 71eb3fb commit 7308bb6

16 files changed

+260
-52
lines changed

changelog.d/15708.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add Unix Socket support for HTTP Replication Listeners. Document and provide usage instructions for utilizing Unix sockets in Synapse. Contributed by Jason Little.

docker/conf-workers/nginx.conf.j2

+4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ server {
3535

3636
# Send all other traffic to the main process
3737
location ~* ^(\\/_matrix|\\/_synapse) {
38+
{% if using_unix_sockets %}
39+
proxy_pass http://unix:/run/main_public.sock;
40+
{% else %}
3841
proxy_pass http://localhost:8080;
42+
{% endif %}
3943
proxy_set_header X-Forwarded-For $remote_addr;
4044
proxy_set_header X-Forwarded-Proto $scheme;
4145
proxy_set_header Host $host;

docker/conf-workers/shared.yaml.j2

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
{% if enable_redis %}
77
redis:
88
enabled: true
9+
{% if using_unix_sockets %}
10+
path: /tmp/redis.sock
11+
{% endif %}
912
{% endif %}
1013

1114
{% if appservice_registrations is not none %}

docker/conf-workers/supervisord.conf.j2

+4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ username=www-data
1919
autorestart=true
2020

2121
[program:redis]
22+
{% if using_unix_sockets %}
23+
command=/usr/local/bin/prefix-log /usr/local/bin/redis-server --unixsocket /tmp/redis.sock
24+
{% else %}
2225
command=/usr/local/bin/prefix-log /usr/local/bin/redis-server
26+
{% endif %}
2327
priority=1
2428
stdout_logfile=/dev/stdout
2529
stdout_logfile_maxbytes=0

docker/conf-workers/worker.yaml.j2

+4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ worker_name: "{{ name }}"
88

99
worker_listeners:
1010
- type: http
11+
{% if using_unix_sockets %}
12+
path: "/run/worker.{{ port }}"
13+
{% else %}
1114
port: {{ port }}
15+
{% endif %}
1216
{% if listener_resources %}
1317
resources:
1418
- names:

docker/conf/homeserver.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ listeners:
3636

3737
# Allow configuring in case we want to reverse proxy 8008
3838
# using another process in the same container
39+
{% if SYNAPSE_USE_UNIX_SOCKET %}
40+
# Unix sockets don't care about TLS or IP addresses or ports
41+
- path: '/run/main_public.sock'
42+
type: http
43+
{% else %}
3944
- port: {{ SYNAPSE_HTTP_PORT or 8008 }}
4045
tls: false
4146
bind_addresses: ['::']
4247
type: http
4348
x_forwarded: false
44-
49+
{% endif %}
4550
resources:
4651
- names: [client]
4752
compress: true
@@ -57,8 +62,11 @@ database:
5762
user: "{{ POSTGRES_USER or "synapse" }}"
5863
password: "{{ POSTGRES_PASSWORD }}"
5964
database: "{{ POSTGRES_DB or "synapse" }}"
65+
{% if not SYNAPSE_USE_UNIX_SOCKET %}
66+
{# Synapse will use a default unix socket for Postgres when host/port is not specified (behavior from `psycopg2`). #}
6067
host: "{{ POSTGRES_HOST or "db" }}"
6168
port: "{{ POSTGRES_PORT or "5432" }}"
69+
{% endif %}
6270
cp_min: 5
6371
cp_max: 10
6472
{% else %}

docker/configure_workers_and_start.py

+78-26
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@
7474
MAIN_PROCESS_INSTANCE_NAME = "main"
7575
MAIN_PROCESS_LOCALHOST_ADDRESS = "127.0.0.1"
7676
MAIN_PROCESS_REPLICATION_PORT = 9093
77+
# Obviously, these would only be used with the UNIX socket option
78+
MAIN_PROCESS_UNIX_SOCKET_PUBLIC_PATH = "/run/main_public.sock"
79+
MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH = "/run/main_private.sock"
7780

7881
# A simple name used as a placeholder in the WORKERS_CONFIG below. This will be replaced
7982
# during processing with the name of the worker.
@@ -407,11 +410,15 @@ def add_worker_roles_to_shared_config(
407410
)
408411

409412
# Map of stream writer instance names to host/ports combos
410-
instance_map[worker_name] = {
411-
"host": "localhost",
412-
"port": worker_port,
413-
}
414-
413+
if os.environ.get("SYNAPSE_USE_UNIX_SOCKET", False):
414+
instance_map[worker_name] = {
415+
"path": f"/run/worker.{worker_port}",
416+
}
417+
else:
418+
instance_map[worker_name] = {
419+
"host": "localhost",
420+
"port": worker_port,
421+
}
415422
# Update the list of stream writers. It's convenient that the name of the worker
416423
# type is the same as the stream to write. Iterate over the whole list in case there
417424
# is more than one.
@@ -423,10 +430,15 @@ def add_worker_roles_to_shared_config(
423430

424431
# Map of stream writer instance names to host/ports combos
425432
# For now, all stream writers need http replication ports
426-
instance_map[worker_name] = {
427-
"host": "localhost",
428-
"port": worker_port,
429-
}
433+
if os.environ.get("SYNAPSE_USE_UNIX_SOCKET", False):
434+
instance_map[worker_name] = {
435+
"path": f"/run/worker.{worker_port}",
436+
}
437+
else:
438+
instance_map[worker_name] = {
439+
"host": "localhost",
440+
"port": worker_port,
441+
}
430442

431443

432444
def merge_worker_template_configs(
@@ -718,17 +730,29 @@ def generate_worker_files(
718730
# Note that yaml cares about indentation, so care should be taken to insert lines
719731
# into files at the correct indentation below.
720732

733+
# Convenience helper for if using unix sockets instead of host:port
734+
using_unix_sockets = environ.get("SYNAPSE_USE_UNIX_SOCKET", False)
721735
# First read the original config file and extract the listeners block. Then we'll
722736
# add another listener for replication. Later we'll write out the result to the
723737
# shared config file.
724-
listeners = [
725-
{
726-
"port": MAIN_PROCESS_REPLICATION_PORT,
727-
"bind_address": MAIN_PROCESS_LOCALHOST_ADDRESS,
728-
"type": "http",
729-
"resources": [{"names": ["replication"]}],
730-
}
731-
]
738+
listeners: List[Any]
739+
if using_unix_sockets:
740+
listeners = [
741+
{
742+
"path": MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH,
743+
"type": "http",
744+
"resources": [{"names": ["replication"]}],
745+
}
746+
]
747+
else:
748+
listeners = [
749+
{
750+
"port": MAIN_PROCESS_REPLICATION_PORT,
751+
"bind_address": MAIN_PROCESS_LOCALHOST_ADDRESS,
752+
"type": "http",
753+
"resources": [{"names": ["replication"]}],
754+
}
755+
]
732756
with open(config_path) as file_stream:
733757
original_config = yaml.safe_load(file_stream)
734758
original_listeners = original_config.get("listeners")
@@ -769,7 +793,17 @@ def generate_worker_files(
769793

770794
# A list of internal endpoints to healthcheck, starting with the main process
771795
# which exists even if no workers do.
772-
healthcheck_urls = ["http://localhost:8080/health"]
796+
# This list ends up being part of the command line to curl, (curl added support for
797+
# Unix sockets in version 7.40).
798+
if using_unix_sockets:
799+
healthcheck_urls = [
800+
f"--unix-socket {MAIN_PROCESS_UNIX_SOCKET_PUBLIC_PATH} "
801+
# The scheme and hostname from the following URL are ignored.
802+
# The only thing that matters is the path `/health`
803+
"http://localhost/health"
804+
]
805+
else:
806+
healthcheck_urls = ["http://localhost:8080/health"]
773807

774808
# Get the set of all worker types that we have configured
775809
all_worker_types_in_use = set(chain(*requested_worker_types.values()))
@@ -806,8 +840,12 @@ def generate_worker_files(
806840
# given worker_type needs to stay assigned and not be replaced.
807841
worker_config["shared_extra_conf"].update(shared_config)
808842
shared_config = worker_config["shared_extra_conf"]
809-
810-
healthcheck_urls.append("http://localhost:%d/health" % (worker_port,))
843+
if using_unix_sockets:
844+
healthcheck_urls.append(
845+
f"--unix-socket /run/worker.{worker_port} http://localhost/health"
846+
)
847+
else:
848+
healthcheck_urls.append("http://localhost:%d/health" % (worker_port,))
811849

812850
# Update the shared config with sharding-related options if necessary
813851
add_worker_roles_to_shared_config(
@@ -826,6 +864,7 @@ def generate_worker_files(
826864
"/conf/workers/{name}.yaml".format(name=worker_name),
827865
**worker_config,
828866
worker_log_config_filepath=log_config_filepath,
867+
using_unix_sockets=using_unix_sockets,
829868
)
830869

831870
# Save this worker's port number to the correct nginx upstreams
@@ -846,8 +885,13 @@ def generate_worker_files(
846885
nginx_upstream_config = ""
847886
for upstream_worker_base_name, upstream_worker_ports in nginx_upstreams.items():
848887
body = ""
849-
for port in upstream_worker_ports:
850-
body += f" server localhost:{port};\n"
888+
if using_unix_sockets:
889+
for port in upstream_worker_ports:
890+
body += f" server unix:/run/worker.{port};\n"
891+
892+
else:
893+
for port in upstream_worker_ports:
894+
body += f" server localhost:{port};\n"
851895

852896
# Add to the list of configured upstreams
853897
nginx_upstream_config += NGINX_UPSTREAM_CONFIG_BLOCK.format(
@@ -877,10 +921,15 @@ def generate_worker_files(
877921
# If there are workers, add the main process to the instance_map too.
878922
if workers_in_use:
879923
instance_map = shared_config.setdefault("instance_map", {})
880-
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
881-
"host": MAIN_PROCESS_LOCALHOST_ADDRESS,
882-
"port": MAIN_PROCESS_REPLICATION_PORT,
883-
}
924+
if using_unix_sockets:
925+
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
926+
"path": MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH,
927+
}
928+
else:
929+
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
930+
"host": MAIN_PROCESS_LOCALHOST_ADDRESS,
931+
"port": MAIN_PROCESS_REPLICATION_PORT,
932+
}
884933

885934
# Shared homeserver config
886935
convert(
@@ -890,6 +939,7 @@ def generate_worker_files(
890939
appservice_registrations=appservice_registrations,
891940
enable_redis=workers_in_use,
892941
workers_in_use=workers_in_use,
942+
using_unix_sockets=using_unix_sockets,
893943
)
894944

895945
# Nginx config
@@ -900,6 +950,7 @@ def generate_worker_files(
900950
upstream_directives=nginx_upstream_config,
901951
tls_cert_path=os.environ.get("SYNAPSE_TLS_CERT"),
902952
tls_key_path=os.environ.get("SYNAPSE_TLS_KEY"),
953+
using_unix_sockets=using_unix_sockets,
903954
)
904955

905956
# Supervisord config
@@ -909,6 +960,7 @@ def generate_worker_files(
909960
"/etc/supervisor/supervisord.conf",
910961
main_config_path=config_path,
911962
enable_redis=workers_in_use,
963+
using_unix_sockets=using_unix_sockets,
912964
)
913965

914966
convert(

docs/development/contributing_guide.md

+1
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ The above will run a monolithic (single-process) Synapse with SQLite as the data
370370
See the [worker documentation](../workers.md) for additional information on workers.
371371
- Passing `ASYNCIO_REACTOR=1` as an environment variable to use the Twisted asyncio reactor instead of the default one.
372372
- Passing `PODMAN=1` will use the [podman](https://podman.io/) container runtime, instead of docker.
373+
- Passing `UNIX_SOCKETS=1` will utilise Unix socket functionality for Synapse, Redis, and Postgres(when applicable).
373374
374375
To increase the log level for the tests, set `SYNAPSE_TEST_LOG_LEVEL`, e.g:
375376
```sh

docs/usage/configuration/config_documentation.md

+51-1
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,20 @@ See the docs [request log format](../administration/request_log.md).
462462
* `additional_resources`: Only valid for an 'http' listener. A map of
463463
additional endpoints which should be loaded via dynamic modules.
464464

465+
Unix socket support (_Added in Synapse 1.88.0_):
466+
* `path`: A path and filename for a Unix socket. Make sure it is located in a
467+
directory with read and write permissions, and that it already exists (the directory
468+
will not be created). Defaults to `None`.
469+
* **Note**: The use of both `path` and `port` options for the same `listener` is not
470+
compatible.
471+
* The `x_forwarded` option defaults to true when using Unix sockets and can be omitted.
472+
* Other options that would not make sense to use with a UNIX socket, such as
473+
`bind_addresses` and `tls` will be ignored and can be removed.
474+
* `mode`: The file permissions to set on the UNIX socket. Defaults to `666`
475+
* **Note:** Must be set as `type: http` (does not support `metrics` and `manhole`).
476+
Also make sure that `metrics` is not included in `resources` -> `names`
477+
478+
465479
Valid resource names are:
466480

467481
* `client`: the client-server API (/_matrix/client), and the synapse admin API (/_synapse/admin). Also implies `media` and `static`.
@@ -474,7 +488,7 @@ Valid resource names are:
474488

475489
* `media`: the media API (/_matrix/media).
476490

477-
* `metrics`: the metrics interface. See [here](../../metrics-howto.md).
491+
* `metrics`: the metrics interface. See [here](../../metrics-howto.md). (Not compatible with Unix sockets)
478492

479493
* `openid`: OpenID authentication. See [here](../../openid.md).
480494

@@ -533,6 +547,22 @@ listeners:
533547
bind_addresses: ['::1', '127.0.0.1']
534548
type: manhole
535549
```
550+
Example configuration #3:
551+
```yaml
552+
listeners:
553+
# Unix socket listener: Ideal for Synapse deployments behind a reverse proxy, offering
554+
# lightweight interprocess communication without TCP/IP overhead, avoid port
555+
# conflicts, and providing enhanced security through system file permissions.
556+
#
557+
# Note that x_forwarded will default to true, when using a UNIX socket. Please see
558+
# https://matrix-org.github.io/synapse/latest/reverse_proxy.html.
559+
#
560+
- path: /var/run/synapse/main_public.sock
561+
type: http
562+
resources:
563+
- names: [client, federation]
564+
```
565+
536566
---
537567
### `manhole_settings`
538568

@@ -3949,6 +3979,14 @@ instance_map:
39493979
host: localhost
39503980
port: 8034
39513981
```
3982+
Example configuration(#2, for UNIX sockets):
3983+
```yaml
3984+
instance_map:
3985+
main:
3986+
path: /var/run/synapse/main_replication.sock
3987+
worker1:
3988+
path: /var/run/synapse/worker1_replication.sock
3989+
```
39523990
---
39533991
### `stream_writers`
39543992

@@ -4108,6 +4146,18 @@ worker_listeners:
41084146
resources:
41094147
- names: [client, federation]
41104148
```
4149+
Example configuration(#2, using UNIX sockets with a `replication` listener):
4150+
```yaml
4151+
worker_listeners:
4152+
- type: http
4153+
path: /var/run/synapse/worker_public.sock
4154+
resources:
4155+
- names: [client, federation]
4156+
- type: http
4157+
path: /var/run/synapse/worker_replication.sock
4158+
resources:
4159+
- names: [replication]
4160+
```
41114161
---
41124162
### `worker_manhole`
41134163

docs/workers.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,12 @@ for the main process
9595
* Secondly, you need to enable
9696
[redis-based replication](usage/configuration/config_documentation.md#redis)
9797
* You will need to add an [`instance_map`](usage/configuration/config_documentation.md#instance_map)
98-
with the `main` process defined, as well as the relevant connection information from
99-
it's HTTP `replication` listener (defined in step 1 above). Note that the `host` defined
100-
is the address the worker needs to look for the `main` process at, not necessarily the same address that is bound to.
98+
with the `main` process defined, as well as the relevant connection information from
99+
it's HTTP `replication` listener (defined in step 1 above).
100+
* Note that the `host` defined is the address the worker needs to look for the `main`
101+
process at, not necessarily the same address that is bound to.
102+
* If you are using Unix sockets for the `replication` resource, make sure to
103+
use a `path` to the socket file instead of a `port`.
101104
* Optionally, a [shared secret](usage/configuration/config_documentation.md#worker_replication_secret)
102105
can be used to authenticate HTTP traffic between workers. For example:
103106

scripts-dev/complement.sh

+4
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ if [[ -n "$ASYNCIO_REACTOR" ]]; then
253253
export PASS_SYNAPSE_COMPLEMENT_USE_ASYNCIO_REACTOR=true
254254
fi
255255

256+
if [[ -n "$UNIX_SOCKETS" ]]; then
257+
# Enable full on Unix socket mode for Synapse, Redis and Postgresql
258+
export PASS_SYNAPSE_USE_UNIX_SOCKET=1
259+
fi
256260

257261
if [[ -n "$SYNAPSE_TEST_LOG_LEVEL" ]]; then
258262
# Set the log level to what is desired

0 commit comments

Comments
 (0)