Skip to content
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

Check if parent scopes are disposed when checking scope IsDisposed #1061

Merged
merged 4 commits into from
Dec 22, 2019

Conversation

alistairjevans
Copy link
Member

@alistairjevans alistairjevans commented Dec 19, 2019

Proposed change that fixes #1020 by making disposal exceptions consistently thrown.

This change uses a new IsTreeDisposed method in Disposable, which checks up the tree of parent scopes to see if any are disposed.

You then always get the correct disposal exception thrown if some part of the scope tree is no longer valid.

I've run the benchmarks on the change, and the effect appears to be minimal; I will supply the raw benchmark results on this over the next few days; thought I'd present the solution as an option.

@tillig
Copy link
Member

tillig commented Dec 19, 2019

Thinking you didn't mean 1029 as the issue this fixes. (?)

@alistairjevans
Copy link
Member Author

Ah, no I did not...I blame disconnect between brain and hands.

Edited the comment (here's hoping Github relinks the PR).

@tillig
Copy link
Member

tillig commented Dec 19, 2019

The things I'm afraid of with this (things to test around) are:

  • Memory leaks: if we hold a reference to something that isn't released right, or possibly in the wrong order, then we get a memory leak. A few years back I really donked things up by trying to track references from parent to child scope to auto-dispose children when the parent is disposed... and it messed up some folks with high-throughput apps (high traffic web sites).
  • Locking: Anywhere we have a lock or thread synchronization has killed us on perf. We still get folks complaining about the lock we actually really need to ensure a singleton really does only get instantiated one time.

For example, the child scope checking to see if the parent scope is disposed... if someone does

Thread 1: childScope.Resolve();
Thread 2: parentScope.Dispose();

then without a lock, technically the child could see the parent as not disposed, then the disposal on the parent finishes, then the child still thinks the parent is alive. I'm not super familiar with how MemoryBarrier handles that sort of thing. If it does properly serialize the accesses... then there's a lock, and we should be mindful of how that may impact, say, a site with thousands of concurrent requests where someone has a unit-of-work pattern (another child lifetime scope they create in a controller action).

I mean, I'm sure you've already thought of all that, just figured for clarity that's the stuff I know I think about.

@alistairjevans
Copy link
Member Author

With regards to the memory leak question, we already keep a reference to the parent lifetime scope, so there's no additional objects being tracked.

I've reworked the changes a bit to re-use the existing ParentLifetimeScope reference for the disposal check.

With regards to the memory barrier, the fact we're now repeatedly accessing _isDisposed on the root scope all the time may well add some synchronisation overhead. It's not an actual lock, but it will have an effect.

I'll look at either updating or reusing the concurrency benchmark to get more data on this.

@alistairjevans
Copy link
Member Author

I've run the new nested scope benchmark that simulates the potentially worrying request scopes + unit of work behaviour (after fixing the benchmarks in #1062).

Results are as follows:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
AMD Ryzen 5 2500U with Radeon Vega Mobile Gfx, 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100
  [Host]     : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT
  DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT

Develop

Method ConcurrentRequests RepeatCount Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
MultipleResolvesOnMultipleTasks 100 1000 305.0 ms 6.58 ms 19.31 ms 297.5 ms 347000.0000 - - 688.19 MB

Parent Disposal Check

Method ConcurrentRequests RepeatCount Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
MultipleResolvesOnMultipleTasks 100 1000 304.8 ms 7.45 ms 21.97 ms 294.7 ms 347000.0000 - - 688.19 MB

@alistairjevans
Copy link
Member Author

Rebased onto the latest develop (which includes the immutable changes).

Updated benchmark reports are below. The branch changes come out slightly under, but within the margin of error for the benchmark.

It appears as though the parent checking changes haven't had an obvious impact, particularly if you check the ConcurrencyNestedScopeBenchmark, which was the area of concern.

ChildScopeResolveBenchmark

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 67.05 us 1.281 us 1.620 us 22.4609 - - 45.89 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 64.43 us 1.240 us 1.327 us 22.4609 - - 45.89 KB

ConcurrencyBenchmark

Develop

Method ResolveTaskCount ResolvesPerTask Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
MultipleResolvesOnMultipleTasks 100 100 1.776 ms 0.0355 ms 0.0380 ms 2542.9688 - - 5.05 MB

Changes

Method ResolveTaskCount ResolvesPerTask Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
MultipleResolvesOnMultipleTasks 100 100 1.650 ms 0.0380 ms 0.0422 ms 2542.9688 - - 5.05 MB

ConcurrencyNestedScopeBenchmark

Develop

Method ConcurrentRequests RepeatCount Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ConcurrentRequestsWithUnitOfWorkResolve 500 100 148.2 ms 2.88 ms 4.97 ms 158750.0000 250.0000 - 315.16 MB

Changes

Method ConcurrentRequests RepeatCount Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ConcurrentRequestsWithUnitOfWorkResolve 500 100 143.5 ms 2.82 ms 5.09 ms 158750.0000 250.0000 - 315.16 MB

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.455 us 0.0780 us 0.0730 us 1.5640 - - 3.2 KB
SingleResolve 1.470 us 0.0196 us 0.0174 us 0.6180 - - 1.27 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.333 us 0.0650 us 0.0608 us 1.5640 - - 3.2 KB
SingleResolve 1.396 us 0.0159 us 0.0148 us 0.6180 - - 1.27 KB

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.458 us 0.0796 us 0.0745 us 1.4877 - - 3.04 KB
SingleResolve 1.520 us 0.0169 us 0.0158 us 0.5798 - - 1.19 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.332 us 0.0665 us 0.0622 us 1.4877 - - 3.04 KB
SingleResolve 1.488 us 0.0144 us 0.0135 us 0.5798 - - 1.19 KB

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 3.625 us 0.0506 us 0.0474 us 1.2398 - - 2600 B
SingleResolve 1.169 us 0.0168 us 0.0149 us 0.4578 - - 960 B

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 3.529 us 0.0352 us 0.0312 us 1.2398 - - 2600 B
SingleResolve 1.098 us 0.0117 us 0.0110 us 0.4578 - - 960 B

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 5.028 us 0.0682 us 0.0638 us 2.1057 - - 4.31 KB
SingleResolve 1.791 us 0.0186 us 0.0174 us 0.9136 - - 1.87 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.775 us 0.0791 us 0.0740 us 2.1057 - - 4.31 KB
SingleResolve 1.709 us 0.0257 us 0.0240 us 0.9136 - - 1.87 KB

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 8.062 us 0.1498 us 0.1401 us 3.5400 - - 7.25 KB
SingleResolve 3.356 us 0.0886 us 0.1270 us 1.6594 - - 3.39 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 7.769 us 0.1134 us 0.1060 us 3.5400 - - 7.25 KB
SingleResolve 3.243 us 0.0646 us 0.0604 us 1.6594 - - 3.39 KB

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 7.206 us 0.0946 us 0.0838 us 2.9831 - - 6.09 KB
SingleResolve 2.870 us 0.0523 us 0.0489 us 1.3771 - - 2.81 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 6.684 us 0.1032 us 0.0965 us 2.9831 - - 6.09 KB
SingleResolve 2.820 us 0.0785 us 0.0656 us 1.3771 - - 2.81 KB

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 5.182 us 0.0638 us 0.0566 us 2.1896 - - 4.48 KB
SingleResolve 1.982 us 0.0391 us 0.0366 us 0.9804 - - 2.01 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.974 us 0.0740 us 0.0692 us 2.1896 - - 4.48 KB
SingleResolve 1.951 us 0.0381 us 0.0424 us 0.9804 - - 2.01 KB

Decorators

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.839 us 0.0882 us 0.0825 us 1.9073 - - 3.91 KB
SingleResolve 1.839 us 0.0352 us 0.0345 us 0.8411 - - 1.72 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
EnumerableResolve 4.737 us 0.0895 us 0.0793 us 1.9073 - - 3.91 KB
SingleResolve 1.788 us 0.0355 us 0.0395 us 0.8411 - - 1.72 KB

DeepGraphResolveBenchmark

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 21.78 us 0.386 us 0.361 us 9.2468 - - 18.93 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 21.51 us 0.277 us 0.231 us 9.2468 - - 18.93 KB

EnumerableResolveBenchmark

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ResolveIEnumerable 5.120 us 0.0959 us 0.0897 us 1.7929 - - 3.7 KB
ResolveIReadOnlyList 5.059 us 0.0742 us 0.0694 us 1.8234 - - 3.73 KB
ResolveArray 5.245 us 0.0840 us 0.0786 us 1.8082 - - 3.7 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ResolveIEnumerable 5.079 us 0.0702 us 0.0622 us 1.7929 - - 3.7 KB
ResolveIReadOnlyList 4.864 us 0.0942 us 0.1321 us 1.8082 - - 3.73 KB
ResolveArray 5.012 us 0.0796 us 0.0745 us 1.7929 - - 3.7 KB

OpenGenericBenchmark

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ResolveOpenGeneric 725.5 ns 11.84 ns 10.50 ns 0.3366 - - 704 B

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ResolveOpenGeneric 718.2 ns 9.11 ns 8.52 ns 0.3366 - - 704 B

PropertyInjectionBenchmark

Develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 7.841 us 0.1473 us 0.1513 us 3.1281 - - 6.41 KB

Changes

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 7.401 us 0.0862 us 0.0807 us 3.1357 - - 6.41 KB

RootContainerResolveBenchmark

Develop

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
OperatorNew 4.254 ns 0.1147 ns 0.0958 ns 1.00 0.00 0.0115 - - 24 B
NonSharedReflectionResolve 714.074 ns 12.0385 ns 11.2608 ns 167.78 4.61 0.3366 - - 704 B
NonSharedDelegateResolve 540.698 ns 10.7129 ns 13.9298 ns 127.79 4.27 0.2632 - - 552 B
SharedResolve 383.081 ns 6.7296 ns 6.2949 ns 90.25 2.43 0.2522 - - 528 B

Changes

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
OperatorNew 4.242 ns 0.1737 ns 0.3508 ns 1.00 0.00 0.0115 - - 24 B
NonSharedReflectionResolve 691.555 ns 6.1444 ns 5.4468 ns 154.72 14.53 0.3366 - - 704 B
NonSharedDelegateResolve 521.804 ns 10.1893 ns 9.5310 ns 116.49 11.45 0.2632 - - 552 B
SharedResolve 369.793 ns 4.1170 ns 3.6496 ns 82.73 7.73 0.2522 - - 528 B

Copy link
Member

@tillig tillig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work once again. Thanks!

@tillig tillig merged commit 852cafd into autofac:develop Dec 22, 2019
@alistairjevans alistairjevans deleted the parent-dispose-check branch December 22, 2019 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ObjectDisposedException in child lifetime scopes of lifetimeScope activated with configurationAction
2 participants