Skip to content

Commit 60d8bc1

Browse files
tobias-tenglermichaelstaib
authored andcommitted
Warmup new executor before replacing old one (#8068)
1 parent 2ad60cb commit 60d8bc1

17 files changed

+479
-312
lines changed

src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static IRequestExecutorBuilder InitializeOnStartup(
3434
throw new ArgumentNullException(nameof(builder));
3535
}
3636

37-
builder.Services.AddHostedService<ExecutorWarmupService>();
37+
builder.Services.AddHostedService<RequestExecutorWarmupService>();
3838
builder.Services.AddSingleton(new WarmupSchemaTask(builder.Name, keepWarm, warmup));
3939
return builder;
4040
}

src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/ExecutorWarmupService.cs

Lines changed: 0 additions & 96 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.Extensions.Hosting;
2+
3+
namespace HotChocolate.AspNetCore.Warmup;
4+
5+
internal sealed class RequestExecutorWarmupService(
6+
IRequestExecutorWarmup executorWarmup)
7+
: IHostedService
8+
{
9+
public async Task StartAsync(CancellationToken cancellationToken)
10+
=> await executorWarmup.WarmupAsync(cancellationToken).ConfigureAwait(false);
11+
12+
public Task StopAsync(CancellationToken cancellationToken)
13+
=> Task.CompletedTask;
14+
}

src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/WarmupSchemaTask.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/HotChocolate/AspNetCore/test/AspNetCore.Tests/EvictSchemaTests.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
using HotChocolate.AspNetCore.Tests.Utilities;
2+
using HotChocolate.Execution;
3+
using Microsoft.Extensions.DependencyInjection;
24

35
namespace HotChocolate.AspNetCore;
46

