[Fix] Serialize FanOutCommunicator queueing calls with a FIFO-fair asyncio.Lock (#30606)
This commit is contained in:
@@ -2,8 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
from collections import deque
|
||||
from typing import Callable, Deque, Generic, List, Optional, TypeVar
|
||||
from typing import Callable, Generic, List, Optional, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -31,31 +30,26 @@ class FanOutCommunicator(Generic[T]):
|
||||
self._mode = mode
|
||||
self._result_event: Optional[asyncio.Event] = None
|
||||
self._result_values: Optional[List[T]] = None
|
||||
self._ready_queue: Deque[asyncio.Event] = deque()
|
||||
self._queueing_lock = asyncio.Lock()
|
||||
|
||||
assert mode in ["queueing", "watching"]
|
||||
|
||||
async def queueing_call(self, obj: T):
|
||||
ready_event = asyncio.Event()
|
||||
if self._result_event is not None or len(self._ready_queue) > 0:
|
||||
self._ready_queue.append(ready_event)
|
||||
await ready_event.wait()
|
||||
assert self._result_event is None
|
||||
assert self._result_values is None
|
||||
# asyncio.Lock is FIFO-fair: a new caller cannot acquire while earlier
|
||||
# callers are still waiting, so requests are strictly serialized in
|
||||
# arrival order. It also releases on exception/cancellation, so a
|
||||
# failed caller never blocks the callers queued behind it.
|
||||
async with self._queueing_lock:
|
||||
if obj is not None:
|
||||
self._send(obj)
|
||||
|
||||
if obj is not None:
|
||||
self._send(obj)
|
||||
self._result_event = asyncio.Event()
|
||||
self._result_values = []
|
||||
await self._result_event.wait()
|
||||
result_values = self._result_values
|
||||
self._result_event = self._result_values = None
|
||||
|
||||
self._result_event = asyncio.Event()
|
||||
self._result_values = []
|
||||
await self._result_event.wait()
|
||||
result_values = self._result_values
|
||||
self._result_event = self._result_values = None
|
||||
|
||||
if len(self._ready_queue) > 0:
|
||||
self._ready_queue.popleft().set()
|
||||
|
||||
return result_values
|
||||
return result_values
|
||||
|
||||
async def watching_call(self, obj):
|
||||
if self._result_event is None:
|
||||
|
||||
Reference in New Issue
Block a user