-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and Motivation
Connected Streams -- like NetworkStream, PipeStream, SslStream, and the new QuicStream class -- need the ability to send EOF to indicate that the write side has completed without closing the Stream (and thus closing the read side too).
At the Socket layer, we support this via Socket.Shutdown(SocketShutdown.Send). We don't expose this in NetworkStream, however.
This is most useful for "connected" Streams that can Read and Write but not Seek. It's not clear whether this concept applies to streams like FileStream that can Seek.
Proposed API
Based on comments from this issue: #43101
The basic idea is to add an API like the following:
public abstract class Stream
{
public virtual bool CanShutdownWrite =>
false;
public virtual void ShutdownWrite() =>
Flush();
public virtual ValueTask ShutdownWriteAsync(CancellationToken cancellationToken = default) =>
new ValueTask(FlushAsync(cancellationToken));
}We'd like naming guidance:
- "ShutdownWrite" corresponds to existing Socket terminology ("shutdown"), but isn't necessarily the most intuitive term for non-Socket scenarios. Suggestions welcome. Unfortunately we cannot use "EndWrite" for obvious reasons. Other alternatives include "FinishWrite" and "CompleteWrite".
- ShutdownWriteAsync will complete once e.g. a shutdown packet has been sent, NOT when the peer acknowledges the shutdown. There is some concern that this isn't obvious from the name.
The hard question is where this API lives. There are three options here:
-
Add a new abstract base class
ConnectedStream(or whatever -- naming suggestions welcome) that derives from Stream and that our various existing streams inherit where appropriate (e.g. NetworkStream, SslStream, PipeStream, QuicStream, possibly others).- This has a con: other streams like
GzipStreamthat wrap a base stream and would be appropriate to pass-through a shutdown request no longer can.
- This has a con: other streams like
-
Just add a new virtual method to Stream itself, ideally with a reasonable default implementation, e.g. just calling Dispose (if there's no good default behavior, then potentially also a Can/Is capability test property).
- This has a con: this feature isn't really applicable for many uses of Stream.
- The "CanShutdownWrite" property is really only needed if we add this to Stream. We can probably remove it otherwise. Without it, any code depending on correct shutdown behavior would break in a difficult to diagnose way.
- Not specifically API but there is debate on if the Shutdown methods should default throw or flush. The intended behavior is that a successful shutdown also implies a flush.
-
Add a mix-in via an interface like
IConnectedStreamthat can be tested for, e.g.if (stream is IConnectedStream cs) cs.ShutdownWrites();- This has the same con as a new class: it breaks composition with transforming streams.
We'd like naming guidance: Shutdown()
Another issue to consider is whether to provide an overload for Write/WriteAsync that allows you to pass a flag indicating that the Stream should send EOF after the specified buffer, e.g. something like this:
public abstract class Stream
{
public virtual Task WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken, bool shutdownWrite);
}This is important because it enables the data and EOF to be sent in a single packet, which can improve performance, specifically for QUIC (and HTTP3 over QUIC) as well as HTTP2 request streams.
If we add a WriteAsync overload like this, then we could opt not to do ShutdownWrite at all and instead achieve this by passing a 0-length buffer and shutdownWrite=true to the WriteAsync overload.