Spaces:
Running
Running
File size: 5,943 Bytes
5a5bab3 79604c9 5a5bab3 79604c9 | 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 | """等待所属同步工作真实终态;内部取消也不能遗留仍读写资源的线程。"""
import asyncio
from functools import wraps
from inspect import Parameter, signature, iscoroutinefunction, isasyncgenfunction, isgeneratorfunction
import threading
async def run_owned_thread(function, *args, **kwargs):
ownership = threading.Lock()
finished = threading.Event()
started = False
outcome = None
def abandon_unstarted(result):
nonlocal outcome
with ownership:
if not started and not finished.is_set():
outcome = result
finished.set()
def execute():
nonlocal started, outcome
with ownership:
if finished.is_set():
return
started = True
try:
result = (True, function(*args, **kwargs))
except BaseException as error:
# 同步异常作为数据交回;取消shield不暴露私有异常正文。
result = (False, error)
with ownership:
outcome = result
finished.set()
async def finish():
try:
await asyncio.to_thread(execute)
except asyncio.CancelledError:
raise
except BaseException as error:
abandon_unstarted((False, error))
coroutine = finish()
try:
task = asyncio.create_task(coroutine)
except BaseException:
coroutine.close()
raise
def consume(completed):
if completed.cancelled():
# 取消和线程启动竞争时,只有尚未开始的工作能被撤销。
abandon_unstarted((False, asyncio.CancelledError()))
else:
error = completed.exception()
if error is not None:
abandon_unstarted((False, error))
task.add_done_callback(consume)
cancelled = None
while not finished.is_set():
try:
if not task.done():
await asyncio.shield(task)
else:
# 内部task已取消但线程仍在运行,不占线程池等待其终态。
await asyncio.sleep(0.01)
except asyncio.CancelledError as error:
cancelled = error
with ownership:
succeeded, result = outcome
if cancelled is not None:
raise cancelled
if not succeeded:
raise result
return result
class OwnedThreadGroup:
def __init__(self, capacity, unavailable):
if type(capacity) is not int or capacity < 1:
raise ValueError('invalid owned thread capacity')
self._capacity = threading.BoundedSemaphore(capacity)
self._unavailable = unavailable
self._gate = threading.Lock()
self._accepting = True
self._pending = 0
self._closers = 0
self._drained = threading.Event()
self._drained.set()
def start(self):
with self._gate:
if self._pending or self._closers:
raise RuntimeError('owned work is still pending')
self._accepting = True
def stop(self):
with self._gate:
self._accepting = False
async def close(self):
with self._gate:
self._accepting = False
self._closers += 1
try:
cancelled = False
while not self._drained.is_set():
try:
await asyncio.sleep(.01)
except asyncio.CancelledError:
cancelled = True
if cancelled:
raise asyncio.CancelledError
finally:
with self._gate:
self._closers -= 1
async def run(self, function, *args, **kwargs):
with self._gate:
if not self._accepting or not self._capacity.acquire(blocking=False):
raise self._unavailable()
self._pending += 1
self._drained.clear()
try:
return await run_owned_thread(function, *args, **kwargs)
finally:
with self._gate:
self._capacity.release()
self._pending -= 1
if not self._pending:
self._drained.set()
def owned_sync_endpoint(group):
def decorate(endpoint):
if iscoroutinefunction(endpoint) or isasyncgenfunction(endpoint) or isgeneratorfunction(endpoint):
raise TypeError('owned endpoint must be a synchronous function')
@wraps(endpoint)
async def invoke(*args, **kwargs):
return await group.run(endpoint, *args, **kwargs)
return invoke
return decorate
def owned_session_endpoint(group, session_factory, session_parameter):
def decorate(endpoint):
if iscoroutinefunction(endpoint) or isasyncgenfunction(endpoint) or isgeneratorfunction(endpoint):
raise TypeError('owned session endpoint must be a synchronous function')
original = signature(endpoint)
if session_parameter not in original.parameters:
raise ValueError('missing owned session parameter')
if any(parameter.kind not in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
for parameter in original.parameters.values()):
raise TypeError('owned session endpoint requires named parameters')
public = original.replace(parameters=[parameter for name, parameter in original.parameters.items()
if name != session_parameter])
@wraps(endpoint)
async def invoke(*args, **kwargs):
arguments = public.bind(*args, **kwargs).arguments
def execute():
with session_factory() as session:
return endpoint(**arguments, **{session_parameter: session})
return await group.run(execute)
invoke.__signature__ = public
return invoke
return decorate
|