Skip to content

Commit 75ca161

Browse files
committed
Refine #1446 tests and add module registration benchmark
- Remove AAA comments from module tests; reference #1446 in each - Align test names with the file's existing convention - Add independence guards: overriding one hook must not subscribe to the other event - Add ModuleRegistrationBenchmark measuring container build cost vs module count
1 parent 434c93c commit 75ca161

3 files changed

Lines changed: 117 additions & 31 deletions

File tree

bench/Autofac.Benchmarks/BenchmarkSet.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ public static class BenchmarkSet
3333
typeof(MultiConstructorBenchmark),
3434
typeof(LambdaResolveBenchmark),
3535
typeof(RequiredPropertyBenchmark),
36+
typeof(ModuleRegistrationBenchmark),
3637
};
3738
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace Autofac.Benchmarks;
5+
6+
/// <summary>
7+
/// Measures the cost of building a container that registers many modules which do
8+
/// not override <see cref="Module.AttachToComponentRegistration"/> or
9+
/// <see cref="Module.AttachToRegistrationSource"/>.
10+
/// </summary>
11+
/// <remarks>
12+
/// See https://github.com/autofac/Autofac/issues/1446. Prior to the fix, every module
13+
/// unconditionally subscribed to the registry's <c>Registered</c> and
14+
/// <c>RegistrationSourceAdded</c> events. Each subscription replays all existing
15+
/// registrations, so build time and allocations grew quadratically with the number of
16+
/// modules. Modules that do not override the hooks no longer subscribe, removing the
17+
/// quadratic behavior. Compare against a baseline package version (for example
18+
/// <c>--baseline-version 9.1.0</c>) to see the difference.
19+
/// </remarks>
20+
public class ModuleRegistrationBenchmark
21+
{
22+
[Params(100, 1000)]
23+
public int ModuleCount
24+
{
25+
get; set;
26+
}
27+
28+
[Benchmark]
29+
public void BuildContainerWithModules()
30+
{
31+
var builder = new ContainerBuilder();
32+
33+
for (var i = 0; i < ModuleCount; i++)
34+
{
35+
builder.RegisterModule<NoHookModule>();
36+
}
37+
38+
using var container = builder.Build();
39+
GC.KeepAlive(container);
40+
}
41+
42+
// A module that overrides neither hook - the common case from issue #1446.
43+
private sealed class NoHookModule : Module
44+
{
45+
protected override void Load(ContainerBuilder builder)
46+
{
47+
builder.RegisterType<Service>().As<IService>();
48+
}
49+
50+
private interface IService
51+
{
52+
}
53+
54+
private sealed class Service : IService
55+
{
56+
}
57+
}
58+
}

test/Autofac.Test/ModuleTests.cs

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ public void CanUseBuilderPropertyBag()
206206
Assert.Equal("value", builder.Properties["prop"]);
207207
}
208208

209-
// A module that overrides neither hook (the common case that caused the performance issue).
209+
// #1446: A module that overrides neither hook (the common case that caused the
210+
// O(N^2) performance issue) must not subscribe to the registry events at all.
210211
internal class NoHookModule : Module
211212
{
212213
protected override void Load(ContainerBuilder builder)
@@ -216,38 +217,35 @@ protected override void Load(ContainerBuilder builder)
216217
}
217218

218219
[Fact]
219-
public void NoHookModule_DoesNotSubscribeToRegisteredEvent()
220+
public void ModuleWithoutHookDoesNotSubscribeToRegisteredEvent()
220221
{
221-
// Arrange
222+
// #1446: Subscribing replays every existing registration, so a module that
223+
// does not override AttachToComponentRegistration must not subscribe.
222224
using var registryBuilder = Factory.CreateEmptyComponentRegistryBuilder();
223225

224-
// Act
225226
new NoHookModule().Configure(registryBuilder);
226227

227-
// Assert: the Registered event delegate stored in Properties should be null
228-
// (no module subscription was added), meaning the key is either absent or null.
229228
var hasRegisteredHandler =
230229
registryBuilder.Properties.TryGetValue(MetadataKeys.RegisteredPropertyKey, out var registeredValue)
231230
&& registeredValue is not null;
232231

233-
Assert.False(hasRegisteredHandler, "A module that does not override AttachToComponentRegistration should not subscribe to the Registered event.");
232+
Assert.False(hasRegisteredHandler);
234233
}
235234

