Repository: matrix-org/synapse

Issue:
Make dependency on sdnotify optional.
### Description

Since #5732  was merged sdnotify is a hard dependency.
Make this an optional dependency to allow running on systems which don't use systemd.

### Version information

 1.2.1>


Discussion:
My understanding was that sdnotify was a no-op on such systems 
I was hit with an error, so ended up staying on 1.2.1. I'll give it another try tomorrow.
If you know of any flags I should set, that would help :) 

please let us know what error you are seeing.
I think line 75 in synapse/python_dependencies.py should be moved to the conditional_requirements, so we can use the systemd flag (line 95).

https://github.com/matrix-org/synapse/blob/master/synapse/python_dependencies.py#L75
https://github.com/matrix-org/synapse/blob/master/synapse/python_dependencies.py#L93

@richvdh The error I got was missing sdnotify dependency when building. Since it is not on the system. I'm in the process of updating the synapse package for void-linux from 0.99.3 to 1.3.0. 1.2.1 builds and installs no problem.
> @richvdh The error I got was missing sdnotify dependency when building.

building what? what command are you running, and what is the error text?
Hello, Alpine Linux maintainer for synapse and previous original package of synapse for Void Linux.

I'm commenting here since i'm having the same error and i think making a new issue would just be a duplicate.

All tests fail with the following error (the message changes for each test and when running but the error is the same)

```
tests.storage.test_end_to_end_keys
===============================================================================
[ERROR]
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/twisted/trial/runner.py", line 531, in loadPackage
    module = modinfo.load()
  File "/usr/lib/python3.7/site-packages/twisted/python/modules.py", line 392, in load
    return self.pathEntry.pythonPath.moduleLoader(self.name)
  File "/usr/lib/python3.7/site-packages/twisted/python/reflect.py", line 308, in namedAny
    topLevelPackage = _importAndCheckStack(trialname)
  File "/usr/lib/python3.7/site-packages/twisted/python/reflect.py", line 255, in _importAndCheckStack
    reraise(excValue, excTraceback)
  File "/home/builder/aports/testing/synapse/src/synapse-1.3.0/tests/storage/test_event_federation.py", line 18, in <module>
    import tests.unittest
  File "/home/builder/aports/testing/synapse/src/synapse-1.3.0/tests/unittest.py", line 31, in <module>
    from synapse.config.homeserver import HomeServerConfig
  File "/home/builder/aports/testing/synapse/src/synapse-1.3.0/synapse/config/homeserver.py", line 27, in <module>
    from .logger import LoggingConfig
  File "/home/builder/aports/testing/synapse/src/synapse-1.3.0/synapse/config/logger.py", line 27, in <module>
    from synapse.app import _base as appbase
  File "/home/builder/aports/testing/synapse/src/synapse-1.3.0/synapse/app/_base.py", line 23, in <module>
    import sdnotify
builtins.ModuleNotFoundError: No module named 'sdnotify'
```

patching synapse/python_dependencies.py to move sdnotify to CONDITIONAL_REQUIREMENTS doesn't fix this error.

EDIT: reverting https://github.com/matrix-org/synapse/pull/5732 fixes the issue, all tests are passing and will be included on Alpine Linux Edge

Resolving patch:
diff --git a/synapse/app/_base.py b/synapse/app/_base.py
--- a/synapse/app/_base.py
+++ b/synapse/app/_base.py
@@ -17,10 +17,10 @@
 import logging
 import os
 import signal
+import socket
 import sys
 import traceback
 
-import sdnotify
 from daemonize import Daemonize
 
 from twisted.internet import defer, error, reactor
@@ -246,13 +246,12 @@ def start(hs, listeners=None):
             def handle_sighup(*args, **kwargs):
                 # Tell systemd our state, if we're using it. This will silently fail if
                 # we're not using systemd.
-                sd_channel = sdnotify.SystemdNotifier()
-                sd_channel.notify("RELOADING=1")
+                sdnotify(b"RELOADING=1")
 
                 for i in _sighup_callbacks:
                     i(hs)
 
-                sd_channel.notify("READY=1")
+                sdnotify(b"READY=1")
 
             signal.signal(signal.SIGHUP, handle_sighup)
 
@@ -308,16 +307,12 @@ def setup_sdnotify(hs):
 
     # Tell systemd our state, if we're using it. This will silently fail if
     # we're not using systemd.
-    sd_channel = sdnotify.SystemdNotifier()
-
     hs.get_reactor().addSystemEventTrigger(
-        "after",
-        "startup",
-        lambda: sd_channel.notify("READY=1\nMAINPID=%s" % (os.getpid())),
+        "after", "startup", sdnotify, b"READY=1\nMAINPID=%i" % (os.getpid(),)
     )
 
     hs.get_reactor().addSystemEventTrigger(
-        "before", "shutdown", lambda: sd_channel.notify("STOPPING=1")
+        "before", "shutdown", sdnotify, b"STOPPING=1"
     )
 
 
@@ -414,3 +409,35 @@ def addressResolved(self, address):
     def resolutionComplete(self):
         self._deferred.callback(())
         self._receiver.resolutionComplete()
+
+
+sdnotify_sockaddr = os.getenv("NOTIFY_SOCKET")
+
+
+def sdnotify(state):
+    """
+    Send a notification to systemd, if the NOTIFY_SOCKET env var is set.
+
+    This function is based on the sdnotify python package, but since it's only a few
+    lines of code, it's easier to duplicate it here than to add a dependency on a
+    package which many OSes don't include as a matter of principle.
+
+    Args:
+        state (bytes): notification to send
+    """
+    if not isinstance(state, bytes):
+        raise TypeError("sdnotify should be called with a bytes")
+    if not sdnotify_sockaddr:
+        return
+    addr = sdnotify_sockaddr
+    if addr[0] == "@":
+        addr = "\0" + addr[1:]
+
+    try:
+        with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock:
+            sock.connect(addr)
+            sock.sendall(state)
+    except Exception as e:
+        # this is a bit surprising, since we don't expect to have a NOTIFY_SOCKET
+        # unless systemd is expecting us to notify it.
+        logger.warning("Unable to send notification to systemd: %s", e)
diff --git a/synapse/python_dependencies.py b/synapse/python_dependencies.py
--- a/synapse/python_dependencies.py
+++ b/synapse/python_dependencies.py
@@ -72,7 +72,6 @@
     "netaddr>=0.7.18",
     "Jinja2>=2.9",
     "bleach>=1.4.3",
-    "sdnotify>=0.3",
 ]
 
 CONDITIONAL_REQUIREMENTS = {


Test patch: