From f02bb63f0c1d880d57992d923c22aaca0550dd5f Mon Sep 17 00:00:00 2001
From: "Olivier Wilkinson (reivilibre)" <oliverw@matrix.org>
Date: Mon, 24 Jul 2023 14:45:24 +0100
Subject: [PATCH 1/4] Add a module API function to provide `call_later`

---
 synapse/module_api/__init__.py | 37 ++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index 95f780011153..3d21b9a783a3 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -34,6 +34,7 @@
 from typing_extensions import ParamSpec
 
 from twisted.internet import defer
+from twisted.internet.interfaces import IDelayedCall
 from twisted.web.resource import Resource
 
 from synapse.api import errors
@@ -1230,6 +1231,42 @@ def looping_background_call(
                 f,
             )
 
+    def delayed_background_call(
+        self,
+        msec: float,
+        f: Callable,
+        *args: object,
+        desc: Optional[str] = None,
+        **kwargs: object,
+    ) -> IDelayedCall:
+        """Wraps a function as a background process and calls it in a given number of milliseconds.
+
+        Added in Synapse v1.89.0.
+
+        Args:
+            msec: How long to wait before calling, in milliseconds.
+            f: The function to call once. f can be either synchronous or
+                asynchronous, and must follow Synapse's logcontext rules.
+                More info about logcontexts is available at
+                https://matrix-org.github.io/synapse/latest/log_contexts.html
+            *args: Positional arguments to pass to function.
+            desc: The background task's description. Default to the function's name.
+            **kwargs: Keyword arguments to pass to function.
+
+        Returns:
+            IDelayedCall handle from twisted, which allows to cancel the delayed call if desired.
+        """
+
+        if desc is None:
+            desc = f.__name__
+
+        return self._clock.call_later(
+            msec * 0.001,
+            run_as_background_process,
+            desc,
+            lambda: maybe_awaitable(f(*args, **kwargs)),
+        )
+
     async def sleep(self, seconds: float) -> None:
         """Sleeps for the given number of seconds.
 

From 40deff53c8622a3af1a2cbfdb2761d0eb748f8fc Mon Sep 17 00:00:00 2001
From: "Olivier Wilkinson (reivilibre)" <oliverw@matrix.org>
Date: Mon, 24 Jul 2023 14:45:52 +0100
Subject: [PATCH 2/4] Newsfile

Signed-off-by: Olivier Wilkinson (reivilibre) <oliverw@matrix.org>
---
 changelog.d/15993.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/15993.misc

diff --git a/changelog.d/15993.misc b/changelog.d/15993.misc
new file mode 100644
index 000000000000..35ead0515711
--- /dev/null
+++ b/changelog.d/15993.misc
@@ -0,0 +1 @@
+Allow modules to schedule delayed background calls.
\ No newline at end of file

From 8ec1c7a151474e721f68bf335604efd4caddd5a2 Mon Sep 17 00:00:00 2001
From: "Olivier Wilkinson (reivilibre)" <oliverw@matrix.org>
Date: Mon, 7 Aug 2023 14:44:49 +0100
Subject: [PATCH 3/4] Add comments

---
 synapse/module_api/__init__.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index 3d21b9a783a3..bf6f127a6309 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -1241,6 +1241,9 @@ def delayed_background_call(
     ) -> IDelayedCall:
         """Wraps a function as a background process and calls it in a given number of milliseconds.
 
+        The scheduled call is not persistent: if the current Synapse instance is
+        restarted before the call is made, the call will not be made.
+
         Added in Synapse v1.89.0.
 
         Args:
@@ -1261,6 +1264,7 @@ def delayed_background_call(
             desc = f.__name__
 
         return self._clock.call_later(
+            # convert ms to seconds as needed by call_later.
             msec * 0.001,
             run_as_background_process,
             desc,

From 35fa91dfad807e6e8e4ccdc1b2680a203d914996 Mon Sep 17 00:00:00 2001
From: reivilibre <oliverw@matrix.org>
Date: Tue, 8 Aug 2023 10:46:52 +0100
Subject: [PATCH 4/4] Update version number

---
 synapse/module_api/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index 9156b5a055f0..acee1dafd3ae 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -1256,7 +1256,7 @@ def delayed_background_call(
         The scheduled call is not persistent: if the current Synapse instance is
         restarted before the call is made, the call will not be made.
 
-        Added in Synapse v1.89.0.
+        Added in Synapse v1.90.0.
 
         Args:
             msec: How long to wait before calling, in milliseconds.