Files

91 lines
3.1 KiB
Python

"""Sequential execution queue — one browser, one tab at a time.
Every platform flow drives abang's real browser window. Two flows at once
would fight over focus, file inputs and dialogs, so everything funnels through
one worker task here.
q = PostQueue(inter_platform_delay=(3.0, 6.0))
await q.start()
result = await q.submit("threads", coro_factory)
`submit` returns that job's result (or raises what the job raised). Jobs run
strictly in submission order.
"""
import asyncio
import random
import time
class PostQueue:
def __init__(self, inter_platform_delay=(3.0, 6.0)):
lo, hi = inter_platform_delay
if lo < 0 or hi < lo:
raise ValueError(
f"inter_platform_delay must be (lo, hi) with 0 <= lo <= hi, got {lo, hi}"
)
self.delay_lo, self.delay_hi = lo, hi
self._queue = asyncio.Queue()
self._worker = None
self._last_finished = None
self.history = [] # (iso_time, label, status)
async def start(self):
if self._worker is None:
self._worker = asyncio.create_task(self._run())
return self
async def stop(self):
if self._worker:
self._worker.cancel()
try:
await self._worker
except asyncio.CancelledError:
pass
self._worker = None
@property
def depth(self):
return self._queue.qsize()
async def submit(self, label, factory):
"""Queue `factory()` (a zero-arg coroutine function) and await its result."""
if self._worker is None:
raise RuntimeError("PostQueue.submit() before start()")
future = asyncio.get_running_loop().create_future()
await self._queue.put((label, factory, future))
return await future
async def _run(self):
while True:
label, factory, future = await self._queue.get()
await self._space_out()
try:
result = await factory()
if not future.done():
future.set_result(result)
status = result.get("status") if isinstance(result, dict) else "ok"
except asyncio.CancelledError:
if not future.done():
future.cancel()
raise
except Exception as exc: # noqa: BLE001 — hand it to the caller intact
if not future.done():
future.set_exception(exc)
status = f"raised {type(exc).__name__}"
finally:
self._last_finished = time.time()
self._queue.task_done()
self.history.append(
(time.strftime("%Y-%m-%dT%H:%M:%S"), label, status)
)
async def _space_out(self):
"""Rate-limit gap between consecutive platform jobs."""
if self._last_finished is None:
return
gap = random.uniform(self.delay_lo, self.delay_hi)
elapsed = time.time() - self._last_finished
if elapsed < gap:
await asyncio.sleep(gap - elapsed)