synapse/tests/util/test_background_queue.py
Erik Johnston 4906771da1
Faster redis replication handling (#19138)
Spawning a background process comes with a bunch of overhead, so let's
try to reduce the number of background processes we need to spawn when
handling inbound fed.

Currently, we seem to be doing roughly one per command. Instead, lets
keep the background process alive for a bit waiting for a new command to
come in.
2025-11-05 13:42:04 +00:00

106 lines
3.7 KiB
Python

#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2025 Element Creations Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
from unittest.mock import Mock
from twisted.internet.defer import Deferred
from twisted.internet.testing import MemoryReactor
from synapse.server import HomeServer
from synapse.util.bacckground_queue import BackgroundQueue
from synapse.util.clock import Clock
from tests.unittest import HomeserverTestCase
class BackgroundQueueTests(HomeserverTestCase):
def prepare(
self, reactor: MemoryReactor, clock: Clock, homeserver: HomeServer
) -> None:
self._process_item_mock = Mock(spec_set=[])
self.queue = BackgroundQueue[int](
hs=homeserver,
name="test_queue",
callback=self._process_item_mock,
timeout_ms=1000,
)
def test_simple_call(self) -> None:
"""Test that items added to the queue are processed."""
# Register a deferred to be the return value of the callback.
callback_result_deferred: Deferred[None] = Deferred()
self._process_item_mock.side_effect = callback_result_deferred
# Adding an item should cause the callback to be invoked.
self.queue.add(1)
self._process_item_mock.assert_called_once_with(1)
self._process_item_mock.reset_mock()
# Adding another item should not cause the callback to be invoked again
# until the previous one has completed.
self.queue.add(2)
self._process_item_mock.assert_not_called()
# Once the first callback completes, the second item should be
# processed.
callback_result_deferred.callback(None)
self._process_item_mock.assert_called_once_with(2)
def test_timeout(self) -> None:
"""Test that the background process wakes up if its idle, and that it
times out after being idle."""
# Register a deferred to be the return value of the callback.
callback_result_deferred: Deferred[None] = Deferred()
self._process_item_mock.side_effect = callback_result_deferred
# Adding an item should cause the callback to be invoked.
self.queue.add(1)
self._process_item_mock.assert_called_once_with(1)
self._process_item_mock.reset_mock()
# Let the callback complete.
callback_result_deferred.callback(None)
# Advance the clock by less than the timeout, and add another item.
self.reactor.advance(0.5)
self.assertIsNotNone(self.queue._wakeup_event)
self.queue.add(2)
# The callback should be invoked again.
callback_result_deferred = Deferred()
self._process_item_mock.side_effect = callback_result_deferred
self._process_item_mock.assert_called_once_with(2)
self._process_item_mock.reset_mock()
# Let the callback complete.
callback_result_deferred.callback(None)
# Advance the clock by more than the timeout.
self.reactor.advance(1.5)
# The background process should have exited, we check this by checking
# the internal wakeup event has been removed.
self.assertIsNone(self.queue._wakeup_event)
# Add another item. This should cause a new background process to be
# started.
self.queue.add(3)
self._process_item_mock.assert_called_once_with(3)