Skip to content

Commit f24968c

Browse files
tobias-tenglermichaelstaib
authored andcommitted
Ensure TypeModuleChangeMonitor is properly disposed (#8041)
1 parent ae922c8 commit f24968c

File tree

2 files changed

+76
-38
lines changed

2 files changed

+76
-38
lines changed

src/HotChocolate/Core/src/Execution/RequestExecutorResolver.cs

Lines changed: 23 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,28 @@ await _optionsMonitor.GetAsync(schemaName, cancellationToken)
9696
setup.SchemaBuilder ?? new SchemaBuilder(),
9797
_applicationServices);
9898

99+
100+
var typeModuleChangeMonitor = new TypeModuleChangeMonitor(this, context.SchemaName);
101+
102+
// if there are any type modules we will register them with the
103+
// type module change monitor.
104+
// The module will track if type modules signal changes to the schema and
105+
// start a schema eviction.
106+
foreach (var typeModule in setup.TypeModules)
107+
{
108+
typeModuleChangeMonitor.Register(typeModule);
109+
}
110+
99111
var schemaServices =
100-
await CreateSchemaServicesAsync(context, setup, cancellationToken)
112+
await CreateSchemaServicesAsync(context, setup, typeModuleChangeMonitor, cancellationToken)
101113
.ConfigureAwait(false);
102114

103115
registeredExecutor = new RegisteredExecutor(
104116
schemaServices.GetRequiredService<IRequestExecutor>(),
105117
schemaServices,
106118
schemaServices.GetRequiredService<IExecutionDiagnosticEvents>(),
107119
setup,
108-
schemaServices.GetRequiredService<TypeModuleChangeMonitor>());
120+
typeModuleChangeMonitor);
109121

110122
var executor = registeredExecutor.Executor;
111123

@@ -131,24 +143,26 @@ public void EvictRequestExecutor(string? schemaName = default)
131143
{
132144
schemaName ??= Schema.DefaultName;
133145

134-
if (_executors.TryRemove(schemaName, out var re))
146+
if (_executors.TryRemove(schemaName, out var executor))
135147
{
136-
re.DiagnosticEvents.ExecutorEvicted(schemaName, re.Executor);
148+
executor.DiagnosticEvents.ExecutorEvicted(schemaName, executor.Executor);
137149

138150
try
139151
{
152+
executor.TypeModuleChangeMonitor.Dispose();
153+
140154
RequestExecutorEvicted?.Invoke(
141155
this,
142-
new RequestExecutorEvictedEventArgs(schemaName, re.Executor));
156+
new RequestExecutorEvictedEventArgs(schemaName, executor.Executor));
143157
_events.RaiseEvent(
144158
new RequestExecutorEvent(
145159
RequestExecutorEventType.Evicted,
146160
schemaName,
147-
re.Executor));
161+
executor.Executor));
148162
}
149163
finally
150164
{
151-
BeginRunEvictionEvents(re);
165+
BeginRunEvictionEvents(executor);
152166
}
153167
}
154168
}
@@ -157,26 +171,7 @@ private void EvictAllRequestExecutors()
157171
{
158172
foreach (var key in _executors.Keys)
159173
{
160-
if (_executors.TryRemove(key, out var re))
161-
{
162-
re.DiagnosticEvents.ExecutorEvicted(key, re.Executor);
163-
164-
try
165-
{
166-
RequestExecutorEvicted?.Invoke(
167-
this,
168-
new RequestExecutorEvictedEventArgs(key, re.Executor));
169-
_events.RaiseEvent(
170-
new RequestExecutorEvent(
171-
RequestExecutorEventType.Evicted,
172-
key,
173-
re.Executor));
174-
}
175-
finally
176-
{
177-
BeginRunEvictionEvents(re);
178-
}
179-
}
174+
EvictRequestExecutor(key);
180175
}
181176
}
182177

