-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
fix: add unwind safety to resource_scope
#22290
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
base: main
Are you sure you want to change the base?
Conversation
| return; | ||
| }; | ||
|
|
||
| // in debug mode, raise a panic if user code re-inserted a resource of this type within the scope. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing this to only panic in debug could be considered a breaking change. We might want to add a migration guide. I could go either way on this as this is a pretty minor change.
| }; | ||
|
|
||
| Some(result) | ||
| was_successful.then_some(result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels weird to me. Why return None here as we already have the result? At the very least the documentation should be updated to say you can get None if f panicks. But given my comment above about the double panic in resource_scope I'm starting to believe that try_resource_scope might need to be changed to return a result instead to differentiate between when the resource doesn't exist vs when f has panicked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the behavior is a tad strange, but it's consistent with how it works on main if the resources get cleared. I'd be down to change that in a separate PR, but my intention here was to not change any behavior for the non-panicking case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the very least the documentation should be updated to say you can get None if f panicks
This isn't quite accurate -- if f panics you'll get !, rather than None. The function never completes or returns a value -- it just attempts to put the resource back into the world as it unwinds
|
Generally onboard with this change. Scoping this to |
ickshonpe
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me and I couldn't find any problems.
Objective
The function
World::resource_scopeis a primitive that allows the user to get mutable access to a resource and the rest of the world at the same time. This is incredibly useful for exclusive systems, implementations ofCommand, and custom abstractions. It works by temporarily removing the resource from the world and running a user-provided closure with mutable access to both. After the closure completes, the resource is re-inserted into the world for other code to use.With the current implementation, any panics originating in the user-provided closure will result in the resource being lost. This is problematic for a couple of reasons:
std::panic::catch_unwindhave to deal with the fact that the panic has left the world in an invalid state.resource_scopecan lead to knock-on errors, which can make debugging more difficult, as the user may need to sort through many errors to identify which one was the root cause.The objective here is to make resource_scope panic-safe, ensuring that the resource is always restored to the world when unwinding from a panic (on platforms that support unwinding;
panic=abortis unrecoverable by design). This enables more robust panic handling, and allows for more graceful shutdown sequences when panics are truly fatal.Solution
Add a "guard" type that captures mutable access to the world and temporary ownership of the resource. This type has a
Dropimplementation that is responsible for re-inserting the resource at the end of the scope -- this pattern is similar totry ... finallyblocks in many languages.Testing
resource_scope.World::clear_resourcesorWorld::clear_all.