NetworkStream.ReadAsync with a cancellation token never cancels

前端 未结 6 1068
天涯浪人
天涯浪人 2020-11-27 04:02

Here the proof.
Any idea what is wrong in this code ?

    [TestMethod]
    public void TestTest()
    {
        var tcp = new TcpClient() { ReceiveTimeou         


        
6条回答
  •  眼角桃花
    2020-11-27 04:55

    Providing more context on three different approaches. My service monitors other web applications availability. So, it needs to establish lots of connections to various web sites. Some of them crash/return errors/become unresponsive.

    Axis Y - number of hung tests (sessions). Drops to 0 caused by deployments/restarts.

    I. (Jan 25th) After revamping a service, the initial implementation used ReadAsync with a cancellation token. This resulted in lots of tests hanging (running requests against those web sites showed that servers indeed sometimes didn't return content).

    II. (Feb 17th) Deployed a change which guarded cancellation with Task.Delay. This completely fixed this issue.

    private async Task StreamReadWithCancellationTokenAsync(Stream stream, byte[] buffer, int count, Task cancellationDelayTask)
    {
        if (cancellationDelayTask.IsCanceled)
        {
            throw new TaskCanceledException();
        }
    
        // Stream.ReadAsync doesn't honor cancellation token. It only checks it at the beginning. The actual
        // operation is not guarded. As a result if remote server never responds and connection never closed
        // it will lead to this operation hanging forever.
        Task readBytesTask = stream.ReadAsync(
            buffer,
            0,
            count);
        await Task.WhenAny(readBytesTask, cancellationDelayTask).ConfigureAwait(false);
    
        // Check whether cancellation task is cancelled (or completed).
        if (cancellationDelayTask.IsCanceled || cancellationDelayTask.IsCompleted)
        {
            throw new TaskCanceledException();
        }
    
        // Means that main task completed. We use Result directly.
        // If the main task failed the following line will throw an exception and
        // we'll catch it above.
        int readBytes = readBytesTask.Result;
    
        return readBytes;
    }
    

    III (March 3rd) Following this StackOverflow implemented closing a stream based on timeout:

    using (timeoutToken.Register(() => stream.Close()))
    {
        // Stream.ReadAsync doesn't honor cancellation token. It only checks it at the beginning. The actual
        // operation is not guarded. As a result if a remote server never responds and connection never closed
        // it will lead to this operation hanging forever.
        // ReSharper disable once MethodSupportsCancellation
        readBytes = await targetStream.ReadAsync(
            buffer,
            0,
            Math.Min(responseBodyLimitInBytes - totalReadBytes, buffer.Length)).ConfigureAwait(false);
    }
    

    This implementation brought hangs back (not to the same extent as the initial approach):

    Reverted back to Task.Delay solution.

提交回复
热议问题