@@ -201,6 +196,7 @@ private static async Task RunEvictionEvents(RegisteredExecutor registeredExecuto
201196
private async Task<IServiceProvider> CreateSchemaServicesAsync(
202197
ConfigurationContext context,
203198
RequestExecutorSetup setup,
199+
TypeModuleChangeMonitor typeModuleChangeMonitor,
204200
CancellationToken cancellationToken)
205201
{
206202
ulong version;
@@ -211,22 +207,12 @@ private async Task<IServiceProvider> CreateSchemaServicesAsync(
211207
}
212208

213209
var serviceCollection = new ServiceCollection();
214-
var typeModuleChangeMonitor = new TypeModuleChangeMonitor(this, context.SchemaName);
215210
var lazy = new SchemaBuilder.LazySchema();
216211

217212
var executorOptions =
218213
await OnConfigureRequestExecutorOptionsAsync(context, setup, cancellationToken)
219214
.ConfigureAwait(false);
220215

221-
// if there are any type modules we will register them with the
222-
// type module change monitor.
223-
// The module will track if type modules signal changes to the schema and
224-
// start a schema eviction.
225-
foreach (var typeModule in setup.TypeModules)
226-
{
227-
typeModuleChangeMonitor.Register(typeModule);
228-
}
229-
230216
// we allow newer type modules to apply configurations.
231217
await typeModuleChangeMonitor.ConfigureAsync(context, cancellationToken)
232218
.ConfigureAwait(false);
@@ -241,7 +227,6 @@ await typeModuleChangeMonitor.ConfigureAsync(context, cancellationToken)
241227
setup.DefaultPipelineFactory,
242228
setup.Pipeline));
243229

244-
serviceCollection.AddSingleton(typeModuleChangeMonitor);
245230
serviceCollection.AddSingleton(executorOptions);
246231
serviceCollection.AddSingleton<IRequestExecutorOptionsAccessor>(
247232
static s => s.GetRequiredService<RequestExecutorOptions>());

src/HotChocolate/Core/test/Execution.Tests/Configuration/TypeModuleTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using HotChocolate.Types.Descriptors;
44
using HotChocolate.Types.Descriptors.Definitions;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Hosting;
67

78
namespace HotChocolate.Execution.Configuration;
89

@@ -64,6 +65,58 @@ public async Task Use_Type_Module_From_Factory()
6465
.MatchSnapshotAsync();
6566
}
6667

68+
[Fact]
69+
public async Task Ensure_Warmups_Are_Triggered_An_Appropriate_Number_Of_Times()
70+
{
71+
// arrange
72+
var typeModule = new TriggerableTypeModule();
73+
var warmups = 0;
74+
var resetEvent = new AutoResetEvent(false);
75+
76+
var services = new ServiceCollection();
77+
services
78+
.AddGraphQL()
79+
.AddTypeModule(_ => typeModule)
80+
.InitializeOnStartup(keepWarm: true, warmup: (_, _) =>
81+
{
82+
warmups++;
83+
resetEvent.Set();
84+
return Task.CompletedTask;
85+
})
86+
.AddQueryType(d => d.Field("foo").Resolve(""));
87+
var provider = services.BuildServiceProvider();
88+
var warmupService = provider.GetRequiredService<IHostedService>();
89+
90+
using var cts = new CancellationTokenSource();
91+
_ = Task.Run(async () =>
92+
{
93+
await warmupService.StartAsync(CancellationToken.None);
94+
}, cts.Token);
95+
96+
var resolver = provider.GetRequiredService<IRequestExecutorResolver>();
97+
98+
await resolver.GetRequestExecutorAsync(null, cts.Token);
99+
100+
// act
101+
// assert
102+
typeModule.TriggerChange();
103+
resetEvent.WaitOne();
104+
105+
// 2 since we have the initial warmup at "startup" and the one triggered above.
106+
Assert.Equal(2, warmups);
107+
108+
resetEvent.Reset();
109+
typeModule.TriggerChange();
110+
resetEvent.WaitOne();
111+
112+
Assert.Equal(3, warmups);
113+
}
114+
115+
private sealed class TriggerableTypeModule : TypeModule
116+
{
117+
public void TriggerChange() => OnTypesChanged();
118+
}
119+
67120
public class DummyTypeModule : ITypeModule
68121
{
69122
#pragma warning disable CS0067

0 commit comments

Comments
 (0)