-
-
Notifications
You must be signed in to change notification settings - Fork 86
Fix shared instance disposal using ObjectTracker reference counting at registration time #2977
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
Conversation
@copilot Nope this is the wrong approach. When a test starts, it should use ObjectTracker to count it's injected data (constructor args, method args) etc. If different tests reference the same instances, then the counts for those objects will be 2, 3, 4, etc. This shows how many tests an object is active in, and so we cannot dispose it while there is still a test pending or running that depends on that object. When the test ends, the object tracker should decrement the count, telling the framework that that test is done and doesn't need that object anymore. When that count reaches 0, we know that no more tests are going to be using that object, and at that point we call dispose. |
I understand the reference counting approach you described. I've reverted my scope-based changes and I'm now investigating the actual issue with the ObjectTracker reference counting system. The current flow should be:
I've added debug logging to ObjectTracker and SingleTestExecutor to diagnose where the reference counting might be failing. The logic appears correct but something isn't working as expected for shared instances (PerClass, PerAssembly, PerTestSession). Will implement the fix based on the reference counting approach once I identify the root cause. Commit: 37d89d7 |
@copilot I think the tracking should actually occur closer to injection time. For constructor or method args, we should track these when we call "Test Registered" - as these are the tests that we're going to execute in the test run. And we need to make sure the property injector class also calls track object when it creates and injects in all its properties, including nested ones. Then we need to make sure that the count is decremented after the test finishes, or after the test is skipped, as we're essentially notifying the framework that this object is no longer being used by this test. |
I've moved the object tracking to injection/registration time as requested:
The flow now correctly follows the reference counting model:
Commit: eb1c278 |
@claude undo the changes to global.json |
Claude finished @thomhurst's task —— View job Undoing global.json changes ✅
Completed: Reverted global.json back to .NET SDK version "9.0.304" from "8.0.119". The change was originally made in a dependency update (#2940) but was inadvertently reverted in commit 37d89d7. The proper version has now been restored. |
@claude remove the added debug logging |
Claude finished @thomhurst's task —— View job Removing debug logging ✅
Completed: Successfully removed all debug logging
All changes have been committed and pushed to the |
@claude analyze in ObjectTracker if we also need to perform the decrement logic in the OnTestSkipped event too. Make sure this wouldn't cause duplicate calls to decrement from the same test. |
Claude finished @thomhurst's task —— View job Analyzing ObjectTracker for OnTestSkipped event
Analysis SummaryThe current implementation correctly handles both normal and skipped tests without duplicate decrements. Current Flow:
Key Finding: No OnTestSkipped event handler neededThe ObjectTracker disposal logic is properly triggered in both scenarios through the
Duplicate Prevention:
Recommendation: No changes needed. The current implementation correctly handles shared instance disposal for both completed and skipped tests without duplicate reference counting. |
Problem
Shared instances injected with
[ClassDataSource<T>(Shared = SharedType.PerClass)]
,SharedType.PerAssembly
, orSharedType.PerTestSession
were not having theirDisposeAsync()
orDispose()
methods called, leading to resource leaks. This affected test infrastructure likeWebApplicationFactory
, database containers, and other disposable shared resources.Root Cause
The framework has an existing ObjectTracker system with reference counting, but shared instances needed to be tracked at the correct lifecycle points to ensure proper disposal timing. The original implementation was tracking objects both during test building and test execution, causing double-counting that prevented proper disposal.
Solution
Implemented proper reference counting for shared instances by tracking objects at injection time during the TestRegistered event phase:
TestArgumentTrackingService
Key Changes
ITestRegisteredEventReceiver
that tracks constructor and method arguments during test registrationSingleTestExecutor
that was preventing proper disposalPropertyInjectionService
continues to track injected properties and nested objectsReference Counting Flow
For a shared instance used by 2 tests:
Result
Now shared instances are properly disposed when all tests using them complete:
This maintains behavioral parity between source-generated and reflection modes while ensuring proper resource cleanup for shared test infrastructure.
Fixes #2867.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.