236235
[Fact]
237-
public void NoHookModule_DoesNotSubscribeToRegistrationSourceAddedEvent()
236+
public void ModuleWithoutHookDoesNotSubscribeToRegistrationSourceAddedEvent()
238237
{
239-
// Arrange
238+
// #1446: A module that does not override AttachToRegistrationSource must not
239+
// subscribe to the RegistrationSourceAdded event.
240240
using var registryBuilder = Factory.CreateEmptyComponentRegistryBuilder();
241241

242-
// Act
243242
new NoHookModule().Configure(registryBuilder);
244243

245-
// Assert: the RegistrationSourceAdded event delegate stored in Properties should be null.
246244
var hasSourceHandler =
247245
registryBuilder.Properties.TryGetValue(MetadataKeys.RegistrationSourceAddedPropertyKey, out var sourceValue)
248246
&& sourceValue is not null;
249247

250-
Assert.False(hasSourceHandler, "A module that does not override AttachToRegistrationSource should not subscribe to the RegistrationSourceAdded event.");
248+
Assert.False(hasSourceHandler);
251249
}
252250

253251
// A module that overrides only AttachToComponentRegistration.
@@ -264,26 +262,24 @@ protected override void AttachToComponentRegistration(
264262
}
265263

266264
[Fact]
267-
public void ComponentRegistrationHookModule_SubscribesToRegisteredEvent()
265+
public void ModuleOverridingComponentRegistrationHookSubscribesToRegisteredEvent()
268266
{
269-
// Arrange
267+
// #1446: A module that overrides AttachToComponentRegistration must still subscribe.
270268
using var registryBuilder = Factory.CreateEmptyComponentRegistryBuilder();
271269

272-
// Act
273270
new ComponentRegistrationHookModule().Configure(registryBuilder);
274271

275-
// Assert: the Registered event delegate stored in Properties should be non-null.
276272
var hasRegisteredHandler =
277273
registryBuilder.Properties.TryGetValue(MetadataKeys.RegisteredPropertyKey, out var registeredValue)
278274
&& registeredValue is not null;
279275

280-
Assert.True(hasRegisteredHandler, "A module that overrides AttachToComponentRegistration must subscribe to the Registered event.");
276+
Assert.True(hasRegisteredHandler);
281277
}
282278

283279
[Fact]
284-
public void ComponentRegistrationHookModule_HookIsCalledForSubsequentRegistrations()
280+
public void ModuleOverridingComponentRegistrationHookStillReceivesRegistrations()
285281
{
286-
// Verify the hook is still called when the override is present (regression guard).
282+
// #1446: regression guard - the overridden hook must still fire for registrations.
287283
var module = new ComponentRegistrationHookModule();
288284
var builder = new ContainerBuilder();
289285
builder.RegisterModule(module);
@@ -294,6 +290,22 @@ public void ComponentRegistrationHookModule_HookIsCalledForSubsequentRegistratio
294290
Assert.NotEmpty(module.Seen);
295291
}
296292

