selenium icon indicating copy to clipboard operation
selenium copied to clipboard

[🐛 Bug]: "System.IO.IOException : Cannot access a closed stream" when disposing DevToolsSession

Open arnonax-tr opened this issue 5 months ago • 7 comments

What happened?

This bug happens pretty rarely, but it does. I ran the following test with [Repeat(1000)] to reproduce it:

using System.Collections.Concurrent;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;
using OpenQA.Selenium.DevTools.V127;
using OpenQA.Selenium.DevTools.V127.Network;
using OpenQA.Selenium.DevTools.V127.Fetch;
using EnableCommandSettings = OpenQA.Selenium.DevTools.V127.Fetch.EnableCommandSettings;
using RequestPattern = OpenQA.Selenium.DevTools.V127.Fetch.RequestPattern;

namespace TestProject2;

internal class DevToolsIssue
{
    private static bool _disposing;
    private static int _repeatCounter;

    [Test, Repeat(1000)]
    public void EventHandlersCanRunAfterDevToolsIsDisposed()
    {
        TestContext.Progress.WriteLine($"Repeat={_repeatCounter++}");
        _disposing = false;

        var executingHandlers = new CountdownEvent(1);

        using var driver = new ChromeDriver();
        var caughtExceptions = new ConcurrentQueue<Exception>();

        using (var devToolsSession = driver.GetDevToolsSession(new DevToolsOptions { ProtocolVersion = 127 }))
        {
            var networkAdapter = new NetworkAdapter(devToolsSession);
            var fetch = new FetchAdapter(devToolsSession);
            var network = new V127Network(networkAdapter, fetch);

            var enableCommandSettings = new EnableCommandSettings
            {
                Patterns = new RequestPattern[]
                {
                    new()
                    {
                        RequestStage = RequestStage.Request,
                        UrlPattern = "*"
                    }
                }
            };
            fetch.Enable(enableCommandSettings);

            network.RequestPaused += async (_, args) =>
            {
                if (!executingHandlers.TryAddCount())
                    return;

                try
                {
                    // Do some stuff...
                    await TestContext.Progress.WriteLineAsync($"ContinueRequestWithoutModification when _disposing={_disposing}");
                    await network.ContinueRequestWithoutModification(args.RequestData);
                }
                catch (Exception ex)
                {
                    caughtExceptions.Enqueue(ex);
                }
                finally
                {
                    executingHandlers.Signal();
                }
            };

            driver.Url = "https://www.google.com";

            executingHandlers.Signal();
            executingHandlers.Wait();

        } // Disposing DevToolsSession

        if (!caughtExceptions.IsEmpty)
            throw new AggregateException(caughtExceptions);
    }
}

And I got the following exception:

System.IO.IOException : Cannot access a closed stream.
   at System.Net.Http.HttpConnection.RawConnectionStream.WriteAsync(ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
   at System.Net.WebSockets.ManagedWebSocket.SendFrameLockAcquiredNonCancelableAsync(MessageOpcode opcode, Boolean endOfMessage, Boolean disableCompression, ReadOnlyMemory`1 payloadBuffer)
   at System.Net.WebSockets.ManagedWebSocket.SendFrameAsync(MessageOpcode opcode, Boolean endOfMessage, Boolean disableCompression, ReadOnlyMemory`1 payloadBuffer, CancellationToken cancellationToken)
   at System.Net.WebSockets.ManagedWebSocket.SendCloseFrameAsync(WebSocketCloseStatus closeStatus, String closeStatusDescription, CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Net.WebSockets.ManagedWebSocket.SendCloseFrameAsync(WebSocketCloseStatus closeStatus, String closeStatusDescription, CancellationToken cancellationToken)
   at System.Net.WebSockets.ManagedWebSocket.CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, String statusDescription, CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Net.WebSockets.ManagedWebSocket.CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, String statusDescription, CancellationToken cancellationToken)
   at System.Net.WebSockets.ManagedWebSocket.CloseOutputAsync(WebSocketCloseStatus closeStatus, String statusDescription, CancellationToken cancellationToken)
   at System.Net.WebSockets.ClientWebSocket.CloseOutputAsync(WebSocketCloseStatus closeStatus, String statusDescription, CancellationToken cancellationToken)
   at OpenQA.Selenium.DevTools.WebSocketConnection.ReceiveData()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining)
   at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
   at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
   at System.Threading.Tasks.ValueTask`1.ValueTaskSourceAsTask.<>c.<.cctor>b__4_0(Object state)
   at System.Net.WebSockets.ManagedWebSocket.ReceiveAsyncPrivate[TResult](Memory`1 payloadBuffer, CancellationToken cancellationToken)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Net.WebSockets.ManagedWebSocket.EnsureBufferContainsAsync(Int32 minimumRequiredBytes, CancellationToken cancellationToken, Boolean throwOnPrematureClosure)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining)
   at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
   at System.Threading.Tasks.Task`1.TrySetResult(TResult result)
   at System.Net.Http.HttpConnection.RawConnectionStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
   at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining)
   at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.SetExistingTaskResult(Task`1 task, TResult result)
   at System.Net.Http.HttpConnection.ReadBufferedAsyncCore(Memory`1 destination)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.InvokeContinuation(Action`1 continuation, Object state, Boolean forceAsync, Boolean requiresExecutionContextFlow)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.OnCompleted(SocketAsyncEventArgs _)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pNativeOverlapped)
