|
18 | 18 |
|
19 | 19 | from ._abc import ReceiveChannel, ReceiveType, SendChannel, SendType, T
|
20 | 20 | from ._core import Abort, RaiseCancelT, Task, enable_ki_protection
|
21 |
| -from ._util import NoPublicConstructor, final, generic_function |
| 21 | +from ._util import ( |
| 22 | + NoPublicConstructor, |
| 23 | + final, |
| 24 | + generic_function, |
| 25 | + raise_single_exception_from_group, |
| 26 | +) |
| 27 | + |
| 28 | +if sys.version_info < (3, 11): |
| 29 | + from exceptiongroup import BaseExceptionGroup |
22 | 30 |
|
23 | 31 | if TYPE_CHECKING:
|
24 | 32 | from types import TracebackType
|
@@ -534,19 +542,29 @@ async def context_manager(
|
534 | 542 | *args: P.args, **kwargs: P.kwargs
|
535 | 543 | ) -> AsyncGenerator[trio._channel.RecvChanWrapper[T], None]:
|
536 | 544 | send_chan, recv_chan = trio.open_memory_channel[T](0)
|
537 |
| - async with trio.open_nursery(strict_exception_groups=True) as nursery: |
538 |
| - agen = fn(*args, **kwargs) |
539 |
| - send_semaphore = trio.Semaphore(0) |
540 |
| - # `nursery.start` to make sure that we will clean up send_chan & agen |
541 |
| - # If this errors we don't close `recv_chan`, but the caller |
542 |
| - # never gets access to it, so that's not a problem. |
543 |
| - await nursery.start(_move_elems_to_channel, agen, send_chan, send_semaphore) |
544 |
| - # `async with recv_chan` could eat exceptions, so use sync cm |
545 |
| - with RecvChanWrapper(recv_chan, send_semaphore) as wrapped_recv_chan: |
546 |
| - yield wrapped_recv_chan |
547 |
| - # User has exited context manager, cancel to immediately close the |
548 |
| - # abandoned generator if it's still alive. |
549 |
| - nursery.cancel_scope.cancel() |
| 545 | + try: |
| 546 | + async with trio.open_nursery(strict_exception_groups=True) as nursery: |
| 547 | + agen = fn(*args, **kwargs) |
| 548 | + send_semaphore = trio.Semaphore(0) |
| 549 | + # `nursery.start` to make sure that we will clean up send_chan & agen |
| 550 | + # If this errors we don't close `recv_chan`, but the caller |
| 551 | + # never gets access to it, so that's not a problem. |
| 552 | + await nursery.start( |
| 553 | + _move_elems_to_channel, agen, send_chan, send_semaphore |
| 554 | + ) |
| 555 | + # `async with recv_chan` could eat exceptions, so use sync cm |
| 556 | + with RecvChanWrapper(recv_chan, send_semaphore) as wrapped_recv_chan: |
| 557 | + yield wrapped_recv_chan |
| 558 | + # User has exited context manager, cancel to immediately close the |
| 559 | + # abandoned generator if it's still alive. |
| 560 | + nursery.cancel_scope.cancel() |
| 561 | + except BaseExceptionGroup as eg: |
| 562 | + try: |
| 563 | + raise_single_exception_from_group(eg) |
| 564 | + except AssertionError: |
| 565 | + raise RuntimeError( |
| 566 | + "Encountered exception during cleanup of generator object, as well as exception in the contextmanager body" |
| 567 | + ) from eg |
550 | 568 |
|
551 | 569 | async def _move_elems_to_channel(
|
552 | 570 | agen: AsyncGenerator[T, None],
|
|
0 commit comments