Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f80e61d
[Fusion] Add Grouping for ExecutionNode
michaelstaib Feb 19, 2026
bc939db
Group Requests By Default
michaelstaib Feb 19, 2026
f5c2bf0
Reworked the dispatcher
michaelstaib Feb 20, 2026
bf9941e
Skip batching if client is not capable.
michaelstaib Feb 20, 2026
0de5ff9
Merge branch 'main' into mst/execution-node-grouping
michaelstaib Feb 20, 2026
f90f087
Updated benchmark gateway
michaelstaib Feb 20, 2026
ff0655b
Minor changes
michaelstaib Feb 20, 2026
f9e1e88
disable grouping
michaelstaib Feb 20, 2026
49626d7
Enable Batching
michaelstaib Feb 21, 2026
8c12d35
Fixed batching opt-out
michaelstaib Feb 23, 2026
c5313ff
Reworked batching condition
michaelstaib Feb 23, 2026
b0424ca
Started work on inlined batches
michaelstaib Feb 23, 2026
522364b
Added operation merging.
michaelstaib Feb 23, 2026
e76eab1
updated snapshots
michaelstaib Feb 23, 2026
e707a37
Merge branch 'main' into mst/execution-node-grouping
michaelstaib Feb 23, 2026
521c37e
Merge branch 'main' into mst/execution-node-grouping
michaelstaib Feb 23, 2026
a24ee83
Optimized plan context
michaelstaib Feb 23, 2026
5fda2cf
Add expandable pools
michaelstaib Feb 23, 2026
e8a45f4
contract after 5 minutes
michaelstaib Feb 23, 2026
27cecd5
Merge branch 'main' into mst/execution-node-grouping
michaelstaib Feb 23, 2026
81c3076
HTTP tuning
michaelstaib Feb 24, 2026
43697f0
reconfigured
michaelstaib Feb 24, 2026
e690df2
Optimized transport and disabled cost
michaelstaib Feb 24, 2026
a5d2f0b
Optimized work queue allocations.
michaelstaib Feb 24, 2026
11c52d8
Removed Service Allocation
michaelstaib Feb 24, 2026
58ed6af
Removed concurrent dictionary.
michaelstaib Feb 24, 2026
435882d
Merge branch 'main' into mst/execution-node-grouping
michaelstaib Feb 24, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public async Task<IExecutionResult> ExecuteSingleAsync(

var requestBuilder = OperationRequestBuilder.From(request);
requestBuilder.SetFlags(flags);
requestBuilder.SetServices(context.RequestServices);

await _requestInterceptor.OnCreateAsync(context, _executor, requestBuilder, context.RequestAborted);

Expand All @@ -116,6 +117,7 @@ public async Task<IResponseStream> ExecuteOperationBatchAsync(
var requestBuilder = OperationRequestBuilder.From(request);
requestBuilder.SetOperationName(operationNames[i]);
requestBuilder.SetFlags(flags);
requestBuilder.SetServices(context.RequestServices);

await _requestInterceptor.OnCreateAsync(context, _executor, requestBuilder, context.RequestAborted);

Expand Down Expand Up @@ -144,6 +146,7 @@ public async Task<IResponseStream> ExecuteBatchAsync(
{
var requestBuilder = OperationRequestBuilder.From(requests[i]);
requestBuilder.SetFlags(flags);
requestBuilder.SetServices(context.RequestServices);

await _requestInterceptor.OnCreateAsync(context, _executor, requestBuilder, context.RequestAborted);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\Transport.Formatters\HotChocolate.Transport.Formatters.csproj" />
<ProjectReference Include="..\Transport.Sockets\HotChocolate.Transport.Sockets.csproj" />
<ProjectReference Include="..\..\..\Caching\src\Caching.Memory\HotChocolate.Caching.Memory.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ public sealed class GraphQLServerOptions
/// <summary>
/// Defines if request batching is enabled.
/// </summary>
public bool EnableBatching { get; set; }
public bool EnableBatching { get; set; } = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public OperationSession(
public bool IsCompleted { get; private set; }

public void BeginExecute(GraphQLRequest request, CancellationToken cancellationToken)
=> SendResultsAsync(request, cancellationToken).FireAndForget();
=> _ = SendResultsAsync(request, cancellationToken);

private async Task SendResultsAsync(GraphQLRequest request, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using HotChocolate.Caching.Memory;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
Expand All @@ -13,8 +13,8 @@ namespace HotChocolate.AspNetCore.Utilities;
/// </summary>
internal static class HeaderUtilities
{
private static readonly ConcurrentDictionary<string, CacheEntry> s_cache =
new(StringComparer.Ordinal);
private static readonly Cache<AcceptHeaderResult> s_headerCache = new(128);
private static readonly Cache<AcceptMediaType> s_mediaTypeCache = new(128);

public static readonly AcceptMediaType[] GraphQLResponseContentTypes =
[
Expand Down Expand Up @@ -42,109 +42,95 @@ public static AcceptHeaderResult GetAcceptHeader(HttpRequest request)
return new AcceptHeaderResult([]);
}

string[] innerArray;

if (count == 1)
{
var headerValue = value[0]!;

if (TryParseMediaType(headerValue, out var parsedValue))
if (s_headerCache.TryGet(headerValue, out var cached))
{
return new AcceptHeaderResult([parsedValue]);
return cached;
}

// note: this is a workaround for now. we need to parse this properly.
if (headerValue.IndexOf(',', StringComparison.Ordinal) != -1)
{
innerArray = headerValue.Split(',');
goto MULTI_VALUES;
}

return new AcceptHeaderResult(headerValue);
}
var result = ParseHeaderValue(headerValue);

innerArray = value!;

MULTI_VALUES:
ref var searchSpace = ref MemoryMarshal.GetReference(innerArray.AsSpan());
var parsedValues = new AcceptMediaType[innerArray.Length];
var p = 0;

for (var i = 0; i < innerArray.Length; i++)
{
var mediaType = Unsafe.Add(ref searchSpace, i);
if (TryParseMediaType(mediaType, out var parsedValue))
{
parsedValues[p++] = parsedValue;
}
else
if (!result.HasError)
{
return new AcceptHeaderResult(mediaType);
s_headerCache.TryAdd(headerValue, result);
}
}

if (parsedValues.Length > p)
{
Array.Resize(ref parsedValues, p);
return result;
}

return new AcceptHeaderResult(parsedValues);
return ParseMultipleHeaderValues(value!);
}

return new AcceptHeaderResult([]);
}

private static bool TryParseMediaType(string s, out AcceptMediaType value)
private static AcceptHeaderResult ParseHeaderValue(string headerValue)
{
MakeSpace();

// first we try to look up the parsed header in the cache.
// if we find it the string was a valid header value and
// we return it.
if (s_cache.TryGetValue(s, out var entry))
if (TryParseMediaType(headerValue, out var parsedValue))
{
value = entry.Value;
return true;
return new AcceptHeaderResult([parsedValue]);
}

// if not we will try to parse it.
if (MediaTypeHeaderValue.TryParse(s, out var parsedValue))
// note: this is a workaround for now. we need to parse this properly.
if (headerValue.IndexOf(',', StringComparison.Ordinal) != -1)
{
entry = s_cache.GetOrAdd(s, k => new CacheEntry(k, parsedValue));
value = entry.Value;
return true;
return ParseMultipleHeaderValues(headerValue.Split(','));
}

value = default;
return false;
return new AcceptHeaderResult(headerValue);
}

private static void MakeSpace()
private static AcceptHeaderResult ParseMultipleHeaderValues(string[] innerArray)
{
// if we reach the maximum available space we will remove around 20% of the cached items.
if (s_cache.Count > 100)
ref var searchSpace = ref MemoryMarshal.GetReference(innerArray.AsSpan());
var parsedValues = new AcceptMediaType[innerArray.Length];
var p = 0;

for (var i = 0; i < innerArray.Length; i++)
{
foreach (var entry in s_cache.Values.OrderBy(t => t.CreatedAt).Take(20))
var mediaType = Unsafe.Add(ref searchSpace, i);
if (TryParseMediaType(mediaType, out var parsedValue))
{
s_cache.TryRemove(entry.Key, out _);
parsedValues[p++] = parsedValue;
}
else
{
return new AcceptHeaderResult(mediaType);
}
}

if (parsedValues.Length > p)
{
Array.Resize(ref parsedValues, p);
}

return new AcceptHeaderResult(parsedValues);
}

private readonly struct CacheEntry
private static bool TryParseMediaType(string s, out AcceptMediaType value)
{
public CacheEntry(string key, MediaTypeHeaderValue value)
if (s_mediaTypeCache.TryGet(s, out var cached))
{
Key = key;
Value = new AcceptMediaType(value.Type, value.SubType, value.Quality, value.Charset);
CreatedAt = DateTime.UtcNow;
value = cached;
return true;
}

public string Key { get; }

public AcceptMediaType Value { get; }
if (MediaTypeHeaderValue.TryParse(s, out var parsedValue))
{
value = new AcceptMediaType(
parsedValue.Type,
parsedValue.SubType,
parsedValue.Quality,
parsedValue.Charset);
s_mediaTypeCache.TryAdd(s, value);
return true;
}

public DateTime CreatedAt { get; }
value = default;
return false;
}

internal readonly struct AcceptHeaderResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ private void EnsureKeepAlive()

if (DateTime.UtcNow - _lastWriteTime >= s_keepAlivePeriod)
{
WriteKeepAliveAsync().FireAndForget();
_ = WriteKeepAliveAsync();
}

async Task WriteKeepAliveAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ private void EnsureKeepAlive()

if (DateTime.UtcNow - _lastWriteTime >= s_keepAlivePeriod)
{
WriteKeepAliveAsync().FireAndForget();
_ = WriteKeepAliveAsync();
}

async Task WriteKeepAliveAsync()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// ReSharper disable IntroduceOptionalParameters.Global

using System.Diagnostics;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
Expand Down Expand Up @@ -187,6 +188,8 @@ private static ByteArrayContent CreatePostContent(
request.Body.WriteTo(jsonWriter);
jsonWriter.Flush();

Debug.WriteLine(Encoding.UTF8.GetString(arrayWriter.WrittenSpan));

var internalBuffer = PooledArrayWriterMarshal.GetUnderlyingBuffer(arrayWriter);
var content = new ByteArrayContent(internalBuffer, 0, arrayWriter.Length);
content.Headers.ContentType = new MediaTypeHeaderValue(ContentType.Json, "utf-8");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public void TrySendCompleteMessage()
{
if (!_completed)
{
TrySendCompleteMessageInternalAsync(socket, id).FireAndForget();
_ = TrySendCompleteMessageInternalAsync(socket, id);
_completed = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using HotChocolate.Features;
using HotChocolate.Language;

Expand Down Expand Up @@ -47,7 +46,7 @@ public PooledRequestContext()
public override IFeatureCollection Features => _features;

/// <inheritdoc />
public override IDictionary<string, object?> ContextData { get; } = new ConcurrentDictionary<string, object?>();
public override IDictionary<string, object?> ContextData { get; } = new RequestContextData();

/// <summary>
/// Initializes the request context after renting it from the pool.
Expand Down
Loading
Loading