293+
[Fact]
294+
public void ModuleOverridingOnlyComponentRegistrationHookDoesNotSubscribeToRegistrationSourceAddedEvent()
295+
{
296+
// #1446: the two detection paths are independent - overriding only the
297+
// component-registration hook must not subscribe to the source-added event.
298+
using var registryBuilder = Factory.CreateEmptyComponentRegistryBuilder();
299+
300+
new ComponentRegistrationHookModule().Configure(registryBuilder);
301+
302+
var hasSourceHandler =
303+
registryBuilder.Properties.TryGetValue(MetadataKeys.RegistrationSourceAddedPropertyKey, out var sourceValue)
304+
&& sourceValue is not null;
305+
306+
Assert.False(hasSourceHandler);
307+
}
308+
297309
// A module that overrides only AttachToRegistrationSource.
298310
internal class RegistrationSourceHookModule : Module
299311
{
@@ -308,20 +320,34 @@ protected override void AttachToRegistrationSource(
308320
}
309321

310322
[Fact]
311-
public void RegistrationSourceHookModule_SubscribesToRegistrationSourceAddedEvent()
323+
public void ModuleOverridingRegistrationSourceHookSubscribesToRegistrationSourceAddedEvent()
312324
{
313-
// Arrange
325+
// #1446: A module that overrides AttachToRegistrationSource must still subscribe.
314326
using var registryBuilder = Factory.CreateEmptyComponentRegistryBuilder();
315327

316-
// Act
317328
new RegistrationSourceHookModule().Configure(registryBuilder);
318329

319-
// Assert: the RegistrationSourceAdded event delegate stored in Properties should be non-null.
320330
var hasSourceHandler =
321331
registryBuilder.Properties.TryGetValue(MetadataKeys.RegistrationSourceAddedPropertyKey, out var sourceValue)
322332
&& sourceValue is not null;
323333

324-
Assert.True(hasSourceHandler, "A module that overrides AttachToRegistrationSource must subscribe to the RegistrationSourceAdded event.");
334+
Assert.True(hasSourceHandler);
335+
}
336+
337+
[Fact]
338+
public void ModuleOverridingOnlyRegistrationSourceHookDoesNotSubscribeToRegisteredEvent()
339+
{
340+
// #1446: the two detection paths are independent - overriding only the
341+
// source-added hook must not subscribe to the component-registered event.
342+
using var registryBuilder = Factory.CreateEmptyComponentRegistryBuilder();
343+
344+
new RegistrationSourceHookModule().Configure(registryBuilder);
345+
346+
var hasRegisteredHandler =
347+
registryBuilder.Properties.TryGetValue(MetadataKeys.RegisteredPropertyKey, out var registeredValue)
348+
&& registeredValue is not null;
349+
350+
Assert.False(hasRegisteredHandler);
325351
}
326352

327353
internal class NullRegistrationSource : IRegistrationSource
@@ -335,9 +361,9 @@ public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Fun
335361
}
336362

337363
[Fact]
338-
public void RegistrationSourceHookModule_HookIsCalledWhenSourceIsAdded()
364+
public void ModuleOverridingRegistrationSourceHookStillReceivesSources()
339365
{
340-
// Verify the hook is still called when the override is present (regression guard).
366+
// #1446: regression guard - the overridden hook must still fire for sources.
341367
var module = new RegistrationSourceHookModule();
342368
var builder = new ContainerBuilder();
343369
builder.RegisterModule(module);
@@ -371,10 +397,10 @@ protected override void Load(ContainerBuilder builder)
371397
}
372398

373399
[Fact]
374-
public void IntermediateBaseOverride_HookIsCalledForConcreteSubclass()
400+
public void ModuleInheritingHookFromIntermediateBaseStillReceivesRegistrations()
375401
{
376-
// A module that inherits the override from an intermediate class must still
377-
// have its hook invoked (DeclaringType != typeof(Module) covers this case).
402+
// #1446: the override-detection uses DeclaringType != typeof(Module), so an
403+
// override inherited from an intermediate base class must still fire.
378404
var module = new ConcreteIntermediateModule();
379405
var builder = new ContainerBuilder();
380406
builder.RegisterModule(module);
@@ -386,8 +412,9 @@ public void IntermediateBaseOverride_HookIsCalledForConcreteSubclass()
386412
}
387413

388414
[Fact]
389-
public void IntermediateBaseOverride_SubscribesToRegisteredEvent()
415+
public void ModuleInheritingHookFromIntermediateBaseSubscribesToRegisteredEvent()
390416
{
417+
// #1446: a module whose override is declared on an intermediate base class must subscribe.
391418
using var registryBuilder = Factory.CreateEmptyComponentRegistryBuilder();
392419

393420
new ConcreteIntermediateModule().Configure(registryBuilder);
@@ -396,7 +423,7 @@ public void IntermediateBaseOverride_SubscribesToRegisteredEvent()
396423
registryBuilder.Properties.TryGetValue(MetadataKeys.RegisteredPropertyKey, out var registeredValue)
397424
&& registeredValue is not null;
398425

399-
Assert.True(hasRegisteredHandler, "A module with an override declared on an intermediate base class must still subscribe to the Registered event.");
426+
Assert.True(hasRegisteredHandler);
400427
}
401428

402429
private class Service1

0 commit comments

Comments
 (0)