-
-
Notifications
You must be signed in to change notification settings - Fork 840
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
Memory allocation allocation tracker test helper #2082
Conversation
@antonfirsov It would be useful if you could confirm I'm not doing anything too critical/stupid fiddling with the |
catch | ||
{ | ||
this.Frame?.Dispose(); | ||
throw; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to do a try-catch here I believe as it is handled by the caller here:
ImageSharp/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
Lines 19 to 26 in 136cc14
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) | |
where TPixel : unmanaged, IPixel<TPixel> | |
{ | |
Guard.NotNull(stream, nameof(stream)); | |
using var decoder = new JpegDecoderCore(configuration, this); | |
return decoder.Decode<TPixel>(configuration, stream, cancellationToken); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah looks like you are right... I think I must have added it on first pass trying to find the leaky code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's very tricky because jpeg is the only decoder which has a disposable internal DecoderCore
implementation :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make it so it’s doesn’t implement IDisposable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... Considering it's only used for Frame
field which is a class field for the sake of not passing it to a bunch of methods we can make it non-disposable for consistency.
If you do so, try-catch must be replaced by try-finally or Frame
would leak in a no-exception scenario. Also don't forget about Identify
method - it also creates a Frame
field so it should have a proper try-finally too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Ace, that’s what I thought. That can be a separate PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah this is fantastic! Exactly what we need. 😍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ValidateDisposedMemoryAllocationsAttribute
is a blessing, also cool that current coverage reports no leaks 😎
no leaks (anymore) in the few tests I added it to anyway :D There will be a need to follow up adding this to a lot more of the test... I didn't want to blow out this PR fixing leaks hiding the framework changes. Despite this being approved I think its worth leave this one open a bit longer before merging to give anyone with more knowledge on how my changed to the |
Codecov Report
@@ Coverage Diff @@
## main #2082 +/- ##
=====================================
Coverage 88% 88%
=====================================
Files 990 990
Lines 52619 52619
Branches 6617 6617
=====================================
Hits 46443 46443
Misses 5083 5083
Partials 1093 1093
Flags with carried forward coverage won't be shown. Click here to find out more. Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to separate the AsyncLocal
test-scoped validation from the global MemoryDiagnostics.TotalUndisposedAllocationCount
counter.
/// <summary> | ||
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer. | ||
/// </summary> | ||
public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount; | ||
public static int TotalUndisposedAllocationCount => Current.TotalUndisposedAllocationCount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TotalUndisposedAllocationCount
was intended to report global allocation count. If I get the docs of AsyncLocal<T>
right, the PR changes the semantics, so it becomes specific to an asynchronous execution flow, and will reset outside of the flow. I don't think this public behavior change is beneficial for the users of MemoryDiagnostics.TotalUndisposedAllocationCount
. We need a separate API for MemoryAllocatorValidator
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It way its setup it will only report the local context when its been overwritten in tests (internal api) by default the backing fields are backs by the MemoryDiagnostics.Default
static instance which will be used in production usage outside of testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now, I missed the purpose of the branching. Though probably still better to keep the internal API as minimalistic and non-intrusive as possible.
using var decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None); | ||
CopyImageBytesToBuffer(buffer, decompressedBuffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we also have separate PR-s for the Jpeg+Tiff+WebP+Bmp fixes or just for PNG?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all the decoder facing fixes are actually in here...but we will need a set of follow up PRs to get full coverage.
{ | ||
public static class MemoryAllocatorValidator | ||
{ | ||
public static IDisposable MonitorAllocations(int max = 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static IDisposable MonitorAllocations(int max = 0) | |
public static TestMemoryAllocatorDisposable MonitorAllocations(int max = 0) |
No point for TestMemoryAllocatorDisposable
being a struct, if we box it into IDisposable
in the end.
return new TestMemoryAllocatorDisposable(max); | ||
} | ||
|
||
public static void ValidateAllocation(int max = 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a use case for max > 0
? Do we really want to allow any test having leak-validation enabled to leak some memory? Or am I misunderstanding the purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no really I can't remember why I added it really... it was probably a bad idea really, consider it gone.
What I would do is to expose (for now internal) internal static void IncrementTotalUndisposedAllocationCount()
{
Interlocked.Increment(ref totalUndisposedAllocationCount);
bufferAllocatedBackingDelegate?.Invoke();
} Then move the |
I think I like that better its less impactful on the prod code.... i'll have a play and see how feels. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better this way I think, thanks! Two minor remarks.
|
||
public class TestMemoryDiagnostics : IDisposable | ||
{ | ||
public int TotalAllocated { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was pre-empting being useful for tests that want to track/verify total number of allocated buffers not just retained buffers... but your right currently not being used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm generally reluctant to add unused/untested features (= stuff to understand, remember and maintain) even to test utility code. We can easily add them later when actual need appears.
But it's really a nit, so I'm not going to block the PR on these two points.
|
||
public int TotalRemainingAllocated { get; set; } | ||
|
||
public void Validate(int expectedAllocationCount) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expectedAllocationCount
seems to be always 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In our current tests your are right, but I was envisioning use cases where we might want to want to validate before all buffers are returned and this is to allow for testing a specific/expected number of buffers are currently allocated still.
Prerequisites
Description
This adds some new test helpers APIs + updates the
MemoryDiagnostics
class to consistently track the memory buffers per test.Also adds 2 helpers in to the tests
ValidateDisposedMemoryAllocationsAttribute
- This attributes a can be applied to test classes andFact
/Theory
s directly to automatically apply the memory application tracking and validation.MemoryAllocatorValidator
static class can be used in places the attribute is not appropriate, i.e. inside Remote Executors.I've also applied some fixes targeting the Decoder tests
this includes the fix from #2081 (which I will merge into this PR so we don't loose attribute of the fix) if we decide this approach is suitable...otherwise we should just merge that one.