Skip to content

Conversation

nvborisenko
Copy link
Member

@nvborisenko nvborisenko commented Aug 8, 2025

User description

💥 What does this PR do?

Fix the issue when service wants to write into disposed stream.
Fix possible dual-blocking IO.

💡 Additional Considerations

This is core fix.

🔄 Types of changes

  • Cleanup (formatting, renaming)
  • Bug fix (backwards compatible)
  • New feature (non-breaking change which adds functionality and tests!)
  • Breaking change (fix or feature that would cause existing functionality to change)

PR Type

Bug fix, Enhancement


Description

  • Fix disposed stream write issue in driver service

  • Prevent dual-blocking IO during initialization

  • Reorder disposal calls in Firefox service

  • Clean up documentation comments


Diagram Walkthrough

flowchart LR
  A["Driver Service Start"] --> B["Begin Output/Error Reading"]
  B --> C["Wait for Initialization"]
  C --> D["Fire Started Event"]
  E["Firefox Service"] --> F["Dispose Base First"]
  F --> G["Dispose Log Writer"]
Loading

File Walkthrough

Relevant files
Bug fix
DriverService.cs
Fix service initialization order                                                 

dotnet/src/webdriver/DriverService.cs

  • Reorder service initialization sequence
  • Move output/error reading before waiting for initialization
  • Prevent blocking IO during startup
+5/-3     
FirefoxDriverService.cs
Fix disposal order and cleanup docs                                           

dotnet/src/webdriver/Firefox/FirefoxDriverService.cs

  • Remove unused parameter documentation
  • Reorder disposal calls to dispose base class first
  • Prevent writing to disposed stream
+2/-3     

@nvborisenko nvborisenko requested a review from diemol August 8, 2025 15:36
@selenium-ci selenium-ci added the C-dotnet .NET Bindings label Aug 8, 2025
Copy link
Contributor

qodo-merge-pro bot commented Aug 8, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Event Ordering

Moving OnDriverProcessStarted after WaitForServiceInitialization changes event sequencing; verify no consumers depend on the event being raised before initialization completes, and confirm no race with BeginOutput/ErrorReadLine.

bool serviceAvailable = this.WaitForServiceInitialization();

DriverProcessStartedEventArgs processStartedEventArgs = new DriverProcessStartedEventArgs(this.driverServiceProcess);
this.OnDriverProcessStarted(processStartedEventArgs);
Dispose Ordering

Disposing base first may dispose streams/event handlers that still reference logWriter; confirm no base dispose path reads/writes to logWriter after it's set to null.

protected override void Dispose(bool disposing)
{
    base.Dispose(disposing);

    if (logWriter != null && disposing)
    {
        logWriter.Dispose();
        logWriter = null;
    }
}

Copy link
Contributor

qodo-merge-pro bot commented Aug 8, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Guard and harden async stream reads

Wrap the asynchronous read starts in a try/catch and ensure they are only called
once to avoid ObjectDisposedException or InvalidOperationException if the
process exits immediately. Also short-circuit if the process has already exited
after Start() to prevent reading from disposed streams.

dotnet/src/webdriver/DriverService.cs [258-266]

 this.driverServiceProcess.Start();
 
-this.driverServiceProcess.BeginOutputReadLine();
-this.driverServiceProcess.BeginErrorReadLine();
+if (!this.driverServiceProcess.HasExited)
+{
+    try
+    {
+        this.driverServiceProcess.BeginOutputReadLine();
+        this.driverServiceProcess.BeginErrorReadLine();
+    }
+    catch (ObjectDisposedException)
+    {
+        // Process exited quickly; ignore as we'll fail initialization below.
+    }
+    catch (InvalidOperationException)
+    {
+        // Async read already started or stream unavailable; ignore safely.
+    }
+}
 
 bool serviceAvailable = this.WaitForServiceInitialization();
 
 DriverProcessStartedEventArgs processStartedEventArgs = new DriverProcessStartedEventArgs(this.driverServiceProcess);
 this.OnDriverProcessStarted(processStartedEventArgs);
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential race condition where the process could exit before async reads are initiated, leading to exceptions. Adding a check for HasExited and a try-catch block significantly improves the robustness of the service startup logic.

Medium
Unsubscribe from data events on dispose

Unsubscribe from process OutputDataReceived/ErrorDataReceived before disposing
logWriter and after calling base, to stop late-arriving events from using a
now-null/disposed logger. This prevents race conditions during shutdown.

dotnet/src/webdriver/Firefox/FirefoxDriverService.cs [279-288]

 protected override void Dispose(bool disposing)
 {
     base.Dispose(disposing);
 
-    if (logWriter != null && disposing)
+    if (disposing)
     {
-        logWriter.Dispose();
-        logWriter = null;
+        if (this.IsRunning)
+        {
+            try
+            {
+                this.driverServiceProcess.OutputDataReceived -= this.OnDriverProcessDataReceived;
+                this.driverServiceProcess.ErrorDataReceived -= this.OnDriverProcessDataReceived;
+            }
+            catch
+            {
+                // Swallow any errors during teardown
+            }
+        }
+
+        if (logWriter != null)
+        {
+            logWriter.Dispose();
+            logWriter = null;
+        }
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential race condition where OnDriverProcessDataReceived could be called after logWriter is disposed, causing a NullReferenceException. Unsubscribing from the events in Dispose is the correct pattern to prevent this.

Medium
Learned
best practice
Dispose children before base

Dispose managed resources owned by this class before calling the base class
Dispose to avoid potential use-after-dispose scenarios. Move the
base.Dispose(disposing) call to the end of the method.

dotnet/src/webdriver/Firefox/FirefoxDriverService.cs [279-288]

 protected override void Dispose(bool disposing)
 {
-    base.Dispose(disposing);
-
     if (logWriter != null && disposing)
     {
         logWriter.Dispose();
         logWriter = null;
     }
+
+    base.Dispose(disposing);
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Use proper resource disposal ordering to avoid accessing disposed objects and to ensure deterministic cleanup.

Low
  • Update

@nvborisenko
Copy link
Member Author

Ai reminds to unsubscribe, let me fix it.

@nvborisenko
Copy link
Member Author

Yeah, made it before release.

@nvborisenko nvborisenko merged commit 6e34ed6 into SeleniumHQ:trunk Aug 8, 2025
10 checks passed
@nvborisenko nvborisenko deleted the dotnet-ff-logpath branch August 8, 2025 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants