ComfyUI-Ranking-API / proxy_download_work.py
ZHIWEI666's picture
Upload 11 files
e12a676 verified
Raw History Blame Contribute Delete
6.33 kB
"""下载准备、响应交付与应用关闭共用的有界归属。"""
import asyncio
from functools import wraps
from fastapi import HTTPException
from workflow_stream_response import OwnedDelivery
def _spawn(function):
async def execute():
try:
return True, await function()
except BaseException as error:
return False, error
coroutine = execute()
try:
return asyncio.create_task(coroutine)
except BaseException:
coroutine.close()
raise
async def _settle(task, interrupted=None):
cancelled = None
while not task.done():
try:
await asyncio.shield(task)
except asyncio.CancelledError as error:
cancelled = error
if interrupted is not None:
interrupted(task)
outcome = (False, asyncio.CancelledError()) if task.cancelled() else task.result()
return outcome, cancelled
class _Download:
def __init__(self, group):
self.group = group
self.active = None
self.response = None
self.aborting = False
self.released = False
self.sending = False
self.cancelled_tasks = set()
self.cleanup = None
self.finished = asyncio.Event()
def cancel(self, task):
self.aborting = True
if not task.done() and task not in self.cancelled_tasks:
self.cancelled_tasks.add(task)
task.cancel()
def release(self):
self.released = True
self.group._pending.discard(self)
self.finished.set()
async def prepare(self, function, args, kwargs):
result = await function(*args, **kwargs)
if isinstance(result, OwnedDelivery):
result.bind_download(self)
self.response = result
return result
async def abort(self):
self.aborting = True
self.cancel(self.active)
await _settle(self.active, self.cancel)
if self.response is None:
self.release()
return
if self.sending:
# 发送拥有者先完成迭代器和连接关闭,再归还名额。
await _settle(_spawn(self.finished.wait))
return
if self.cleanup is None:
async def close():
try:
await self.response.aclose()
finally:
self.release()
self.cleanup = _spawn(close)
outcome, cancelled = await _settle(self.cleanup)
succeeded, result = outcome
if cancelled is not None:
raise cancelled
if not succeeded:
raise result
async def deliver(self, response, scope, receive, send):
if self.released or self.aborting or self.sending or response is not self.response:
raise asyncio.CancelledError()
self.sending = True
try:
self.active = _spawn(lambda: response.send_owned(scope, receive, send))
outcome, cancelled = await _settle(self.active, self.cancel)
succeeded, result = outcome
if cancelled is not None:
raise cancelled
if not succeeded:
raise result
return result
finally:
try:
# 发送任务在首次执行前被取消时,也必须关闭尚未消费的响应。
await response.aclose()
finally:
self.release()
class ProxyDownloadWork:
def __init__(self, capacity=8):
if type(capacity) is not int or capacity < 1:
raise ValueError('invalid download capacity')
self.capacity = capacity
self._pending = set()
self._accepting = True
self._closing = None
self._closers = 0
def start(self):
if self._pending or self._closers or self._closing is not None and not self._closing.done():
raise RuntimeError('download work is still pending')
if self._closing is not None:
succeeded, result = self._closing.result()
if not succeeded:
raise result
self._closing = None
self._accepting = True
def stop(self):
self._accepting = False
def endpoint(self, function):
@wraps(function)
async def invoke(*args, **kwargs):
return await self.run(function, *args, **kwargs)
return invoke
async def run(self, function, *args, **kwargs):
if not self._accepting or len(self._pending) >= self.capacity:
raise HTTPException(status_code=503, detail='service_unavailable')
owner = _Download(self)
self._pending.add(owner)
handed_off = False
try:
owner.active = _spawn(lambda: owner.prepare(function, args, kwargs))
outcome, cancelled = await _settle(owner.active, owner.cancel)
succeeded, result = outcome
if cancelled is not None or owner.aborting:
raise cancelled or asyncio.CancelledError()
if not succeeded:
raise result
handed_off = owner.response is not None
return result
finally:
if owner.response is None:
owner.release()
elif not handed_off:
await owner.abort()
async def close(self):
self.stop()
self._closers += 1
try:
if self._closing is None:
async def drain():
tasks = [_spawn(owner.abort) for owner in tuple(self._pending)]
results = await asyncio.gather(*tasks)
for succeeded, result in results:
if not succeeded:
raise result
self._closing = _spawn(drain)
outcome, cancelled = await _settle(self._closing)
succeeded, result = outcome
if cancelled is not None:
raise cancelled
if not succeeded:
raise result
finally:
self._closers -= 1
_downloads = ProxyDownloadWork()
proxy_download_endpoint = _downloads.endpoint
start_proxy_downloads = _downloads.start
stop_proxy_downloads = _downloads.stop
close_proxy_downloads = _downloads.close