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

obtaining the stream state #4428

Closed
marten-seemann opened this issue Apr 11, 2024 · 5 comments
Closed

obtaining the stream state #4428

marten-seemann opened this issue Apr 11, 2024 · 5 comments

Comments

@marten-seemann
Copy link
Member

HTTP Datagrams (#3522) require us to track the state of the underlying QUIC stream:

  • It is not permitted to send DATAGRAMs for a stream whose send side is closed. We should reject calls to SendDatagram in our API.
  • Conversely, when DATAGRAMs are received for a stream whose receive side is closed, they MUST be discarded.

My first idea how to implement this was tracking the state transitions by wrapping a quic.Stream. For example, we know that the send side is closed when Closed and CancelWrite are called. So far, so good. However, the send side can also be closed by receiving a STOP_SENDING frame. As a consumer of a quic.Stream, we'll only learn about this on the next call to Write.
But here's the problem: What if the application never writes again. Sure - it should not just walk away from the stream without either calling Close or CancelWrite, but there's no guarantee for that. At the QUIC layer, we'd garbage collect the stream, whereas on the HTTP/3 layer this stream would remain open indefinitely.

The same happens on the read side, if the application never calls Read after the RESET_STREAM has been received. Again, the application shouldn't do that (and risks leaking the stream if it does and no RESET_STREAM is received), but we also don't have a guarantee for that.

It would be nice if there was an API that could be used to obtain the stream state, avoid any synchronization issues. It would be even nicer if no quic.Config entry was required, since our HTTP/3 API allows using a QUIC connection.

@marten-seemann
Copy link
Member Author

Here's an API proposal:

We add a new method to the SendStream, ReceiveStream and Stream:

type StreamState int

// TODO: define constants for all stream states defined in RFC 9000

type {Send,Receive}Stream interface {
     StateTransition() <-chan StreamState
}

The channel returned here is a buffered channel, with a capacity sufficient to hold all possible state transitions. I haven't done the math yet, but 8 should be sufficient, maybe even smaller.

The nice property of this channel API is that it doesn't matter when the application calls it: For example, the stream might already be closed when it is called. The channel will then contain all state transitions that happened to reach this state.


On the other hand, it requires a Go routine to block on this channel. Maybe it's better to set a callback:

type {Send,Receive}Stream interface {
     OnStateTransition(func(StreamState))
}

This callback would be called multiple times if multiple state transitions had already happened.

@marten-seemann
Copy link
Member Author

Note that the callback allows us to have multiple layers set a callback. We'd just wrap the existing callback. It's not terribly efficient, but should be fine for a limited number of callbacks. I don't see how this would work with the channel-based API.

@marten-seemann
Copy link
Member Author

Alternatively, we could consider changing how the QUIC layers reports streams as completed: We could only do that once the STOP_SENDING error (for send streams) or RESET_STREAM error (for receive streams) has been read by the application.

This would enforce "correct" usage of the stream API, and allow applications to more easily track the stream state by just wrapping a quic.Stream. The downside is that this change is pretty subtle, and we really need to make sure that we get it right, otherwise we risk deadlocks if streams don't get garbage collected.

@marten-seemann
Copy link
Member Author

Another downside is that the stream actually needs to be used (read from and written to). It's possible to just open the HTTP stream, and send and receive datagrams, but that means you'll never observe the state transition.

@marten-seemann
Copy link
Member Author

Resolved by #4445 and #4460.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant