@@ -257,10 +257,10 @@ def spin(self, poll_interval: float = 0.1) -> None:
257257
258258 def spin_once (self , timeout : float | None = 0.0 ) -> bool :
259259 """
260- Process at most one subscription callback .
260+ Process any subscription callbacks ready within the timeout window .
261261
262- :param timeout: Seconds to wait for a callback . Use None to block forever.
263- :return: True if a callback was processed, False otherwise.
262+ :param timeout: Seconds to wait for callbacks . Use None to block forever.
263+ :return: True if any callback was processed, False otherwise.
264264 """
265265 self ._ensure_started ()
266266 if self ._shutdown_requested .is_set ():
@@ -289,31 +289,32 @@ def spin_once(self, timeout: float | None = 0.0) -> bool:
289289 finally :
290290 if not keep_future :
291291 self ._spin_future = None
292- if result is None :
292+ if not result :
293293 return False
294+ processed = False
295+ for entry , cm , msg in result :
296+ _ , callback , zero_copy = entry
294297
295- entry , cm , msg = result
296- _ , callback , zero_copy = entry
297-
298- try :
299- if not zero_copy :
300- msg = deepcopy (msg )
301- callback (msg )
302- except Exception :
303- logger .exception ("Unhandled exception in subscription callback" )
304- finally :
305- exit_fut = asyncio .run_coroutine_threadsafe (
306- cm .__aexit__ (None , None , None ), self ._loop
307- )
308298 try :
309- _future_result (exit_fut , None )
310- except CacheMiss :
311- logger .warning (
312- "Cache miss while releasing message; publisher likely exited."
313- )
299+ if not zero_copy :
300+ msg = deepcopy (msg )
301+ callback (msg )
314302 except Exception :
315- logger .exception ("Failed while releasing message backpressure" )
316- return True
303+ logger .exception ("Unhandled exception in subscription callback" )
304+ finally :
305+ exit_fut = asyncio .run_coroutine_threadsafe (
306+ cm .__aexit__ (None , None , None ), self ._loop
307+ )
308+ try :
309+ _future_result (exit_fut , None )
310+ except CacheMiss :
311+ logger .warning (
312+ "Cache miss while releasing message; publisher likely exited."
313+ )
314+ except Exception :
315+ logger .exception ("Failed while releasing message backpressure" )
316+ processed = True
317+ return processed
317318
318319 def shutdown (self ) -> None :
319320 self ._shutdown_requested .set ()
@@ -368,30 +369,14 @@ def spin(context: SyncContext, poll_interval: float = 0.1) -> None:
368369
369370
370371def spin_once (context : SyncContext , timeout : float | None = 0.0 ) -> bool :
371- """Process at most one subscription callback ."""
372+ """Process any subscription callbacks ready within the timeout window ."""
372373 return context .spin_once (timeout = timeout )
373374
374375
375376async def _recv_any (
376377 entries : Iterable [tuple [SyncSubscriber , Callable [[Any ], None ], bool ]],
377378 timeout : float | None ,
378- ) -> tuple [tuple [SyncSubscriber , Callable [[Any ], None ], bool ], Any , Any ] | None :
379- async def _cleanup_result (result : Any ) -> None :
380- if isinstance (result , BaseException ):
381- return
382- try :
383- _ , cm , _ = result
384- except Exception :
385- return
386- try :
387- await cm .__aexit__ (None , None , None )
388- except CacheMiss :
389- logger .warning (
390- "Cache miss while releasing message; publisher likely exited."
391- )
392- except Exception :
393- logger .exception ("Failed while releasing message backpressure" )
394-
379+ ) -> list [tuple [tuple [SyncSubscriber , Callable [[Any ], None ], bool ], Any , Any ]]:
395380 async def _recv_entry (
396381 entry : tuple [SyncSubscriber , Callable [[Any ], None ], bool ]
397382 ) -> tuple [tuple [SyncSubscriber , Callable [[Any ], None ], bool ], Any , Any ]:
@@ -410,20 +395,9 @@ async def _recv_entry(
410395 done , pending = await asyncio .wait (
411396 tasks , timeout = remaining , return_when = asyncio .FIRST_COMPLETED
412397 )
413- if not done :
414- for task in pending :
415- try :
416- task .cancel ()
417- except RuntimeError :
418- pass
419- pending_results = await asyncio .gather (
420- * pending , return_exceptions = True
421- )
422- for result in pending_results :
423- await _cleanup_result (result )
424- return None
425-
426- winner_result = None
398+ results : list [
399+ tuple [tuple [SyncSubscriber , Callable [[Any ], None ], bool ], Any , Any ]
400+ ] = []
427401 for task in done :
428402 try :
429403 result = task .result ()
@@ -435,28 +409,32 @@ async def _recv_entry(
435409 except Exception :
436410 logger .exception ("Sync subscription receive failed" )
437411 continue
438- if winner_result is None :
439- winner_result = result
440- else :
441- await _cleanup_result (result )
412+ results .append (result )
442413
443414 for task in pending :
444415 try :
445416 task .cancel ()
446417 except RuntimeError :
447418 pass
448- pending_results = await asyncio .gather (
449- * pending , return_exceptions = True
450- )
419+ pending_results = await asyncio .gather (* pending , return_exceptions = True )
451420 for result in pending_results :
452- await _cleanup_result (result )
421+ if isinstance (result , CacheMiss ):
422+ continue
423+ if isinstance (result , asyncio .CancelledError ):
424+ continue
425+ if isinstance (result , BaseException ):
426+ logger .exception (
427+ "Sync subscription receive failed" , exc_info = result
428+ )
429+ continue
430+ results .append (result )
453431
454- if winner_result is not None :
455- return winner_result
432+ if results :
433+ return results
456434
457435 # Only CacheMiss/cancelled/error occurred; continue within timeout window.
458436 if deadline is not None and loop .time () >= deadline :
459- return None
437+ return []
460438 finally :
461439 for task in tasks :
462440 if not task .done ():
0 commit comments