Skip to content

Conversation

@nvborisenko
Copy link
Member

@nvborisenko nvborisenko commented Jan 23, 2026

User description

🔗 Related Issues

Resolves #16989

💥 What does this PR do?

  • Forces all methods to provide CancellationToken
  • Adds optional cancellation token which works with Timeout command option

🔄 Types of changes

  • New feature
  • Breaking change (fix or feature that would cause existing functionality to change)

PR Type

Enhancement, Tests


Description

  • Add CancellationToken parameter to all BiDi async methods

  • Implement linked token source for timeout and cancellation support

  • Add test coverage for timeout and cancellation token behavior

  • Propagate cancellation tokens through module hierarchy


Diagram Walkthrough

flowchart LR
  A["BiDi.ConnectAsync<br/>BiDi.StatusAsync<br/>BiDi.NewAsync<br/>BiDi.EndAsync"] -->|"CancellationToken"| B["SessionModule<br/>BrowserModule<br/>EmulationModule"]
  B -->|"CancellationToken"| C["Broker.ExecuteCommandAsync"]
  C -->|"LinkedTokenSource"| D["Timeout + CancellationToken"]
  D -->|"Register callback"| E["TaskCompletionSource"]
Loading

File Walkthrough

Relevant files
Enhancement
18 files
BiDi.cs
Add CancellationToken to public BiDi methods                         
+8/-8     
Broker.cs
Implement linked token source for timeout handling             
+9/-2     
Module.cs
Add CancellationToken to ExecuteCommandAsync signature     
+3/-2     
BrowserModule.cs
Add CancellationToken to all browser commands                       
+17/-17 
BrowsingContext.cs
Add CancellationToken to browsing context methods               
+22/-22 
BrowsingContextModule.cs
Add CancellationToken to context module commands                 
+25/-24 
BrowsingContextInputModule.cs
Add CancellationToken to input action methods                       
+7/-6     
BrowsingContextNetworkModule.cs
Add CancellationToken to network interception methods       
+11/-10 
BrowsingContextScriptModule.cs
Add CancellationToken to script evaluation methods             
+13/-12 
BrowsingContextStorageModule.cs
Add CancellationToken to storage cookie methods                   
+7/-6     
EmulationModule.cs
Add CancellationToken to emulation override methods           
+21/-20 
InputModule.cs
Add CancellationToken to input action commands                     
+7/-6     
NetworkModule.cs
Add CancellationToken to network command methods                 
+29/-28 
PermissionsModule.cs
Add CancellationToken to permission setting method             
+3/-2     
ScriptModule.cs
Add CancellationToken to script execution methods               
+17/-16 
SessionModule.cs
Add CancellationToken to session management methods           
+11/-10 
StorageModule.cs
Add CancellationToken to storage query methods                     
+7/-6     
WebExtensionModule.cs
Add CancellationToken to extension management methods       
+5/-4     
Tests
1 files
SessionTest.cs
Add timeout and cancellation token test cases                       
+20/-0   

@selenium-ci selenium-ci added the C-dotnet .NET Bindings label Jan 23, 2026
@nvborisenko nvborisenko marked this pull request as draft January 23, 2026 21:32
@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟢
🎫 #16989
🟢 Add a CancellationToken parameter to BiDi async command APIs (token should be
optional/defaultable at the public API layer).
Ensure async commands honor both a provided Timeout and a provided CancellationToken.
Implement combined cancellation by linking timeout-based cancellation with external
cancellation (linked CancellationTokenSource).
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 23, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Remove pending command on exit

Prevent a potential memory leak by ensuring the pending command is always
removed from the _pendingCommands dictionary using a try/finally block.

dotnet/src/webdriver/BiDi/Broker.cs [151-153]

-await _transport.SendAsync(data, cts.Token).ConfigureAwait(false);
+try
+{
+    await _transport.SendAsync(data, cts.Token).ConfigureAwait(false);
+    return (TResult)await tcs.Task.ConfigureAwait(false);
+}
+finally
+{
+    _pendingCommands.TryRemove(command.Id, out _);
+}
 
-return (TResult)await tcs.Task.ConfigureAwait(false);
-

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a potential memory leak where a pending command might not be removed from the _pendingCommands dictionary if an operation is canceled or fails, and provides a robust fix using a try/finally block.

High
Possible issue
Consolidate CTS creation and disposal
Suggestion Impact:The commit removed the separate timeout and linked CancellationTokenSource instances and replaced them with a single `using var cts` that is conditionally linked when the external token can be canceled, then applies the timeout via `CancelAfter(timeout)`, ensuring a single CTS is disposed.

code diff:

+        using var cts = cancellationToken.CanBeCanceled
+            ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
+            : new CancellationTokenSource();
+
         var timeout = options?.Timeout ?? TimeSpan.FromSeconds(30);
-
-        using var timeoutCts = new CancellationTokenSource(timeout);
-        using var linkedCts = cancellationToken != default ?
-            CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token) :
-            null;
-
-        var cts = linkedCts ?? timeoutCts;
+        cts.CancelAfter(timeout);
+

Refactor the cancellation token source creation to use a single
CancellationTokenSource, conditionally linking it based on whether the external
token can be canceled, and ensure proper disposal.

