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

proxy_instance_serial_ws() uses select! with non-cancel-safe future(s) #3356

Closed
jgallagher opened this issue Jun 14, 2023 · 4 comments
Closed
Assignees

Comments

@jgallagher
Copy link
Contributor

jgallagher commented Jun 14, 2023

In proxy_instance_serial_ws(), we construct four futures over which we will tokio::select!. In any given loop iteration, at least two of the futures are instances of Fuse::terminated() (and therefore ignored by select!). The possible combinations of non-Fuse::terminated() futures are:

  1. nexus_read (a StreamExt::next() future) and nexus_write(a SinkExt::send() future)
  2. nexus_read and propolis_read (a future from InstanceSerialConsoleHelper::recv() in propolis)
  3. propolis_write (a future from support::send in propolis which is a wrapper around SinkExt::send()) alone
  4. propolis_write and nexus_write

Case 3 is fine because there is only one possible future for select! to choose, so there is no possibility for cancellation. But in the other three cases, select! will cancel one of the two futures once it chooses one. In terms of cancel safety, none of these futures is explicitly documented; however:

  • I believe (hope?) that StreamExt::next() is cancel safe, because the equivalent method in tokio-stream's StreamExt is explicitly documented as cancel safe.
  • I believe SinkExt::send() is not cancel safe: if select!ing over a SinkExt::send() future and a different arm is chosen, the item being sent to the sink will be lost. (A reproducer; I'll also file an issue on futures-util to confirm / try to get it documented. EDIT: filed Documenting cancel safety of SinkExt::send() rust-lang/futures-rs#2754)
  • I'm not sure whether InstanceSerialConsoleHelper::recv() is cancel safe, but based on it containing multiple .await points I suspect it is not: if an item is pulled out of self.ws_stream and the future is dropped at a later await point, I believe the item will be lost.

I'm not sure at the moment how to address this; I'm just skimming through omicron's select!s looking for suspicious cancel safety issues. CC @lifning

@jgallagher jgallagher changed the title proxy_instance_serial_ws() uses select! with a non-cancel-safe future(s) proxy_instance_serial_ws() uses select! with non-cancel-safe future(s) Jun 14, 2023
@lifning lifning self-assigned this Jun 15, 2023
@sunshowers
Copy link
Contributor

sunshowers commented Jun 16, 2023

I'm not sure whether InstanceSerialConsoleHelper::recv() is cancel safe, but based on it containing multiple .await points I suspect it is not: if an item is pulled out of self.ws_stream and the future is dropped at a later await point, I believe the item will be lost.

Indeed InstanceSerialConsoleHelper::recv wasn't cancel-safe. oxidecomputer/propolis#435 addresses that, and once it lands and omicron is updated to that version, it should be cancel-safe.

@sunshowers
Copy link
Contributor

I believe SinkExt::send() is not cancel safe: if select!ing over a SinkExt::send() future and a different arm is chosen, the item being sent to the sink will be lost. (A reproducer; I'll also file an issue on futures-util to confirm / try to get it documented. EDIT: filed rust-lang/futures-rs#2754)

I think the best solution here is to define a version of SinkExt::send that's cancel-safe. I've outlined a proposal here: rust-lang/futures-rs#2754 (comment)

@sunshowers
Copy link
Contributor

sunshowers commented Jun 21, 2023

Current status:

sunshowers added a commit to oxidecomputer/propolis that referenced this issue Jun 22, 2023
This allows arbitrary Sink combinators, and in particular the
combinators in cancel-safe-futures,
to be used against InstanceSerialConsoleHelper.

This should not be disruptive to most users since the send(item) API removed here matches the futures::SinkExt::send(item) API exactly -- at most, they'll have to import futures::SinkExt.

This is required as part of the fix for oxidecomputer/omicron#3356.
sunshowers added a commit that referenced this issue Jun 27, 2023
Use the cancel-safe adapters defined in
https://github.com/oxidecomputer/cancel-safe-futures to ensure that the
branches of the select loop are all cancel-safe.

See #3356 for a detailed description of what's going on here.
@sunshowers
Copy link
Contributor

The remaining uses have been tackled in #3411. We should be good with respect to cancel safety now.

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

No branches or pull requests

3 participants