--- End of stack trace from previous location ---
   at System.Net.WebSockets.ManagedWebSocket.SendCloseFrameAsync(WebSocketCloseStatus closeStatus, String closeStatusDescription, CancellationToken cancellationToken)
   at System.Net.WebSockets.ManagedWebSocket.CloseOutputAsyncCore(WebSocketCloseStatus closeStatus, String statusDescription, CancellationToken cancellationToken)
   at OpenQA.Selenium.DevTools.WebSocketConnection.ReceiveData()
   at OpenQA.Selenium.DevTools.WebSocketConnection.<Start>b__22_0()
   at OpenQA.Selenium.DevTools.WebSocketConnection.Stop()
   at OpenQA.Selenium.DevTools.DevToolsSession.TerminateSocketConnection()
   at OpenQA.Selenium.DevTools.DevToolsSession.<Dispose>b__43_0()
   at OpenQA.Selenium.DevTools.DevToolsSession.Dispose(Boolean disposing)
   at OpenQA.Selenium.DevTools.DevToolsSession.Dispose()
   at TestProject2.DevToolsIssue.EventHandlersCanRunAfterDevToolsIsDisposed()

How can we reproduce the issue?

Just paste the above code to a new nUnit project and run the test.

Relevant log output

14:19:20.338 TRACE SeleniumManager: Driver path: chromedriver.EXE
14:19:20.338 TRACE SeleniumManager: Browser path: C:\Program Files\Google\Chrome\Application\chrome.exe
14:19:20.864 DEBUG HttpCommandExecutor: Executing command: []: newSession {"capabilities":{"firstMatch":[{"browserName":"chrome","goog:chromeOptions":{"binary":"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"}}]}}
14:19:20.864 TRACE HttpCommandExecutor: >> Method: POST, RequestUri: 'http://localhost:52938/session', Version: 1.1, Content: System.Net.Http.ByteArrayContent, Headers:
{
  Accept: application/json; charset=utf-8
  User-Agent: selenium/4.23.0
  User-Agent: (.net windows)
  Content-Type: application/json; charset=utf-8
  Content-Length: 151
}
{"capabilities":{"firstMatch":[{"browserName":"chrome","goog:chromeOptions":{"binary":"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"}}]}}
14:19:21.677 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Cache-Control: no-cache
  Content-Length: 883
  Content-Type: application/json; charset=utf-8
}
14:19:21.678 DEBUG HttpCommandExecutor: Response: (3eb6914a3922045bf8e623a3e8dc17bc Success: System.Collections.Generic.Dictionary`2[System.String,System.Object])
14:19:25.763 DEBUG HttpCommandExecutor: Executing command: [3eb6914a3922045bf8e623a3e8dc17bc]: get {"url":"https://www.google.com"}
14:19:25.763 TRACE HttpCommandExecutor: >> Method: POST, RequestUri: 'http://localhost:52938/session/3eb6914a3922045bf8e623a3e8dc17bc/url', Version: 1.1, Content: System.Net.Http.ByteArrayContent, Headers:
{
  Accept: application/json; charset=utf-8
  User-Agent: selenium/4.23.0
  User-Agent: (.net windows)
  Content-Type: application/json; charset=utf-8
  Content-Length: 32
}
{"url":"https://www.google.com"}
14:19:26.371 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Cache-Control: no-cache
  Content-Length: 14
  Content-Type: application/json; charset=utf-8
}
14:19:26.371 DEBUG HttpCommandExecutor: Response: ( Success: )
14:19:26.388 DEBUG HttpCommandExecutor: Executing command: [3eb6914a3922045bf8e623a3e8dc17bc]: quit {}
14:19:26.389 TRACE HttpCommandExecutor: >> Method: DELETE, RequestUri: 'http://localhost:52938/session/3eb6914a3922045bf8e623a3e8dc17bc', Version: 1.1, Content: <null>, Headers:
{
  User-Agent: selenium/4.23.0
  User-Agent: (.net windows)
  Accept: application/json
  Accept: image/png
}
14:19:26.491 TRACE HttpCommandExecutor: << StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Cache-Control: no-cache
  Content-Length: 14
  Content-Type: application/json; charset=utf-8
}
14:19:26.491 DEBUG HttpCommandExecutor: Response: ( Success: )

Operating System

Windows 11

Selenium version

C# 11 (dotnet 8), Selenium.WebDriver 4.23.0

What are the browser(s) and version(s) where you see this issue?

Chrome 128

What are the browser driver(s) and version(s) where you see this issue?

ChromeDriver 128.0.6613.8600

Are you using Selenium Grid?

No response

arnonax-tr avatar Sep 02 '24 11:09 arnonax-tr