5-
public class EvictSchemaTests : ServerTestBase
7+
public class EvictSchemaTests(TestServerFactory serverFactory) : ServerTestBase(serverFactory)
68
{
7-
public EvictSchemaTests(TestServerFactory serverFactory)
8-
: base(serverFactory)
9-
{
10-
}
11-
129
[Fact]
1310
public async Task Evict_Default_Schema()
1411
{
1512
// arrange
13+
var newExecutorCreatedResetEvent = new AutoResetEvent(false);
1614
var server = CreateStarWarsServer();
1715

1816
var time1 = await server.GetAsync(
1917
new ClientQueryRequest { Query = "{ time }", });
2018

19+
var resolver = server.Services.GetRequiredService<IRequestExecutorResolver>();
20+
resolver.Events.Subscribe(new RequestExecutorEventObserver(@event =>
21+
{
22+
if (@event.Type == RequestExecutorEventType.Created)
23+
{
24+
newExecutorCreatedResetEvent.Set();
25+
}
26+
}));
27+
2128
// act
2229
await server.GetAsync(
2330
new ClientQueryRequest { Query = "{ evict }", });
31+
newExecutorCreatedResetEvent.WaitOne(5000);
2432

2533
// assert
2634
var time2 = await server.GetAsync(
@@ -32,16 +40,27 @@ await server.GetAsync(
3240
public async Task Evict_Named_Schema()
3341
{
3442
// arrange
43+
var newExecutorCreatedResetEvent = new AutoResetEvent(false);
3544
var server = CreateStarWarsServer();
3645

3746
var time1 = await server.GetAsync(
3847
new ClientQueryRequest { Query = "{ time }", },
3948
"/evict");
4049

50+
var resolver = server.Services.GetRequiredService<IRequestExecutorResolver>();
51+
resolver.Events.Subscribe(new RequestExecutorEventObserver(@event =>
52+
{
53+
if (@event.Type == RequestExecutorEventType.Created)
54+
{
55+
newExecutorCreatedResetEvent.Set();
56+
}
57+
}));
58+
4159
// act
4260
await server.GetAsync(
4361
new ClientQueryRequest { Query = "{ evict }", },
4462
"/evict");
63+
newExecutorCreatedResetEvent.WaitOne(5000);
4564

4665
// assert
4766
var time2 = await server.GetAsync(

src/HotChocolate/Core/src/Execution/AutoUpdateRequestExecutorProxy.cs

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ private AutoUpdateRequestExecutorProxy(
2121
_executorProxy = requestExecutorProxy;
2222
_executor = initialExecutor;
2323

24-
_executorProxy.ExecutorEvicted += (_, _) => BeginUpdateExecutor();
25-
26-
BeginUpdateExecutor();
24+
_executorProxy.ExecutorUpdated += (_, args) => _executor = args.Executor;
2725
}
2826

2927
/// <summary>
@@ -144,26 +142,6 @@ public Task<IResponseStream> ExecuteBatchAsync(
144142
CancellationToken cancellationToken = default)
145143
=> _executor.ExecuteBatchAsync(requestBatch, cancellationToken);
146144

147-
private void BeginUpdateExecutor()
148-
=> UpdateExecutorAsync().FireAndForget();
149-
150-
private async ValueTask UpdateExecutorAsync()
151-
{
152-
await _semaphore.WaitAsync().ConfigureAwait(false);
153-
154-
try
155-
{
156-
var executor = await _executorProxy
157-
.GetRequestExecutorAsync(CancellationToken.None)
158-
.ConfigureAwait(false);
159-
_executor = executor;
160-
}
161-
finally
162-
{
163-
_semaphore.Release();
164-
}
165-
}
166-
167145
/// <inheritdoc cref="IDisposable" />
168146
public void Dispose()
169147
{

src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,8 @@ internal static IServiceCollection TryAddRequestExecutorResolver(
154154
this IServiceCollection services)
155155
{
156156
services.TryAddSingleton<RequestExecutorResolver>();
157-
services.TryAddSingleton<IRequestExecutorResolver>(
158-
sp => sp.GetRequiredService<RequestExecutorResolver>());
159-
services.TryAddSingleton<IInternalRequestExecutorResolver>(
160-
sp => sp.GetRequiredService<RequestExecutorResolver>());
157+
services.TryAddSingleton<IRequestExecutorResolver>(sp => sp.GetRequiredService<RequestExecutorResolver>());
158+
services.TryAddSingleton<IRequestExecutorWarmup>(sp => sp.GetRequiredService<RequestExecutorResolver>());
161159
return services;
162160
}
163161

src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
<ItemGroup>
1414
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
15+
<InternalsVisibleTo Include="HotChocolate.AspNetCore" />
1516
<InternalsVisibleTo Include="HotChocolate.AspNetCore.Tests" />
1617
<InternalsVisibleTo Include="HotChocolate.Caching" />
1718
<InternalsVisibleTo Include="HotChocolate.CostAnalysis" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace HotChocolate.Execution;
2+
3+
/// <summary>
4+
/// Allows to run the initial warmup for registered <see cref="IRequestExecutor"/>s.
5+
/// </summary>
6+
internal interface IRequestExecutorWarmup
7+
{
8+
/// <summary>
9+
/// Runs the initial warmup tasks.
10+
/// </summary>
11+
/// <param name="cancellationToken">
12+
/// The cancellation token.
13+
/// </param>
14+
/// <returns>
15+
/// Returns a task that completes once the warmup is done.
16+
/// </returns>
17+
Task WarmupAsync(CancellationToken cancellationToken);
18+
}

src/HotChocolate/Core/src/Execution/Internal/IInternalRequestExecutorResolver.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/HotChocolate/Core/src/Execution/RequestExecutorProxy.cs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public RequestExecutorProxy(IRequestExecutorResolver executorResolver, string sc
2929
_schemaName = schemaName;
3030
_eventSubscription =
3131
_executorResolver.Events.Subscribe(
32-
new ExecutorObserver(EvictRequestExecutor));
32+
new RequestExecutorEventObserver(OnRequestExecutorEvent));
3333
}
3434

3535
public IRequestExecutor? CurrentExecutor => _executor;
@@ -178,22 +178,40 @@ public async ValueTask<IRequestExecutor> GetRequestExecutorAsync(
178178
return executor;
179179
}
180180

181-
private void EvictRequestExecutor(string schemaName)
181+
private void OnRequestExecutorEvent(RequestExecutorEvent @event)
182182
{
183-
if (!_disposed && schemaName.Equals(_schemaName))
183+
if (_disposed || !@event.Name.Equals(_schemaName) || _executor is null)
184+
{
185+
return;
186+
}
187+
188+
if (@event.Type is RequestExecutorEventType.Evicted)
184189
{
185190
_semaphore.Wait();
186191

187192
try
188193
{
189-
_executor = null;
190194
ExecutorEvicted?.Invoke(this, EventArgs.Empty);
191195
}
192196
finally
193197
{
194198
_semaphore.Release();
195199
}
196200
}
201+
else if (@event.Type is RequestExecutorEventType.Created)
202+
{
203+
_semaphore.Wait();
204+
205+
try
206+
{
207+
_executor = @event.Executor;
208+
ExecutorUpdated?.Invoke(this, new RequestExecutorUpdatedEventArgs(@event.Executor));
209+
}
210+
finally
211+
{
212+
_semaphore.Release();
213+
}
214+
}
197215
}
198216

199217
public void Dispose()
@@ -206,19 +224,4 @@ public void Dispose()
206224
_disposed = true;
207225
}
208226
}
209-
210-
private sealed class ExecutorObserver(Action<string> evicted) : IObserver<RequestExecutorEvent>
211-
{
212-
public void OnNext(RequestExecutorEvent value)
213-
{
214-
if (value.Type is RequestExecutorEventType.Evicted)
215-
{
216-
evicted(value.Name);
217-
}
218-
}
219-
220-
public void OnError(Exception error) { }
221-
222-
public void OnCompleted() { }
223-
}
224227
}

0 commit comments

Comments
 (0)