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

Commit c835bef

Browse files
authored
Add Unix socket support for Redis connections (#15644)
Adds a new configuration setting to connect to Redis via a Unix socket instead of over TCP. Disabled by default.
1 parent 50918c4 commit c835bef

File tree

7 files changed

+100
-23
lines changed

7 files changed

+100
-23
lines changed

changelog.d/15644.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add Unix socket support for Redis connections. Contributed by Jason Little.

docs/usage/configuration/config_documentation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3979,6 +3979,8 @@ This setting has the following sub-options:
39793979
* `enabled`: whether to use Redis support. Defaults to false.
39803980
* `host` and `port`: Optional host and port to use to connect to redis. Defaults to
39813981
localhost and 6379
3982+
* `path`: The full path to a local Unix socket file. **If this is used, `host` and
3983+
`port` are ignored.** Defaults to `/tmp/redis.sock'
39823984
* `password`: Optional password if configured on the Redis instance.
39833985
* `dbid`: Optional redis dbid if needs to connect to specific redis logical db.
39843986
* `use_tls`: Whether to use tls connection. Defaults to false.
@@ -3991,6 +3993,8 @@ This setting has the following sub-options:
39913993

39923994
_Changed in Synapse 1.84.0: Added use\_tls, certificate\_file, private\_key\_file, ca\_file and ca\_path attributes_
39933995

3996+
_Changed in Synapse 1.85.0: Added path option to use a local Unix socket_
3997+
39943998
Example configuration:
39953999
```yaml
39964000
redis:

stubs/txredisapi.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def lazyConnection(
6161
# most methods to it via ConnectionHandler.__getattr__.
6262
class ConnectionHandler(RedisProtocol):
6363
def disconnect(self) -> "Deferred[None]": ...
64+
def __repr__(self) -> str: ...
65+
66+
class UnixConnectionHandler(ConnectionHandler): ...
6467

6568
class RedisFactory(protocol.ReconnectingClientFactory):
6669
continueTrying: bool

synapse/config/redis.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
3333

3434
self.redis_host = redis_config.get("host", "localhost")
3535
self.redis_port = redis_config.get("port", 6379)
36+
self.redis_path = redis_config.get("path", None)
3637
self.redis_dbid = redis_config.get("dbid", None)
3738
self.redis_password = redis_config.get("password")
3839

synapse/replication/tcp/handler.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,15 @@ def start_replication(self, hs: "HomeServer") -> None:
352352

353353
reactor = hs.get_reactor()
354354
redis_config = hs.config.redis
355-
if hs.config.redis.redis_use_tls:
355+
if redis_config.redis_path is not None:
356+
reactor.connectUNIX(
357+
redis_config.redis_path,
358+
self._factory,
359+
timeout=30,
360+
checkPID=False,
361+
)
362+
363+
elif hs.config.redis.redis_use_tls:
356364
ssl_context_factory = ClientContextFactory(hs.config.redis)
357365
reactor.connectSSL(
358366
redis_config.redis_host,

synapse/replication/tcp/redis.py

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
from typing import TYPE_CHECKING, Any, Generic, List, Optional, Type, TypeVar, cast
1818

1919
import attr
20-
import txredisapi
20+
from txredisapi import (
21+
ConnectionHandler,
22+
RedisFactory,
23+
SubscriberProtocol,
24+
UnixConnectionHandler,
25+
)
2126
from zope.interface import implementer
2227

2328
from twisted.internet.address import IPv4Address, IPv6Address
@@ -68,7 +73,7 @@ def __set__(self, obj: Optional[T], value: V) -> None:
6873

6974

7075
@implementer(IReplicationConnection)
71-
class RedisSubscriber(txredisapi.SubscriberProtocol):
76+
class RedisSubscriber(SubscriberProtocol):
7277
"""Connection to redis subscribed to replication stream.
7378
7479
This class fulfils two functions:
@@ -95,7 +100,7 @@ class RedisSubscriber(txredisapi.SubscriberProtocol):
95100
synapse_handler: "ReplicationCommandHandler"
96101
synapse_stream_prefix: str
97102
synapse_channel_names: List[str]
98-
synapse_outbound_redis_connection: txredisapi.ConnectionHandler
103+
synapse_outbound_redis_connection: ConnectionHandler
99104

100105
def __init__(self, *args: Any, **kwargs: Any):
101106
super().__init__(*args, **kwargs)
@@ -229,7 +234,7 @@ async def _async_send_command(self, cmd: Command) -> None:
229234
)
230235

231236

232-
class SynapseRedisFactory(txredisapi.RedisFactory):
237+
class SynapseRedisFactory(RedisFactory):
233238
"""A subclass of RedisFactory that periodically sends pings to ensure that
234239
we detect dead connections.
235240
"""
@@ -245,7 +250,7 @@ def __init__(
245250
dbid: Optional[int],
246251
poolsize: int,
247252
isLazy: bool = False,
248-
handler: Type = txredisapi.ConnectionHandler,
253+
handler: Type = ConnectionHandler,
249254
charset: str = "utf-8",
250255
password: Optional[str] = None,
251256
replyTimeout: int = 30,
@@ -326,7 +331,7 @@ class RedisDirectTcpReplicationClientFactory(SynapseRedisFactory):
326331
def __init__(
327332
self,
328333
hs: "HomeServer",
329-
outbound_redis_connection: txredisapi.ConnectionHandler,
334+
outbound_redis_connection: ConnectionHandler,
330335
channel_names: List[str],
331336
):
332337
super().__init__(
@@ -368,7 +373,7 @@ def lazyConnection(
368373
reconnect: bool = True,
369374
password: Optional[str] = None,
370375
replyTimeout: int = 30,
371-
) -> txredisapi.ConnectionHandler:
376+
) -> ConnectionHandler:
372377
"""Creates a connection to Redis that is lazily set up and reconnects if the
373378
connections is lost.
374379
"""
@@ -380,7 +385,7 @@ def lazyConnection(
380385
dbid=dbid,
381386
poolsize=1,
382387
isLazy=True,
383-
handler=txredisapi.ConnectionHandler,
388+
handler=ConnectionHandler,
384389
password=password,
385390
replyTimeout=replyTimeout,
386391
)
@@ -408,3 +413,44 @@ def lazyConnection(
408413
)
409414

410415
return factory.handler
416+
417+
418+
def lazyUnixConnection(
419+
hs: "HomeServer",
420+
path: str = "/tmp/redis.sock",
421+
dbid: Optional[int] = None,
422+
reconnect: bool = True,
423+
password: Optional[str] = None,
424+
replyTimeout: int = 30,
425+
) -> ConnectionHandler:
426+
"""Creates a connection to Redis that is lazily set up and reconnects if the
427+
connection is lost.
428+
429+
Returns:
430+
A subclass of ConnectionHandler, which is a UnixConnectionHandler in this case.
431+
"""
432+
433+
uuid = path
434+
435+
factory = SynapseRedisFactory(
436+
hs,
437+
uuid=uuid,
438+
dbid=dbid,
439+
poolsize=1,
440+
isLazy=True,
441+
handler=UnixConnectionHandler,
442+
password=password,
443+
replyTimeout=replyTimeout,
444+
)
445+
factory.continueTrying = reconnect
446+
447+
reactor = hs.get_reactor()
448+
449+
reactor.connectUNIX(
450+
path,
451+
factory,
452+
timeout=30,
453+
checkPID=False,
454+
)
455+
456+
return factory.handler

synapse/server.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -864,22 +864,36 @@ def get_outbound_redis_connection(self) -> "ConnectionHandler":
864864

865865
# We only want to import redis module if we're using it, as we have
866866
# `txredisapi` as an optional dependency.
867-
from synapse.replication.tcp.redis import lazyConnection
867+
from synapse.replication.tcp.redis import lazyConnection, lazyUnixConnection
868868

869-
logger.info(
870-
"Connecting to redis (host=%r port=%r) for external cache",
871-
self.config.redis.redis_host,
872-
self.config.redis.redis_port,
873-
)
869+
if self.config.redis.redis_path is None:
870+
logger.info(
871+
"Connecting to redis (host=%r port=%r) for external cache",
872+
self.config.redis.redis_host,
873+
self.config.redis.redis_port,
874+
)
874875

875-
return lazyConnection(
876-
hs=self,
877-
host=self.config.redis.redis_host,
878-
port=self.config.redis.redis_port,
879-
dbid=self.config.redis.redis_dbid,
880-
password=self.config.redis.redis_password,
881-
reconnect=True,
882-
)
876+
return lazyConnection(
877+
hs=self,
878+
host=self.config.redis.redis_host,
879+
port=self.config.redis.redis_port,
880+
dbid=self.config.redis.redis_dbid,
881+
password=self.config.redis.redis_password,
882+
reconnect=True,
883+
)
884+
else:
885+
logger.info(
886+
"Connecting to redis (path=%r) for external cache",
887+
self.config.redis.redis_path,
888+
)
889+
890+
return lazyUnixConnection(
891+
hs=self,
892+
path=self.config.redis.redis_path,
893+
dbid=self.config.redis.redis_dbid,
894+
password=self.config.redis.redis_password,
895+
reconnect=True,
896+
)
883897

884898
def should_send_federation(self) -> bool:
885899
"Should this server be sending federation traffic directly?"

0 commit comments

Comments
 (0)