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

Enable async-over-sync FileStream read/writes to be cancelable on Windows #87103

Merged
merged 2 commits into from
Jun 16, 2023

Conversation

stephentoub
Copy link
Member

@stephentoub stephentoub commented Jun 4, 2023

Using the same helpers we previously used to enable this in pipe streams on Windows, enable FileStream.Read/WriteAsync on a FileStream created for synchronous I/O to be cancelable. This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied.

Fixes #84290

This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied. There's no meaningful improvement for FileStream, since it was effectively ignoring the CancellationToken previously. So here's a benchmark showing the impact on PipeStream, which as noted was already cancelable using this mechanism:

Method Toolchain Mean Ratio Allocated Alloc Ratio
ReadWriteAsync \main\corerun.exe 3.979 us 1.00 84 B 1.00
ReadWriteAsync \pr\corerun.exe 3.342 us 0.85 2 B 0.02
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.IO.Pipes;
using System.Threading.Tasks;
using System.Threading;

[MemoryDiagnoser(false)]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    private readonly byte[] _buffer = new byte[1];
    private AnonymousPipeServerStream _server;
    private AnonymousPipeClientStream _client;

    [GlobalSetup]
    public void Setup()
    {
        _server = new AnonymousPipeServerStream(PipeDirection.Out);
        _client = new AnonymousPipeClientStream(PipeDirection.In, _server.ClientSafePipeHandle);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _server.Dispose();
        _client.Dispose();
    }

    [Benchmark(OperationsPerInvoke = 1000)]
    public async Task ReadWriteAsync()
    {
        for (int i = 0; i < 1000; i++)
        {
            ValueTask<int> read = _client.ReadAsync(_buffer, _cts.Token);
            await _server.WriteAsync(_buffer, _cts.Token);
            await read;
        }
    }
}

@ghost
Copy link

ghost commented Jun 4, 2023

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

Issue Details

Using the same helpers we previously used to enable this in pipe streams on Windows, enable FileStream.Read/WriteAsync on a FileStream created for synchronous I/O to be cancelable. This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied.

Fixes #84290

This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied. There's no meaningful improvement for FileStream, since it was effectively ignoring the CancellationToken previously. So here's a benchmark showing the impact on PipeStream, which as noted was already cancelable using this mechanism:

Method Toolchain Mean Ratio Allocated Alloc Ratio
ReadWriteAsync \main\corerun.exe 3.979 us 1.00 84 B 1.00
ReadWriteAsync \pr\corerun.exe 3.342 us 0.85 2 B 0.02
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.IO.Pipes;
using System.Threading.Tasks;
using System.Threading;

[MemoryDiagnoser(false)]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    private readonly byte[] _buffer = new byte[1];
    private AnonymousPipeServerStream _server;
    private AnonymousPipeClientStream _client;

    [GlobalSetup]
    public void Setup()
    {
        _server = new AnonymousPipeServerStream(PipeDirection.Out);
        _client = new AnonymousPipeClientStream(PipeDirection.In, _server.ClientSafePipeHandle);
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        _server.Dispose();
        _client.Dispose();
    }

    [Benchmark(OperationsPerInvoke = 1000)]
    public async Task ReadWriteAsync()
    {
        for (int i = 0; i < 1000; i++)
        {
            ValueTask<int> read = _client.ReadAsync(_buffer, _cts.Token);
            await _server.WriteAsync(_buffer, _cts.Token);
            await read;
        }
    }
}
Author: stephentoub
Assignees: stephentoub
Labels:

area-System.IO

Milestone: -

…dows

Using the same helpers we previously used to enable this in pipe streams on Windows, enable FileStream.Read/WriteAsync on a FileStream created for synchronous I/O to be cancelable.  This also makes some tweaks to those helpers to reduce allocation when a cancelable token is supplied.
Copy link
Member

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

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

LGTM, it's great that the helper components were so easy to re-use. Thanks @stephentoub !

@adamsitnik adamsitnik added this to the 8.0.0 milestone Jun 16, 2023
…ancellation.cs

Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@stephentoub stephentoub merged commit e154624 into dotnet:main Jun 16, 2023
@stephentoub stephentoub deleted the filestreamcancellation branch June 16, 2023 22:08
@ghost ghost locked as resolved and limited conversation to collaborators Jul 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow cancellation of Process standard IO stream operations on Windows
3 participants