Spaces:
Running
Running
Download request_queue.py from Cashel/diffusion-chatbot: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/Cashel/diffusion-chatbot/resolve/main/request_queue.py
- Command line
-
hf download hf://spaces/Cashel/diffusion-chatbot/request_queue.py
-
curl -L -o request_queue.py https://huggingface.co/spaces/Cashel/diffusion-chatbot/resolve/main/request_queue.py
10.9 kB
| """A bounded, single-worker inference queue with cancellable visitor leases.""" | |
| import logging | |
| import threading | |
| import time | |
| from collections import OrderedDict, deque | |
| from dataclasses import dataclass, field | |
| class AdmissionError(Exception): | |
| def __init__(self, message, status=429, code="queue_full"): | |
| super().__init__(message) | |
| self.status = status | |
| self.code = code | |
| class RequestCancelled(Exception): | |
| pass | |
| class Ticket: | |
| request_id: str | |
| payload: dict | |
| created: float | |
| lease_until: float | None | |
| is_disconnected: object = None | |
| events: deque = field(default_factory=deque) | |
| status: str = "waiting" | |
| cancelled: bool = False | |
| done: bool = False | |
| class InferenceQueue: | |
| def __init__(self, runner, inference_lock=None, capacity=2, lease_seconds=12, | |
| max_wait=180, buffer_size=8, clock=time.monotonic, autostart=True): | |
| self.runner = runner | |
| self.inference_lock = inference_lock or threading.Lock() | |
| self.capacity = capacity | |
| self.lease_seconds = lease_seconds | |
| self.max_wait = max_wait | |
| self.buffer_size = buffer_size | |
| self.clock = clock | |
| self._condition = threading.Condition() | |
| self._tickets = {} | |
| self._waiting = deque() | |
| self._active = None | |
| self._legacy = False | |
| self._closed = False | |
| self._cancelled_ids = OrderedDict() | |
| self._thread = None | |
| self._autostart = autostart | |
| def _start_locked(self): | |
| if self._autostart and self._thread is None: | |
| self._thread = threading.Thread(target=self._work, name="marvin-inference", daemon=True) | |
| self._thread.start() | |
| def _remember_cancelled_locked(self, request_id): | |
| self._cancelled_ids[request_id] = self.clock() + 30 | |
| self._cancelled_ids.move_to_end(request_id) | |
| while len(self._cancelled_ids) > 1024: | |
| self._cancelled_ids.popitem(last=False) | |
| def _positions_locked(self): | |
| for position, ticket in enumerate(self._waiting, 1): | |
| # Positions can change faster than a slow client reads. Only the | |
| # current position matters, and these updates must stay bounded. | |
| ticket.events = deque(event for event in ticket.events if event.get("type") != "queued") | |
| ticket.events.append({"type": "queued", "position": position}) | |
| self._condition.notify_all() | |
| def _cancel_locked(self, ticket, message="Request cancelled.", code="cancelled"): | |
| if ticket.done or ticket.cancelled: | |
| return | |
| ticket.cancelled = True | |
| ticket.events.clear() | |
| ticket.events.append({"type": "error", "error": message, "code": code}) | |
| self._remember_cancelled_locked(ticket.request_id) | |
| if ticket in self._waiting: | |
| self._waiting.remove(ticket) | |
| ticket.done = True | |
| self._positions_locked() | |
| self._condition.notify_all() | |
| def _expire_locked(self): | |
| now = self.clock() | |
| for request_id, expiry in list(self._cancelled_ids.items()): | |
| if expiry <= now: | |
| del self._cancelled_ids[request_id] | |
| for ticket in list(self._tickets.values()): | |
| expired = ticket.lease_until is not None and ticket.lease_until <= now | |
| if ticket.done: | |
| if expired or now - ticket.created > self.max_wait + 300: | |
| self._tickets.pop(ticket.request_id, None) | |
| continue | |
| if ticket.is_disconnected is not None and ticket.is_disconnected(): | |
| self._cancel_locked(ticket) | |
| elif expired: | |
| self._cancel_locked(ticket, "Your turn ended because you left Marvin. Come back and try again.", "lease_expired") | |
| elif ticket.status == "waiting" and now - ticket.created >= self.max_wait: | |
| self._cancel_locked(ticket, "Marvin's taking a while. Please try again in a moment.", "queue_expired") | |
| def submit(self, request_id, payload, leased=True, allow_wait=True, is_disconnected=None): | |
| with self._condition: | |
| self._expire_locked() | |
| if self._closed: | |
| raise AdmissionError("Marvin is restarting. Give him a moment.", 503, "unavailable") | |
| if request_id in self._cancelled_ids: | |
| raise AdmissionError("This request was cancelled. Send a new message when you're ready.", 409, "cancelled") | |
| if request_id in self._tickets: | |
| raise AdmissionError("That request is already with Marvin.", 409, "duplicate_request") | |
| occupied = self._active is not None or self._legacy | |
| if occupied and (not allow_wait or len(self._waiting) >= self.capacity): | |
| raise AdmissionError("Marvin's got a little crowd. Try again shortly.") | |
| now = self.clock() | |
| ticket = Ticket(request_id, payload, now, now + self.lease_seconds if leased else None, is_disconnected) | |
| self._tickets[request_id] = ticket | |
| if occupied: | |
| self._waiting.append(ticket) | |
| self._positions_locked() | |
| else: | |
| ticket.status = "starting" | |
| self._active = ticket | |
| self._start_locked() | |
| self._condition.notify_all() | |
| return ticket | |
| def keepalive(self, request_id): | |
| with self._condition: | |
| self._expire_locked() | |
| ticket = self._tickets.get(request_id) | |
| if ticket is None or ticket.cancelled or ticket.done: | |
| return False | |
| if ticket.lease_until is not None: | |
| ticket.lease_until = self.clock() + self.lease_seconds | |
| return True | |
| def cancel(self, request_id): | |
| with self._condition: | |
| # A pagehide beacon can overtake the original POST on the network. | |
| # Remember it even before admission, so that POST cannot start work. | |
| self._remember_cancelled_locked(request_id) | |
| ticket = self._tickets.get(request_id) | |
| if ticket is not None: | |
| self._cancel_locked(ticket) | |
| self._condition.notify_all() | |
| def checkpoint(self, ticket): | |
| with self._condition: | |
| self._expire_locked() | |
| if ticket.cancelled or ticket.done or self._closed: | |
| raise RequestCancelled() | |
| def emit(self, ticket, event): | |
| with self._condition: | |
| stalled_at = self.clock() | |
| while len(ticket.events) >= self.buffer_size: | |
| self.checkpoint(ticket) | |
| if self.clock() - stalled_at >= self.lease_seconds: | |
| self._cancel_locked(ticket, "The connection to Marvin stopped. Please try again.", "connection_stalled") | |
| raise RequestCancelled() | |
| self._condition.wait(0.25) | |
| self.checkpoint(ticket) | |
| ticket.events.append(event) | |
| self._condition.notify_all() | |
| def events(self, ticket): | |
| try: | |
| while True: | |
| with self._condition: | |
| self._expire_locked() | |
| if not ticket.events and not ticket.done and not ticket.cancelled: | |
| self._condition.wait(2) | |
| self._expire_locked() | |
| if ticket.events: | |
| event = ticket.events.popleft() | |
| self._condition.notify_all() | |
| elif ticket.done or ticket.cancelled: | |
| break | |
| else: | |
| event = None # An SSE heartbeat notices broken sockets. | |
| yield event | |
| finally: | |
| self.cancel(ticket.request_id) | |
| with self._condition: | |
| if ticket.done: | |
| self._tickets.pop(ticket.request_id, None) | |
| def _promote_locked(self): | |
| self._expire_locked() | |
| if self._active is None and not self._legacy and self._waiting: | |
| self._active = self._waiting.popleft() | |
| self._active.events.clear() | |
| self._active.status = "starting" | |
| self._positions_locked() | |
| def run_once(self): | |
| """Run one reserved job; also permits deterministic tests without threads.""" | |
| with self._condition: | |
| self._expire_locked() | |
| ticket = self._active | |
| if ticket is None or ticket.status != "starting": | |
| return False | |
| ticket.status = "running" | |
| locked = False | |
| try: | |
| self.checkpoint(ticket) | |
| locked = self.inference_lock.acquire(blocking=False) | |
| if not locked: | |
| raise RuntimeError("Inference admission lost its exclusive lock") | |
| self.runner(ticket, self) | |
| except RequestCancelled: | |
| pass | |
| except Exception: | |
| logging.exception("Diffusion generation failed") | |
| try: | |
| self.emit(ticket, {"type": "error", "error": "Generation failed. Please try again."}) | |
| except RequestCancelled: | |
| pass | |
| finally: | |
| if locked: | |
| self.inference_lock.release() | |
| with self._condition: | |
| ticket.done = True | |
| ticket.payload = None | |
| self._active = None | |
| self._promote_locked() | |
| self._condition.notify_all() | |
| return True | |
| def _work(self): | |
| while True: | |
| with self._condition: | |
| self._expire_locked() | |
| if self._closed: | |
| return | |
| if self._active is None: | |
| self._condition.wait(0.5) | |
| continue | |
| self.run_once() | |
| def acquire_legacy(self): | |
| with self._condition: | |
| self._expire_locked() | |
| if self._closed or self._active is not None or self._waiting or self._legacy: | |
| return False | |
| if not self.inference_lock.acquire(blocking=False): | |
| return False | |
| self._legacy = True | |
| return True | |
| def release_legacy(self): | |
| with self._condition: | |
| self.inference_lock.release() | |
| self._legacy = False | |
| self._promote_locked() | |
| self._condition.notify_all() | |
| def snapshot(self): | |
| with self._condition: | |
| self._expire_locked() | |
| return {"active": self._active is not None or self._legacy, | |
| "waiting": len(self._waiting), "capacity": self.capacity, | |
| "lease_seconds": self.lease_seconds} | |
| def close(self): | |
| with self._condition: | |
| self._closed = True | |
| for ticket in list(self._tickets.values()): | |
| self._cancel_locked(ticket) | |
| self._condition.notify_all() | |
| if self._thread: | |
| self._thread.join(timeout=5) | |