-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit allows the engine to track and run subscribers, to consume messages as they arrive on the broker channel.
- Loading branch information
Showing
11 changed files
with
442 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import asyncio | ||
import logging | ||
from typing import Type | ||
|
||
from protean.core.subscriber import BaseSubscriber | ||
from protean.port.broker import BaseBroker | ||
|
||
logging.basicConfig( | ||
level=logging.INFO, | ||
format="%(asctime)s,%(msecs)d %(levelname)s: %(message)s", | ||
datefmt="%H:%M:%S", | ||
) | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class BrokerSubscription: | ||
""" | ||
Represents a subscription to a broker channel. | ||
A broker subscription allows a subscriber to receive and process messages from a specific channel. | ||
It provides methods to start and stop the subscription, as well as process messages in batches. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
engine, | ||
broker, | ||
subscriber_id: str, | ||
channel: str, | ||
handler: Type[BaseSubscriber], | ||
messages_per_tick: int = 10, | ||
tick_interval: int = 1, | ||
) -> None: | ||
""" | ||
Initialize the Subscription object. | ||
Args: | ||
engine: The Protean engine instance. | ||
subscriber_id (str): The unique identifier for the subscriber. | ||
channel (str): The name of the stream to subscribe to. | ||
handler (Union[BaseEventHandler, BaseCommandHandler]): The event or command handler. | ||
messages_per_tick (int, optional): The number of messages to process per tick. Defaults to 10. | ||
tick_interval (int, optional): The interval between ticks. Defaults to 1. | ||
""" | ||
self.engine = engine | ||
self.broker: BaseBroker = broker | ||
self.loop = engine.loop | ||
|
||
self.subscriber_id = subscriber_id | ||
self.channel = channel | ||
self.handler = handler | ||
self.messages_per_tick = messages_per_tick | ||
self.tick_interval = tick_interval | ||
|
||
self.keep_going = True # Initially set to keep going | ||
|
||
async def start(self) -> None: | ||
""" | ||
Start the subscription. | ||
This method initializes the subscription by loading the last position from the event store | ||
and starting the polling loop. | ||
Returns: | ||
None | ||
""" | ||
logger.debug(f"Starting {self.subscriber_id}") | ||
|
||
# Start the polling loop | ||
self.loop.create_task(self.poll()) | ||
|
||
async def poll(self) -> None: | ||
""" | ||
Polling loop for processing messages. | ||
This method continuously polls for new messages and processes them by calling the `tick` method. | ||
It sleeps for the specified `tick_interval` between each tick. | ||
Returns: | ||
None | ||
""" | ||
await self.tick() | ||
|
||
if self.keep_going and not self.engine.shutting_down: | ||
# Keep control of the loop if in test mode | ||
# Otherwise `asyncio.sleep` will give away control and | ||
# the loop will be able to be stopped with `shutdown()` | ||
if not self.engine.test_mode: | ||
await asyncio.sleep(self.tick_interval) | ||
|
||
self.loop.create_task(self.poll()) | ||
|
||
async def tick(self): | ||
""" | ||
This method retrieves the next batch of messages to process and calls the `process_batch` method | ||
to handle each message. It also updates the read position after processing each message. | ||
Returns: | ||
None | ||
""" | ||
messages = await self.get_next_batch_of_messages() | ||
if messages: | ||
await self.process_batch(messages) | ||
|
||
async def shutdown(self): | ||
""" | ||
Shutdown the subscription. | ||
This method signals the subscription to stop polling and updates the current position to the store. | ||
It also logs a message indicating the shutdown of the subscription. | ||
Returns: | ||
None | ||
""" | ||
self.keep_going = False # Signal to stop polling | ||
logger.info(f"Shutting down subscription {self.subscriber_id}") | ||
|
||
async def get_next_batch_of_messages(self): | ||
""" | ||
Get the next batch of messages to process. | ||
This method reads messages from the event store starting from the current position + 1. | ||
It retrieves a specified number of messages per tick and applies filtering based on the origin stream name. | ||
Returns: | ||
List[Message]: The next batch of messages to process. | ||
""" | ||
messages = self.broker.read( | ||
self.channel, | ||
no_of_messages=self.messages_per_tick, | ||
) # FIXME Implement filtering | ||
|
||
return messages | ||
|
||
async def process_batch(self, messages: list[dict]): | ||
""" | ||
Process a batch of messages. | ||
This method takes a batch of messages and processes each message by calling the `handle_message` method | ||
of the engine. It also updates the read position after processing each message. If an exception occurs | ||
during message processing, it logs the error using the `log_error` method. | ||
Args: | ||
messages (List[Message]): The batch of messages to process. | ||
Returns: | ||
int: The number of messages processed. | ||
""" | ||
logging.debug(f"Processing {len(messages)} messages...") | ||
for message in messages: | ||
await self.engine.handle_broker_message(self.handler, message) | ||
|
||
return len(messages) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.