Source code for mersal.outbox.outbox_forwarder

from mersal.logging import Logger
from mersal.messages.transport_message import TransportMessage
from mersal.outbox.outbox_message_batch import OutboxMessageBatch
from mersal.outbox.outbox_storage import OutboxStorage
from mersal.threading.periodic_async_task_factory import PeriodicAsyncTaskFactory
from mersal.transport import TransactionContext, TransactionScope, Transport
from mersal.utils import AsyncRetrier

__all__ = ("OutboxForwarder",)


[docs] class OutboxForwarder: """Send messages stored in the outbox. The class checks the outbox at a fixed period, when it finds any outbox messages, it sends them with a retry mechanism in the case of failure """
[docs] def __init__( self, periodic_task_factory: PeriodicAsyncTaskFactory, transport: Transport, outbox_storage: OutboxStorage, logger: Logger, forwarding_period: float = 1, ) -> None: """Initialize ``OutboxForwader``. Args: periodic_task_factory: Creates an instance of :class:`PeriodicAsyncTask <.threading.PeriodicAsyncTask>`. The instance is responsible for running the periodic query & send. transport: The relevant :class:`Transport <.transport.Transport>`. outbox_storage: A storage for outbox messages that implements :class:`OutboxStorage <.outbox.OutboxStorage>`. logger: Logger instance. forwarding_period: Period for rechecking the outbox storage (in seconds). Default to 1 second. """ self.transport = transport self.outbox_storage = outbox_storage self.forwader = periodic_task_factory.__call__("Outbox-Forwader", self._run, forwarding_period) _delays = [0.1, 0.1, 0.1, 0.1, 0.1, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1, 1] self._retrier = AsyncRetrier(_delays) self._logger = logger
async def start(self) -> None: await self.forwader.start() async def stop(self) -> None: await self.forwader.stop() async def _run(self) -> None: # async with create_task_group() as tg: await self._task() async def _task(self) -> None: batch = await self.outbox_storage.get_next_message_batch() if not len(batch): self._logger.debug("outbox.batch.empty") await batch.close() return await self._process_batch(batch) await batch.complete() await batch.close() async def _process_batch(self, batch: OutboxMessageBatch) -> None: async with TransactionScope() as scope: transaction_context = scope.transaction_context await self._send_messages(batch, transaction_context) await scope.complete() async def _send_messages(self, batch: OutboxMessageBatch, transaction_context: TransactionContext) -> None: self._logger.debug("outbox.batch.send", batch_size=len(batch)) for message in batch: destination_address = message.destination_address transport_message = message.transport_message() async def action( destination_address: str = destination_address, transport_message: TransportMessage = transport_message, ) -> None: await self.transport.send( destination_address=destination_address, message=transport_message, transaction_context=transaction_context, ) await self._retrier.run(action) self._logger.debug("outbox.batch.sent", batch_size=len(batch))