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

Add forward #71

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Add forward #71

wants to merge 2 commits into from

Conversation

badeend
Copy link
Contributor

@badeend badeend commented Feb 11, 2024

During the plumber's summit I noticed that a forwarding mechanism was up for consideration in preview2.1.
So, here's my take.

@badeend badeend marked this pull request as ready for review February 11, 2024 22:03
Copy link

@pchickey pchickey left a comment

Choose a reason for hiding this comment

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

Looks like a great start! Thanks for writing this up.

wit/streams.wit Outdated
/// yet it does not change the ownership structure of them. I.e. they remain
/// children of their parent resources. If the parents place any lifetimes
/// restrictions on their children, those continue to apply.
forward: func(src: input-stream, dst: output-stream) -> future-forward-result;

Choose a reason for hiding this comment

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

These arguments will pass ownership of both the input-stream and output-stream. I believe sending an owned input-stream is correct here. In some use cases, we need a way to permit further use of the output-stream after forwarding has either finished or failed. We can do that by either making this parameter a borrow of the output-stream, or we get an owned output-stream in the resolution of future-forward-result.get

Copy link
Contributor Author

@badeend badeend Feb 12, 2024

Choose a reason for hiding this comment

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

Changing dst: output-stream from an owned handle to a borrowed handle will introduce multithreaded access to the stream. I can't see why one would want this as there's no way coordinate between the writes from different tasks/threads. From end-user and implementation complexity perspective I'd go for: (1) consume the stream and hand it back later, or: (2) document & enforce that while the forward is in progress any attempt to access the borrowed streams will trap.

That being said, the way I designed it, the host is guaranteed that no one else can touch the streams ever again. In my mind this is a "feature", as the host can then permanently fuse them together. This may enable additional optimizations not possible otherwise.

An example that triggered me to file this PR in the first place is: kTLS. Piping a TLS stream into a Socket stream could "automagically" offload TLS to the kernel (or even the NIC). Requiring the output-stream to remain usable after the input-stream has ended would prevent this optimization. To be clear, I haven't actually implemented this, so assign as much value to this use-case as you want.

Anyway, I can see merit in both designs. Maybe they can coexist?

Choose a reason for hiding this comment

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

I hadn't thought about that case, but I have heard of some use cases for wanting to use the destination stream after the source stream is consumed. So, I guess they can both coexist. Are you up for introducing both variants in this PR?

wit/streams.wit Outdated
/// has already been taken before.
/// - `some(ok(value))`: when the future is ready with a value. This is
/// returned only once.
get: func() -> option<result<stream-error>>;

Choose a reason for hiding this comment

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

We also need this get function to have a way to represent "youve already retrieved the result of this and i cant give it to you again", in http we used an outer result for that, so it would end up like option<result<result<stream-error>, _>>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, that's documented as:

- `some(error(_))`: when the future is resolved, but the result value has already been taken before.

Choose a reason for hiding this comment

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

oh, sorry, I read the type signature before the docs here. How do we represent the case where the forward completed with success? there is no stream-error to represent success, you need a result<stream-error> for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well "success" in this case is when one of the streams ended with closed. Which is already captured in the stream-error type.

Choose a reason for hiding this comment

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

OK, but as a user I would want to distinguish between whether the input-stream closed (expected outcome of the operation) and the output-stream closed (unexpected). When the expected outcome happens, we should return an ok.

Additionally I'd consider returning a variant forward-error { source(stream-error), destination(stream-error) } to distinguish where the error came from - we shouldnt throw away information that the user will need to debug.

Copy link
Contributor Author

@badeend badeend Feb 14, 2024

Choose a reason for hiding this comment

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

OK, but as a user I would want to distinguish between whether the input-stream closed (expected outcome of the operation) and the output-stream closed (unexpected). When the expected outcome happens, we should return an ok.

Fair enough.

Additionally I'd consider returning a variant forward-error { source(stream-error), destination(stream-error) } to distinguish where the error came from - we shouldnt throw away information that the user will need to debug.

I took this design from splice which already tosses errors from both streams on the same pile. Are you sure we want to introduce this inconsistency & complexity in return types? Or is it OK to say that whenever a consumer really cares about which stream failed, they can just attempt input-stream::read(0) or output-stream::check-write() after the fact?

wit/streams.wit Outdated Show resolved Hide resolved
@badeend
Copy link
Contributor Author

badeend commented Feb 14, 2024

I've updated the definitions. Pretty much the entire text has changed so please check again (sorry).

Noteworty changes:

  • There are now two dinstinct functions.
  • The future now returns an additional result.
  • I added a flush-on-block parameter. TBH, I'm not quite happy with this as-is, because there are a bazillion flushing strategies. The current version uses I/O readiness for its heuristic. Ideally, the input stream should have a native way to to have a way to return a flush signal from the read operation.
  • forward states: "Any attempt to access or drop the streams in the meantime will trap.". This is the simplest to implement. Another valid strategy could be to say that while the forward is in progress, any read/write attempt will return their EWOULDBLOCK equivalent until the forward is finished. This latter solution is less surprising to end users, but more difficult to implement.
  • Dropping the future may trap depending on its state.
  • The future can be canceled.

I have not added the forward-error variant (yet), see above.

@badeend
Copy link
Contributor Author

badeend commented Feb 17, 2024

After discussion in #73, I realized my interpretation of flush was not quite correct. I've removed the flush-on-block flag.

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.

None yet

3 participants