trustgraph/trustgraph-base/trustgraph/base/consumer.py

182 lines
4.8 KiB
Python
Raw Normal View History

2025-04-16 16:11:18 +01:00
import _pulsar
import asyncio
import time
from .. exceptions import TooManyRequests
2025-04-14 17:51:23 +01:00
class Consumer:
2025-04-14 17:51:23 +01:00
def __init__(
2025-04-16 16:11:18 +01:00
self, taskgroup, flow, client, queue, subscriber, schema,
2025-04-14 22:39:09 +01:00
handler, rate_limit_retry_time = 10,
2025-04-15 16:13:18 +01:00
rate_limit_timeout = 7200, metrics = None,
start_of_messages=False,
2025-04-14 17:51:23 +01:00
):
2025-04-14 17:51:23 +01:00
self.taskgroup = taskgroup
2025-04-16 16:11:18 +01:00
self.flow = flow
2025-04-14 17:51:23 +01:00
self.client = client
self.queue = queue
self.subscriber = subscriber
self.schema = schema
self.handler = handler
2025-04-14 22:39:09 +01:00
self.rate_limit_retry_time = rate_limit_retry_time
self.rate_limit_timeout = rate_limit_timeout
2025-04-15 16:13:18 +01:00
self.start_of_messages = start_of_messages
2025-04-14 17:51:23 +01:00
self.running = True
self.task = None
2025-04-14 22:39:09 +01:00
self.metrics = metrics
2025-04-16 23:48:11 +01:00
self.consumer = None
2025-04-16 16:11:18 +01:00
def __del__(self):
self.running = False
if hasattr(self, "consumer"):
self.consumer.close()
2025-04-15 16:13:18 +01:00
async def stop(self):
self.running = False
2025-04-16 23:48:11 +01:00
await self.task
2025-04-15 16:13:18 +01:00
2025-04-14 17:51:23 +01:00
async def start(self):
2025-04-14 17:51:23 +01:00
self.running = True
2025-04-14 22:39:09 +01:00
# Puts it in the stopped state, the run thread should set running
if self.metrics:
self.metrics.state("stopped")
2025-04-14 09:05:42 +01:00
2025-04-14 22:39:09 +01:00
self.task = self.taskgroup.create_task(self.run())
2025-04-14 09:05:42 +01:00
async def run(self):
2025-04-14 22:39:09 +01:00
if self.metrics:
2025-04-16 23:48:11 +01:00
self.metrics.state("stopped")
while self.running:
try:
print(self.queue, "subscribing...", flush=True)
self.consumer = await asyncio.to_thread(
self.client.subscribe,
queue = self.queue, subscriber = self.subscriber,
schema = self.schema,
start_of_messages = self.start_of_messages
)
except Exception as e:
print("Exception:", e, flush=True)
await asyncio.sleep(2)
continue
print(self.queue, "subscribed", flush=True)
if self.metrics:
self.metrics.state("running")
try:
await self.consume()
if self.metrics:
self.metrics.state("stopped")
except Exception as e:
print("Exception:", e, flush=True)
self.consumer.close()
self.consumer = None
await asyncio.sleep(2)
continue
async def consume(self):
2025-04-14 22:39:09 +01:00
2025-04-14 17:51:23 +01:00
while self.running:
2025-04-14 09:05:42 +01:00
2025-04-16 16:11:18 +01:00
try:
msg = await asyncio.to_thread(
self.consumer.receive,
timeout_millis=2000
)
except _pulsar.Timeout:
continue
except Exception as e:
raise e
2025-04-14 22:39:09 +01:00
expiry = time.time() + self.rate_limit_timeout
# This loop is for retry on rate-limit / resource limits
2025-04-16 23:48:11 +01:00
while self.running:
if time.time() > expiry:
print("Gave up waiting for rate-limit retry", flush=True)
# Message failed to be processed, this causes it to
# be retried
self.consumer.negative_acknowledge(msg)
2025-04-14 22:39:09 +01:00
if self.metrics:
self.metrics.process("error")
# Break out of retry loop, processes next message
break
try:
2025-04-15 16:18:03 +01:00
print("Handle...", flush=True)
2025-04-14 22:39:09 +01:00
if self.metrics:
with self.metrics.record_time():
2025-04-16 16:11:18 +01:00
await self.handler(msg, self, self.flow)
2025-04-14 22:39:09 +01:00
else:
await self.handler(msg, self.consumer)
2025-04-15 16:18:03 +01:00
print("Handled.", flush=True)
# Acknowledge successful processing of the message
self.consumer.acknowledge(msg)
2025-04-14 22:39:09 +01:00
if self.metrics:
self.metrics.process("success")
# Break out of retry loop
break
except TooManyRequests:
print("TooManyRequests: will retry...", flush=True)
2025-04-14 22:39:09 +01:00
if self.metrics:
self.metrics.rate_limit()
# Sleep
2025-04-14 22:39:09 +01:00
await asyncio.sleep(self.rate_limit_retry)
# Contine from retry loop, just causes a reprocessing
continue
2025-04-14 17:51:23 +01:00
except Exception as e:
print("Exception:", e, flush=True)
# Message failed to be processed, this causes it to
# be retried
self.consumer.negative_acknowledge(msg)
2025-04-14 22:39:09 +01:00
if self.metrics:
self.metrics.process("error")
# Break out of retry loop, processes next message
break