fix(wecom): guard flush task against cancel-delivery race to prevent message loss
When asyncio.sleep() fires just before Task.cancel() is called, CPython sets _must_cancel=True but cannot cancel the already-completed sleep future, so CancelledError is delivered at the next await (handle_message) rather than at the sleep. By that point the superseded task has already popped the merged event from _pending_text_batches, so the superseding task sees an empty batch and silently drops the message. Fix: add a synchronous task-registry check between the sleep and the pop. No await between the check and the pop means no other coroutine can interleave, so the guard is race-free.
This commit is contained in:
@@ -616,6 +616,18 @@ class WeComAdapter(BasePlatformAdapter):
|
||||
else:
|
||||
delay = self._text_batch_delay_seconds
|
||||
await asyncio.sleep(delay)
|
||||
# Guard against the cancel-delivery race: when the sleep timer
|
||||
# fires just before cancel() is called, CPython sets
|
||||
# Task._must_cancel but cannot cancel the already-done sleep
|
||||
# future, so CancelledError is delivered at the *next* await
|
||||
# (handle_message) rather than here. By that point this task
|
||||
# has already popped the merged event, so the superseding task
|
||||
# sees an empty batch and silently drops the message.
|
||||
# This check is synchronous — no await between the sleep and
|
||||
# the pop — so no other coroutine can modify the task registry
|
||||
# in between.
|
||||
if self._pending_text_batch_tasks.get(key) is not current_task:
|
||||
return
|
||||
event = self._pending_text_batches.pop(key, None)
|
||||
if not event:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user