How does tokio::task::spawn
(and its variants) avoid an UnwindSafe
bound on the supplied future? Is there some mechanism besides std::panic::catch_unwind
that the poller uses?
Just like how this is "incorrect" for std::thread::spawn
, it is also "incorrect" for tokio::spawn
to lack these. However, the traits are safe, so it's not a big deal.
Note that you don't need UnwindSafe
to actually catch the panics - unwind safety isn't about the ability to catch the panics, but about what you can safely assume about anything shared between the code that panicked and the code catching the panic.
The point of UnwindSafe
is to tell you that this type's invariants are never broken by a panic, and thus it's safe to inspect a value of that type after a panic has been caught. Because tokio::task::spawn
does not inspect the contents of the future after a panic has been caught, it doesn't need the future to be UnwindSafe
. It does need other things (such as the implementation details of JoinHandle
) to be UnwindSafe
, since it will touch them after a panic, but Tokio is written so that those things are UnwindSafe
.
Couldn't you create a future that owns a Rc<RefCell<T>>
, spawn it locally, have it panic, and then later observe the RefCell
contents? Is UnwindSafe
just an advisory tag?
It's safe to implement and there's even a provided (and safe) way to ignore it, so yes. It's meant to serve as a lint for logic errors. If it's possible to create UB by ignoring it, that's still the fault of some unsafe
somewhere, and not of the programmer ignoring it.
You can, and as it happens, both Rc
and RefCell
are UnwindSafe
if T
is both RefUnwindSafe
and UnwindSafe
.
This is the only reason UnwindSafe
exists - it allows you to opt-in to error E0277 where you think that there's a strong chance that a parameter not being unwind safe is a logic error. catch_unwind
uses it, because it expects that anything modified in the closure you pass it will also be referenced after any unwind, and this should be caught.
Doesn't Rc<S>: UnwindSafe
require S: RefUnwindSafe
(which RefCell<T>
doesn't satisfy)?
What is different about catch_unwind
vs spawning a task or thread? Just that you need to opt out of unwind safety rather than in? Or is there something special about unwinding a thread?
Good point - I misread the docs for RefCell
.
So, the only way to not be unwind safe is to share something that is not unwind safe with your environment - everything that's internal to you is irrelevant (since it's all destroyed during unwinding anyway) - and the presumption is different between spawn
and catch_unwind
.
With catch_unwind
, if the closure shares something with the environment outside catch_unwind
that's not unwind safe, it's quite likely that it does so because you're going to mutate it from inside the closure and then inspect it later - the authors of catch_unwind
want you to know that you're doing something that probably won't work the way you want it to.
With spawn
, the normal case is that you're not going to inspect anything that you've passed into the closure - chances are that if it is shared with the environment, it's either unwind safe (e.g. Mutex<_>
), or it's just an artefact of how closures capture things by default, and you're not going to look at it after handing it off. In this case, the E0277 error because your argument to spawn
is not unwind safe is almost always going to be a false positive, and rather than forcing you to use AssertUnwindSafe
all the time, it's easier to not have the bound to begin with.
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.