Skip to content

Addendum to #2367#2371

Open
deAtog wants to merge 3 commits intorestsharp:devfrom
deAtog:dev
Open

Addendum to #2367#2371
deAtog wants to merge 3 commits intorestsharp:devfrom
deAtog:dev

Conversation

@deAtog
Copy link

@deAtog deAtog commented Mar 17, 2026

Description

  • Adds missing ConfigureAwait(false) to await call in RunSync method.
  • Refactors the CustomSynchronizationContext and associated RunSync method for code clarity.
  • Drops the ConfigureAwait(false) method call on the task executed from the CustomSynchronizationContext to ensure continuations occur on the custom context, not the thread pool.

Purpose

These changes should prevent deadlocks from calling async methods and ensure that continuations happen synchronously on the CustomSynchronizationContext instead of the thread pool.

History

The CustomSychronizationContext implementation was originally pulled from https://github.com/rebus-org/Rebus/blob/27b212a2380d55edc16a0036dfefd8e6b3ad9c2c/Rebus/Bus/Advanced/RebusAsyncHelpers.cs

Their correct implementation does not call ConfigureAwait(false) on the task executed from within the CustomSynchronizationContext to ensure any continuations are queued on the CustomSynchronnizationContext, not the thread pool.

David Ellingsworth added 2 commits March 16, 2026 16:31
…ext when it is run.

This simplifies the RunSync method and clarifies the context in which the PostCallback is ran.
@deAtog
Copy link
Author

deAtog commented Mar 17, 2026

@dotnet-policy-service agree company="The Davey Tree Expert Company"

@qodo-free-for-open-source-projects
Copy link
Contributor

Review Summary by Qodo

Refactor CustomSynchronizationContext to prevent deadlocks

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Refactored CustomSynchronizationContext to manage context setup internally
• Removed ConfigureAwait(false) from task execution to ensure continuations stay on custom context
• Added ConfigureAwait(false) to RunSync<T> method's await call
• Simplified RunSync method by moving context management into CustomSynchronizationContext.Run()
Diagram
flowchart LR
  A["RunSync calls<br/>CustomSynchronizationContext"] -->|"simplified"| B["CustomSynchronizationContext.Run<br/>manages context"]
  B -->|"sets context"| C["SynchronizationContext<br/>set to custom"]
  C -->|"executes"| D["PostCallback<br/>without ConfigureAwait"]
  D -->|"ensures continuations<br/>stay on context"| E["Prevents deadlocks<br/>and thread pool jumps"]
Loading

Grey Divider

File Changes

1. src/RestSharp/AsyncHelpers.cs 🐞 Bug fix +26/-20

Refactor context management and ConfigureAwait usage

• Simplified RunSync method by removing context management logic
• Moved SynchronizationContext.SetSynchronizationContext calls into
 CustomSynchronizationContext.Run() method
• Removed ConfigureAwait(false) from PostCallback to keep continuations on custom context
• Added ConfigureAwait(false) to RunSync<T> method's await call for consistency
• Added explanatory comments clarifying the context behavior and continuation handling

src/RestSharp/AsyncHelpers.cs


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Contributor

qodo-free-for-open-source-projects bot commented Mar 17, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. RunSync<T> escapes context🐞 Bug ✓ Correctness
Description
AsyncHelpers.RunSync<T> now uses ConfigureAwait(false), causing the continuation that assigns the
result (and any future logic after the await) to run on the thread pool instead of the custom
synchronization context/calling thread. This breaks the method’s documented contract and affects all
synchronous RestClient APIs implemented via AsyncHelpers.RunSync.
Code

src/RestSharp/AsyncHelpers.cs[41]

+        RunSync(async () => { result = await task().ConfigureAwait(false); });
Evidence
RunSync/RunSync<T> is documented to run tasks synchronously on the calling thread by queueing
continuations onto a temporary synchronization context, and PostCallback explicitly avoids
ConfigureAwait(false) to keep continuations on that context. Adding ConfigureAwait(false) in
RunSync<T> bypasses that context for the continuation that assigns the result, and since many
synchronous RestClient APIs are implemented via AsyncHelpers.RunSync, the behavior change propagates
broadly.

src/RestSharp/AsyncHelpers.cs[22-42]
src/RestSharp/AsyncHelpers.cs[103-109]
src/RestSharp/RestClient.Extensions.Get.cs[37-43]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`AsyncHelpers.RunSync&amp;lt;T&amp;gt;` uses `await task().ConfigureAwait(false)`, which allows the continuation that assigns `result` to run on the thread pool rather than the custom synchronization context/calling thread. This contradicts the documented behavior of running synchronously on the calling thread via a pumped synchronization context and changes behavior for all synchronous RestClient APIs built on `RunSync`.
## Issue Context
`CustomSynchronizationContext.PostCallback` explicitly avoids `ConfigureAwait(false)` to keep continuations queued on the custom context, but `RunSync&amp;lt;T&amp;gt;` reintroduces a thread-pool continuation via `ConfigureAwait(false)`.
## Fix Focus Areas
- src/RestSharp/AsyncHelpers.cs[33-43]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

The anonymous method in the RunSync call does not capture the
current context until it is executed. Because it is called
from within the CustomSynchronizationContext, it will always capture
the CustomSynchhronizationContext and not the caller's context.

This reverts commit ec15969.
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant