Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/RestSharp/Extensions/HttpResponseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static async Task<string> GetResponseString(this HttpResponseMessage resp
var encoding = encodingString != null ? TryGetEncoding(encodingString) : clientEncoding;

using var reader = new StreamReader(new MemoryStream(bytes), encoding);
return await reader.ReadToEndAsync();
return await reader.ReadToEndAsync().ConfigureAwait(false);
Encoding TryGetEncoding(string es) {
try {
return Encoding.GetEncoding(es);
Expand Down
2 changes: 1 addition & 1 deletion src/RestSharp/Response/RestResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async Task<RestResponse> GetDefaultResponse() {
#endif

var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
var content = bytes == null ? null : await httpResponse.GetResponseString(bytes, options.Encoding);
var content = bytes == null ? null : await httpResponse.GetResponseString(bytes, options.Encoding).ConfigureAwait(false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Restresponse.cs header mismatch 📘 Rule violation ✓ Correctness

src/RestSharp/Response/RestResponse.cs does not contain the exact required Apache-2.0 license
header text even though the file is modified in this PR. This fails the repository licensing
compliance requirement for changed /src C# files.
Agent Prompt
## Issue description
`src/RestSharp/Response/RestResponse.cs` does not match the repository’s exact required Apache-2.0 license header text.

## Issue Context
Compliance requires every modified `/src` C# file to include the exact header as defined in `.github/copilot-instructions.md`.

## Fix Focus Areas
- src/RestSharp/Response/RestResponse.cs[1-14]
- .github/copilot-instructions.md[65-81]

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


return new(request) {
Content = content,
Expand Down
6 changes: 3 additions & 3 deletions src/RestSharp/RestClient.Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task<RestResponse<T>> ExecuteAsync<T>(
Ensure.NotNull(request, nameof(request));

var response = await client.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return await client.Serializers.Deserialize<T>(request, response, client.Options, cancellationToken);
return await client.Serializers.Deserialize<T>(request, response, client.Options, cancellationToken).ConfigureAwait(false);
}
Comment on lines 45 to 49

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Deadlock fix lacks tests 📘 Rule violation ⛯ Reliability

This PR changes async flow to prevent sync-over-async deadlocks, but it does not add/update tests to
cover the reported hang scenario. Without a regression test, the deadlock could reappear unnoticed.
Agent Prompt
## Issue description
The PR fixes a deadlock risk (sync-over-async) by adding `.ConfigureAwait(false)`, but there is no regression test to ensure the original hang scenario stays fixed.

## Issue Context
The reported issue involves sync methods (like `ExecuteGet`/`AsyncHelpers.RunSync`) hanging when invoked from ThreadPool work items due to `SynchronizationContext` capture.

## Fix Focus Areas
- src/RestSharp/RestClient.Extensions.cs[41-49]
- test/RestSharp.Tests/Fixtures/MockHttpClient.cs[1-17]
- test/RestSharp.Tests/[new test file covering ThreadPool ExecuteGet scenario]

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


/// <summary>
Expand Down Expand Up @@ -172,9 +172,9 @@ [EnumeratorCancellation] CancellationToken cancellationToken
using var reader = new StreamReader(stream);

#if NET7_0_OR_GREATER
while (await reader.ReadLineAsync(cancellationToken) is { } line && !cancellationToken.IsCancellationRequested) {
while (await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false) is { } line && !cancellationToken.IsCancellationRequested) {
#else
while (await reader.ReadLineAsync() is { } line && !cancellationToken.IsCancellationRequested) {
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line && !cancellationToken.IsCancellationRequested) {
#endif
if (string.IsNullOrWhiteSpace(line)) continue;

Expand Down
57 changes: 57 additions & 0 deletions test/RestSharp.Tests.Integrated/SyncRequestTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace RestSharp.Tests.Integrated;

#pragma warning disable xUnit1031 // Blocking calls in tests are intentional — we are testing sync-over-async deadlock safety

public sealed class SyncRequestTests(WireMockTestServer server) : IClassFixture<WireMockTestServer> {
[Fact]
public void Sync_execute_should_not_deadlock() {
// Regression test for https://github.com/restsharp/RestSharp/issues/2083
// Sync methods (ExecuteGet) could deadlock when await calls inside the pipeline
// did not use ConfigureAwait(false), causing continuations to try to marshal
// back to a captured SynchronizationContext.

using var client = new RestClient(server.Url!);
var request = new RestRequest("success");

RestResponse? response = null;

var completed = Task.Run(() => {
response = client.ExecuteGet(request);
}).Wait(TimeSpan.FromSeconds(10));

completed.Should().BeTrue("sync ExecuteGet should complete without deadlocking");
response.Should().NotBeNull();
response!.IsSuccessStatusCode.Should().BeTrue();
}

[Fact]
public void Sync_execute_with_deserialization_should_not_deadlock() {
using var client = new RestClient(server.Url!);
var request = new RestRequest("success");

RestResponse<SuccessResponse>? response = null;

var completed = Task.Run(() => {
response = client.ExecuteGet<SuccessResponse>(request);
}).Wait(TimeSpan.FromSeconds(10));

completed.Should().BeTrue("sync ExecuteGet<T> should complete without deadlocking");
response.Should().NotBeNull();
response!.IsSuccessStatusCode.Should().BeTrue();
response.Data.Should().NotBeNull();
}

[Fact]
public void Sync_execute_from_multiple_threads_should_not_deadlock() {
using var client = new RestClient(server.Url!);
const int threadCount = 5;

var completed = Parallel.For(0, threadCount, _ => {
var request = new RestRequest("success");
var response = client.ExecuteGet(request);
response.IsSuccessStatusCode.Should().BeTrue();
});

completed.IsCompleted.Should().BeTrue();
}
}
Loading