-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use new PipeWriter Json overloads #55740
Changes from 4 commits
7a0ed50
f69ef58
36a6150
b609397
7b0f48e
9360f66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO.Pipelines; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using System.Text.Json.Serialization.Metadata; | ||
|
@@ -89,13 +90,23 @@ public static Task WriteAsJsonAsync<TValue>( | |
|
||
response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; | ||
|
||
var startTask = Task.CompletedTask; | ||
if (!response.HasStarted) | ||
{ | ||
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush. | ||
startTask = response.StartAsync(cancellationToken); | ||
} | ||
|
||
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException | ||
if (!cancellationToken.CanBeCanceled) | ||
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled) | ||
{ | ||
return WriteAsJsonAsyncSlow(response.Body, value, options, response.HttpContext.RequestAborted); | ||
return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, options, | ||
ignoreOCE: !cancellationToken.CanBeCanceled, | ||
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "Slow" path is normal one right, since the middleware doesn't pass a cancellation token? I think it would be better to change Right now, the only way to get the fast pass is to pass in a token from a cancellable source and then not cancel it. Passing in @JamesNK @Tratcher do you have opinions on not passing in |
||
} | ||
|
||
return JsonSerializer.SerializeAsync(response.Body, value, options, cancellationToken); | ||
startTask.GetAwaiter().GetResult(); | ||
return JsonSerializer.SerializeAsync(response.BodyWriter, value, options, cancellationToken); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -120,21 +131,33 @@ public static Task WriteAsJsonAsync<TValue>( | |
|
||
response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; | ||
|
||
var startTask = Task.CompletedTask; | ||
if (!response.HasStarted) | ||
{ | ||
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush. | ||
startTask = response.StartAsync(cancellationToken); | ||
} | ||
|
||
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException | ||
if (!cancellationToken.CanBeCanceled) | ||
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled) | ||
{ | ||
return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo); | ||
return WriteAsJsonAsyncSlow(startTask, response, value, jsonTypeInfo, | ||
ignoreOCE: !cancellationToken.CanBeCanceled, | ||
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted); | ||
} | ||
|
||
return JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, cancellationToken); | ||
startTask.GetAwaiter().GetResult(); | ||
return JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken); | ||
|
||
static async Task WriteAsJsonAsyncSlow(HttpResponse response, TValue value, JsonTypeInfo<TValue> jsonTypeInfo) | ||
static async Task WriteAsJsonAsyncSlow(Task startTask, HttpResponse response, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, | ||
bool ignoreOCE, CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
await JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, response.HttpContext.RequestAborted); | ||
await startTask; | ||
await JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken); | ||
} | ||
catch (OperationCanceledException) { } | ||
catch (OperationCanceledException) when (ignoreOCE) { } | ||
} | ||
} | ||
|
||
|
@@ -161,37 +184,52 @@ public static Task WriteAsJsonAsync( | |
|
||
response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; | ||
|
||
var startTask = Task.CompletedTask; | ||
if (!response.HasStarted) | ||
{ | ||
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush. | ||
startTask = response.StartAsync(cancellationToken); | ||
} | ||
|
||
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException | ||
if (!cancellationToken.CanBeCanceled) | ||
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled) | ||
{ | ||
return WriteAsJsonAsyncSlow(response, value, jsonTypeInfo); | ||
return WriteAsJsonAsyncSlow(startTask, response, value, jsonTypeInfo, | ||
ignoreOCE: !cancellationToken.CanBeCanceled, | ||
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted); | ||
} | ||
|
||
return JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, cancellationToken); | ||
startTask.GetAwaiter().GetResult(); | ||
return JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken); | ||
|
||
static async Task WriteAsJsonAsyncSlow(HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo) | ||
static async Task WriteAsJsonAsyncSlow(Task startTask, HttpResponse response, object? value, JsonTypeInfo jsonTypeInfo, | ||
bool ignoreOCE, CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
await JsonSerializer.SerializeAsync(response.Body, value, jsonTypeInfo, response.HttpContext.RequestAborted); | ||
await startTask; | ||
await JsonSerializer.SerializeAsync(response.BodyWriter, value, jsonTypeInfo, cancellationToken); | ||
} | ||
catch (OperationCanceledException) { } | ||
catch (OperationCanceledException) when (ignoreOCE) { } | ||
} | ||
} | ||
|
||
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] | ||
[RequiresDynamicCode(RequiresDynamicCodeMessage)] | ||
private static async Task WriteAsJsonAsyncSlow<TValue>( | ||
Stream body, | ||
Task startTask, | ||
PipeWriter body, | ||
TValue value, | ||
JsonSerializerOptions? options, | ||
bool ignoreOCE, | ||
CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
await startTask; | ||
await JsonSerializer.SerializeAsync(body, value, options, cancellationToken); | ||
} | ||
catch (OperationCanceledException) { } | ||
catch (OperationCanceledException) when (ignoreOCE) { } | ||
} | ||
|
||
/// <summary> | ||
|
@@ -266,29 +304,42 @@ public static Task WriteAsJsonAsync( | |
|
||
response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; | ||
|
||
var startTask = Task.CompletedTask; | ||
if (!response.HasStarted) | ||
{ | ||
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush. | ||
startTask = response.StartAsync(cancellationToken); | ||
} | ||
|
||
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException | ||
if (!cancellationToken.CanBeCanceled) | ||
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled) | ||
{ | ||
return WriteAsJsonAsyncSlow(response.Body, value, type, options, response.HttpContext.RequestAborted); | ||
return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, type, options, | ||
ignoreOCE: !cancellationToken.CanBeCanceled, | ||
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted); | ||
} | ||
|
||
return JsonSerializer.SerializeAsync(response.Body, value, type, options, cancellationToken); | ||
startTask.GetAwaiter().GetResult(); | ||
return JsonSerializer.SerializeAsync(response.BodyWriter, value, type, options, cancellationToken); | ||
} | ||
|
||
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] | ||
[RequiresDynamicCode(RequiresDynamicCodeMessage)] | ||
private static async Task WriteAsJsonAsyncSlow( | ||
Stream body, | ||
Task startTask, | ||
PipeWriter body, | ||
object? value, | ||
Type type, | ||
JsonSerializerOptions? options, | ||
bool ignoreOCE, | ||
CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
await startTask; | ||
await JsonSerializer.SerializeAsync(body, value, type, options, cancellationToken); | ||
} | ||
catch (OperationCanceledException) { } | ||
catch (OperationCanceledException) when (ignoreOCE) { } | ||
} | ||
|
||
/// <summary> | ||
|
@@ -316,21 +367,33 @@ public static Task WriteAsJsonAsync( | |
|
||
response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset; | ||
|
||
var startTask = Task.CompletedTask; | ||
if (!response.HasStarted) | ||
{ | ||
// Flush headers before starting Json serialization. This avoids an extra layer of buffering before the first flush. | ||
startTask = response.StartAsync(cancellationToken); | ||
} | ||
|
||
// if no user provided token, pass the RequestAborted token and ignore OperationCanceledException | ||
if (!cancellationToken.CanBeCanceled) | ||
if (!startTask.IsCompleted || !cancellationToken.CanBeCanceled) | ||
{ | ||
return WriteAsJsonAsyncSlow(); | ||
return WriteAsJsonAsyncSlow(startTask, response.BodyWriter, value, type, context, | ||
ignoreOCE: !cancellationToken.CanBeCanceled, | ||
cancellationToken.CanBeCanceled ? cancellationToken : response.HttpContext.RequestAborted); | ||
} | ||
|
||
return JsonSerializer.SerializeAsync(response.Body, value, type, context, cancellationToken); | ||
startTask.GetAwaiter().GetResult(); | ||
return JsonSerializer.SerializeAsync(response.BodyWriter, value, type, context, cancellationToken); | ||
|
||
async Task WriteAsJsonAsyncSlow() | ||
static async Task WriteAsJsonAsyncSlow(Task startTask, PipeWriter body, object? value, Type type, JsonSerializerContext context, | ||
bool ignoreOCE, CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
await JsonSerializer.SerializeAsync(response.Body, value, type, context, cancellationToken); | ||
await startTask; | ||
await JsonSerializer.SerializeAsync(body, value, type, context, cancellationToken); | ||
} | ||
catch (OperationCanceledException) { } | ||
catch (OperationCanceledException) when (ignoreOCE) { } | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be nice to get some HTTP/2 numbers too. I don't think this optimization helps in that case, but it probably doesn't hurt either. @sebastienros Do we have any HTTP/2 scenarios that use
WriteAsJsonAsync
?