Spaces:
Running
Running
File size: 6,330 Bytes
0fe2742 e12a676 0fe2742 e12a676 0fe2742 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | """下载准备、响应交付与应用关闭共用的有界归属。"""
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
|