Skip to content

Consider removing Stream's semaphore #45089

@stephentoub

Description

@stephentoub

The sole field remaining in the base Stream class is a semaphore used to serlalize any Read/WriteAsync calls issued to run concurrently on the instance. This behavior is an holdover from the original behavior similarly employed by BeginRead/Write that dates back to their original introduction.

On the one hand, it's dubious to use such APIs on Stream concurrently when the Stream doesn't guarantee this behavior is supported, and it'd be nice to be able to remove the semaphore, not only to reduce the size of Stream but also to reduce the complexity of the code that uses it (BufferedStream also relies on this, and some other streams have their own variant of this, e.g. PipeStream). It's also problematic for Streams that do support concurrent reads and writes but that don't override the async methods, as in #36481, though the right solution there is to override and not just rely on the default async-over-sync implementations.)

On the other hand, some streams do explicitly support reading/writing concurrently, e.g. NetworkStream, SslStream, etc., and so it's reasonable someone might assume all streams could be used in this fashion, and this semaphore protects against that. In fact, some streams like FileStream have multiple modes, one of which does support concurrent operations, and the base implementation which is then inherited into the other mode provides some consistency/protection. It's also possible/likely someone somewhere is relying on the serialization this provides, e.g. calling WriteAsync multiple times without waiting on the previous one to complete, knowing the base implementation will handle queueing the operations for them.

We should consider removing the semaphore behavior, at least by default, potentially with a quirk to put it back, at least for a release or two. We could keep the base field to support such a quirk, or alternatively to avoid the extra state in every Stream instance, employ a ConditionalWeakTable to only utilize the semaphore functionality if it's really needed.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions