Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix regressions introduced when hiding Nursery.start implementation-detail nursery #2845

Conversation

gschaffner
Copy link
Member

proposed alternative fix and tests for #2611. fixes #2844.

note that these tests are parametrized:

For the tests, there are currently enough separate cases that I can imagine missing that something is backwards. Could we write something parameterized, such that it obviously does each possible set of operations, and then the asserts only depend on whether we're in strict mode? (I expect this to be harder to read individually, but the confidence in consistency is what I'm after) --- #2826 (comment)

@gschaffner gschaffner force-pushed the fix-start-nursery-implementation-detail-hiding branch from 05f230a to 926edbb Compare October 31, 2023 09:41
@codecov
Copy link

codecov bot commented Oct 31, 2023

Codecov Report

Merging #2845 (588558c) into master (6c50dc2) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2845   +/-   ##
=======================================
  Coverage   99.18%   99.18%           
=======================================
  Files         115      115           
  Lines       17594    17596    +2     
  Branches     3142     3143    +1     
=======================================
+ Hits        17451    17453    +2     
  Misses         99       99           
  Partials       44       44           
Files Coverage Δ
trio/_core/_run.py 100.00% <100.00%> (ø)
trio/_core/_tests/test_run.py 100.00% <100.00%> (ø)

@gschaffner
Copy link
Member Author

note: if having start internally depend on strict_exception_groups=False is unwanted, an alternative patch could be

click to expand
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index e07d56d6..c2928c87 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -1103,13 +1103,6 @@ class Nursery(metaclass=NoPublicConstructor):

         popped = self._parent_task._child_nurseries.pop()
         assert popped is self
-
-        # don't unnecessarily wrap an exceptiongroup in another exceptiongroup
-        # see https://github.com/python-trio/trio/issues/2611
-        if len(self._pending_excs) == 1 and isinstance(
-            self._pending_excs[0], BaseExceptionGroup
-        ):
-            return self._pending_excs[0]
         if self._pending_excs:
             try:
                 return MultiError(
@@ -1218,15 +1211,25 @@ class Nursery(metaclass=NoPublicConstructor):
             raise RuntimeError("Nursery is closed to new arrivals")
         try:
             self._pending_starts += 1
-            async with open_nursery() as old_nursery:
-                task_status: _TaskStatus[StatusT] = _TaskStatus(old_nursery, self)
-                thunk = functools.partial(async_fn, task_status=task_status)
-                task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
-                    thunk, args, old_nursery, name
-                )
-                task._eventual_parent_nursery = self
-                # Wait for either TaskStatus.started or an exception to
-                # cancel this nursery:
+            try:
+                async with open_nursery(strict_exception_groups=True) as old_nursery:
+                    task_status: _TaskStatus[StatusT] = _TaskStatus(old_nursery, self)
+                    thunk = functools.partial(async_fn, task_status=task_status)
+                    task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
+                        thunk, args, old_nursery, name
+                    )
+                    task._eventual_parent_nursery = self
+                    # Wait for either TaskStatus.started or an exception to
+                    # cancel this nursery:
+            except BaseExceptionGroup as nursery_exc_grp:
+                if len(nursery_exc_grp.exceptions) == 1:
+                    # Hide the implementation-detail nursery; see #2611.
+                    raise nursery_exc_grp.exceptions[0] from None
+                else:
+                    # Unfortunately multiple tasks can be in the nursery if they
+                    # were injected via current_task().parent_nursery; see
+                    # #1599.
+                    raise
             # If we get here, then the child either got reparented or exited
             # normally. The complicated logic is all in TaskStatus.started().
             # (Any exceptions propagate directly out of the above.)

@gschaffner gschaffner changed the title Fix regressions introduced when fixing Nursery.start implementation-detail nursery hiding Fix regressions introduced when hiding Nursery.start implementation-detail nursery Oct 31, 2023
@gschaffner gschaffner force-pushed the fix-start-nursery-implementation-detail-hiding branch from 926edbb to 8d2f364 Compare October 31, 2023 10:09
Copy link
Member

@jakkdl jakkdl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, and the fix is definitely cleaner in attacking the actual root of the problem.

The test is quite dense and hard to read, one could maybe add some more comments, but I think I managed to grok it in the end.

Thanks for fixing my mistake!

Comment on lines 1 to 4
When a starting function raises before calling :func:`trio.TaskStatus.started`,
:func:`trio.Nursery.start` will no longer wrap the exception in an undocumented
:exc:`ExceptionGroup`. Previously, an :exc:`ExceptionGroup` was added when using
``trio.run(..., strict_exception_groups=True)``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's also good to mention in particular that this led to double-wrapped exceptions if used together with trio.Nursery.start with strict_exception_groups set to True or None.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, i think i'm struggling to phrase it clearly without making the newsfragment quite long. i had changed the newsfragment to avoid mentioning double-wrapping because i felt it would be clearer to focus on what was broken (start doing wrapping) and not go into how that could interact with other things working correctly (open_nursery(strict=True).aexit doing wrapping).

i've just changed part of the newsfragment to make clear that the thing that was doing the bad wrapping (and the only thing that has changed) was start—not nursery exit. what do you think of this new phrasing?

Copy link
Member Author

@gschaffner gschaffner Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently my attempt at mentioning that this could lead to double-wrapped exceptions is

diff --git a/newsfragments/2611.bugfix.rst b/newsfragments/2611.bugfix.rst
index a39faa2d..b259210a 100644
--- a/newsfragments/2611.bugfix.rst
+++ b/newsfragments/2611.bugfix.rst
@@ -2,4 +2,11 @@ When a starting function raises before calling :func:`trio.TaskStatus.started`,
 :func:`trio.Nursery.start` will no longer wrap the exception in an undocumented
 :exc:`ExceptionGroup`. Previously, :func:`trio.Nursery.start` would incorrectly
 raise an :exc:`ExceptionGroup` containing it when using ``trio.run(...,
-strict_exception_groups=True)``.
+strict_exception_groups=True)``. (Note that the behavior of nursery exit has not
+changed, i.e. a nursery that sets/inherits `strict_exception_groups=True` will
+still wrap all exception(s) in an :exc:`ExceptionGroup`. The difference is that
+:func:`trio.Nursery.start` will now raise the pre-``started()`` exception
+instead of raising an :exc:`ExceptionGroup` containing it, i.e. the
+pre-``started()`` exception will no longer get incorrectly double-wrapped by
+both :func:`trio.Nursery.start` (incorrectly) and the strict nursery exit
+(correctly).)

but i am not super happy about its phrasing/length

@jakkdl jakkdl requested review from Zac-HD and A5rocks October 31, 2023 11:58
Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great to me - thanks so much @gschaffner!

@gschaffner gschaffner force-pushed the fix-start-nursery-implementation-detail-hiding branch from 14faacc to 588558c Compare November 1, 2023 06:20
@Zac-HD Zac-HD merged commit 3b6b167 into python-trio:master Nov 1, 2023
27 checks passed
@gschaffner gschaffner deleted the fix-start-nursery-implementation-detail-hiding branch November 2, 2023 01:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

(Regression on master) strict_exception_groups is not respected
3 participants