dotnet/src/webdriver/BiDi/Broker.cs [140-145]

-using var timeoutCts = new CancellationTokenSource(timeout);
-using var linkedCts = cancellationToken != default ?
-    CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token) :
-    null;
+CancellationTokenSource cts = cancellationToken.CanBeCanceled
+    ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeout)
+    : new CancellationTokenSource(timeout);
+using (cts)
 
-var cts = linkedCts ?? timeoutCts;
-

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a way to simplify the cancellation token logic, improving code readability and robustness by avoiding a nullable using variable and consolidating the creation logic.

Low
  • More

@nvborisenko nvborisenko marked this pull request as ready for review January 24, 2026 11:07
@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟢
🎫 #16989
🟢 Add an optional CancellationToken parameter to BiDi async commands (e.g., ...Options?
options = null, CancellationToken cancellationToken = default).
Ensure command cancellation honors both the provided Timeout option and the provided
CancellationToken by using a linked cancellation token source.
Keep existing timeout behavior (default timeout ~30 seconds) while enabling explicit
cancellation via token.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Cancellation cleanup risk: The new cancellation/timeout implementation registers cts.Token.Register(...) without
disposing the registration, which may cause resource leaks or incomplete cleanup of
command state on cancellation depending on surrounding code not visible in the diff.

Referred Code
var tcs = new TaskCompletionSource<EmptyResult>(TaskCreationOptions.RunContinuationsAsynchronously);

using var cts = cancellationToken.CanBeCanceled
    ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
    : new CancellationTokenSource();

var timeout = options?.Timeout ?? TimeSpan.FromSeconds(30);
cts.CancelAfter(timeout);

cts.Token.Register(() => tcs.TrySetCanceled(cts.Token));
var commandInfo = new CommandInfo(command.Id, tcs, jsonResultTypeInfo);

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix potential memory leak on cancellation

To prevent a memory leak, wrap the _transport.SendAsync call and task awaiting
in a try...finally block to ensure the command is always removed from the
_pendingCommands dictionary.

dotnet/src/webdriver/BiDi/Broker.cs [130-153]

 public async Task<TResult> ExecuteCommandAsync<TCommand, TResult>(TCommand command, CommandOptions? options, JsonTypeInfo<TCommand> jsonCommandTypeInfo, JsonTypeInfo<TResult> jsonResultTypeInfo, CancellationToken cancellationToken)
     where TCommand : Command
     where TResult : EmptyResult
 {
     command.Id = Interlocked.Increment(ref _currentCommandId);
 
     var tcs = new TaskCompletionSource<EmptyResult>(TaskCreationOptions.RunContinuationsAsynchronously);
 
     using var cts = cancellationToken.CanBeCanceled
         ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
         : new CancellationTokenSource();
 
     var timeout = options?.Timeout ?? TimeSpan.FromSeconds(30);
     cts.CancelAfter(timeout);
 
     cts.Token.Register(() => tcs.TrySetCanceled(cts.Token));
     var commandInfo = new CommandInfo(command.Id, tcs, jsonResultTypeInfo);
     _pendingCommands[command.Id] = commandInfo;
     var data = JsonSerializer.SerializeToUtf8Bytes(command, jsonCommandTypeInfo);
 
-    await _transport.SendAsync(data, cts.Token).ConfigureAwait(false);
+    try
+    {
+        await _transport.SendAsync(data, cts.Token).ConfigureAwait(false);
 
-    return (TResult)await tcs.Task.ConfigureAwait(false);
+        return (TResult)await tcs.Task.ConfigureAwait(false);
+    }
+    finally
+    {
+        _pendingCommands.TryRemove(command.Id, out _);
+    }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential memory leak where a command might not be removed from _pendingCommands if an exception occurs, and proposes a try...finally block to ensure cleanup, which is a robust solution.

Medium
General
Use async test assertions

Replace Assert.That with Assert.ThrowsAsync for testing exceptions in
asynchronous methods to ensure the tasks are properly awaited.

dotnet/test/common/BiDi/Session/SessionTest.cs [41-53]

-Assert.That(
-    () => bidi.StatusAsync(new() { Timeout = TimeSpan.FromMicroseconds(1) }),
-    Throws.InstanceOf<TaskCanceledException>());
+Assert.ThrowsAsync<TaskCanceledException>(async () =>
+    await bidi.StatusAsync(new() { Timeout = TimeSpan.FromMicroseconds(1) }));
 
-Assert.That(
-    () => bidi.StatusAsync(cancellationToken: cts.Token),
-    Throws.InstanceOf<TaskCanceledException>());
+Assert.ThrowsAsync<TaskCanceledException>(async () =>
+    await bidi.StatusAsync(cancellationToken: cts.Token));
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that Assert.That with a lambda returning a Task does not await it, and proposes using Assert.ThrowsAsync which is the correct way to test for exceptions in async methods.

Medium
  • More

@nvborisenko nvborisenko merged commit 150c432 into SeleniumHQ:trunk Jan 24, 2026
45 checks passed
@nvborisenko nvborisenko deleted the bidi-cancellation-token branch January 24, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🚀 Feature]: [dotnet] [bidi] Introduce CancellationToken for async commands

2 participants