-
Notifications
You must be signed in to change notification settings - Fork 897
Resolve FilterFixture NRE #1113
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
/cc @nulltoken , @shiftkey , @ammeep Minor API redesign here. Need you to please take a look. Thanks! |
Tons of thanks for this! ✨ I'll take a deeper look tomorrow. But below a quick first feedback:
|
@jamill You've successfully survived some garbage collection wars before, Could you please glance at this? Kudos and ❤️ to @jeffhostetler and his fine-tuned psychic debugger: This was indeed GC related! |
While I can agree with this, it means adding a lot of methods to a structure called "settings". I'm not sure if that is logical in and of itself. Honestly, I only left the methods in If we really want all the global code to move to
Finalizers are non-deterministic. That means racy - while I do not have a problem having a finalizer that calls As a contributor to a rather large-ish app, I much prefer to have the ability to dispose of my disposable objects when I determine it is the right time, and not be at the mercy of the GC. Things to consider when dealing with finalizers / finalize operations have the following limitations:
I originally considered an
Again a trivial change. I recommend performing the copy to prevent any long term locking or racy-ness in the global variable. If we want to expose it as an |
OK, so I've pushed an update with most of @nulltoken was asking for so that we can look at the version side-by-side.
The first three are done because they're trivial and mostly have no impact on the library itself. The second three are pending additional feedback. I prefer enabling the developer to control the disposal of memory, thus have retained the |
Indeed, maybe
I wasn't suggesting to not free it as soon as possible. Just that I'd rather not multiply the number of public Considering the usage of filters, beside very simple code samples, I don't see them being wrapped into a
You're right, of course, we should copy and return this copy as an IEnumerable.
AFAIR Linq |
Indeed, but not in this pull-request. If we're assuming a rename could be in order, then I agree - let's move all the logic to
😞 not sure what to do here. Having discrete control of memory is rather important from my point-of-view. If the dev prefers to allow the finalizer to do the work, great - but enabling the devs to clean up memory as necessary is powerful and immensely useful, and we lose nothing by implementing it. If we keep the Am I making sense? I'm never quite sure. 😕
Unfortunately, I don't believe this to be true. Look at corefx/Enumberable.cs @ line 1489. It calls
I highly recommend that we stick with the _EDIT_ Nevermind, reading code is hard. Due to the magic of |
@whoisj What about this code part?
|
As we (the lg2# framework)now keep a collection of the registered filters, do they need to be disposable? They should not need a finalizer (except to handle logic errors in the library?), as lg2# is now managing the lifetime of these objects, and can make sure the correct clean up is done when they are removed. |
Yeah - see my addendum. 😖
Keeping the objects alive as long as we need them is independent of enabling devs to manage their memory. They have a strong cross section, but see them as independently valuable aspects. |
|
||
registeredFilters = GlobalSettings.GetRegisteredFilters() as IList<FilterRegistration>; | ||
|
||
Assert.Equal(registeredFilters.Count, 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.
As an array implement ICollection
, I believe the code can be simplified to
registeredFilters = GlobalSettings.GetRegisteredFilters();
Assert.Equal(0, registeredFilters.Count());
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 want ICollection<T>
or IEnumerable<T>
? 😕
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.
@whoisj Linq Count()
implementation tests is the underlying type implements ICollection
. When that's the case, it will directly invoke the Count
property from it.
Thus, we can make GetRegisteredFilters
return and IEnumerable<>
and let the tests leverage the Linq Count()
extension method.
This should also allow us to get rid of the cast as IList<FilterRegistration>
in the tests.
So the following code
var registeredFilters = GlobalSettings.GetRegisteredFilters() as IList<FilterRegistration>;
Assert.Equal(1, registeredFilters.Count);
can be rewritten as
var registeredFilters = GlobalSettings.GetRegisteredFilters();
Assert.Equal(1, registeredFilters.Count());
Sorry if I was unclear earlier.
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.
Ahh yeah, just add the using System.Linq;
in the header and "magic!"
Global nitpick: Could you please switch the following code constructs
into
XUnit defines the expected value as the first parameter. Following this pattern makes the assertion failure far more easy to understand 😉 |
Yes, and another framework I was using them has this way - I agree, making the error message sensible is a good thing. Why can't these framework writers just agree on ordering?! 😜 |
@nulltoken if we're good with the final code, I can squash for a merge. Let me know. |
@whoisj I'm still uneasy with |
I'd like to understand your unease. I'm likely just not understanding something obvious. I know that I can be overly zealous about use of I'm moved all the native allocations into the Per MSDN,
I think Can you explain your logic behind the reluctance to using |
Note that this would also be the only |
👍 to what @ethomson wrote. I've got also some other points to expose, but the answer will likely take me more than five minutes to write. Let me sleep over this and I'll write down my thoughts tomorrow to make my reasoning clearer. |
@nulltoken, @ethomson OK that makes more sense, but I we'll still need a way to reclaim filters that leave scope - perhaps an internal implementation of Remember, the reason we were leaking was because the tests were releasing their handles on filters without deregistering them. While the tests are fixed up now, I foresee this as a common problem for app devs. I suppose with the ledger this is less meaningful. I'll wait for you response in the AM and follow whatever course of action get this issue resolved 😉 |
With the ledger, now LG2# is responsible for cleaning up the native resources when the filter is deregistered. If a filter go out of scope and the native resources are not cleaned up, then this would indicate (quite a serious) issue in the framework. Consumers of LibGit2Sharp (App devs?) should not have a problem with the native resources not being cleaned up. They might still have an issue with not deregistering the filter. I think something that implements something similar to the dispose pattern internally would be nice (so at least we test / assert our correct behavior). |
👍 |
OK so I've changed the code so that unregistering a filter releases its native memory and removed the reliance on
/CC @ammeep because she's the author of the fact. |
That's not the mechanism that I would have expected. Why doesn't |
Why would The |
I'm not making a claim as to what class(es) things should live in. |
@whoisj I don't really care about C# type/C function names coherency. From an API perspective, this is an implementation detail and shouldn't influence the design. |
Unfortunately, I don't believe that most c# devs properly understand/honor I share @ethomson's views. We should only expose one true way to do things. Easier to troubleshoot for us, less confusing for the consumer. Things livings in |
@nulltoken The
Just so that I'm clear here, you're saying you want native allocation to happen at filter registration and not creation - is that correct? |
Indeed. It should be safe to create a filter for a user. From his/her perpective, it shouldn't be anything more than some POCO. |
@whoisj BTW, Could you please take a look at the failing builds? |
Per my comment above, it'll fail on that test until I revert my change. I'll get it fixed up asap (likely early next week). |
@nulltoken all fixed up per your request. Let me know if you need anything else. If not, I'll squash and resubmit. |
@whoisj Can you please squash? |
Assert.Equal(0, GlobalSettings.GetRegisteredFilters().Count()); | ||
|
||
var filter = new EmptyFilter(FilterName, attributes); | ||
var registration = GlobalSettings.RegisterFilter(filter); |
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.
Maybe add a
Assert.True(registration.isValid);
Moves core registration logic into `FilterRegistration` while leaving legacy methods in `GlobalSettings`. Methods in `GlobalSettings` now forward to methods in `FilterRegistration`. Retains handles on all filter and registration values in static `GlobalSettings` class to prevent native code calling disposed managed objects resulting in NRE. Manages register, deregister, and dispose logic coherently. Added a new test to verify improved features and survivability.
@nulltoken, done. |
💎 |
Resolve racy NRE via re-architect of filter logic.
Moves all filter logic into the
Filter
class. MadeFilter
intoIDisposable
.Seperates registration logic into the
FilterRegistration
class. MadeFilterRegistration
intoIDisposable
.Moves core registration logic into
FilterRegistration
while leaving legacy methods inGlobalSettings
. Methods inGlobalSettings
now forward to methods inFilterRegistration
.Retains handles on all filter and registration values in static
FilterRegistration
class to prevent native code calling disposed managed objects resulting in NRE. Manages register, deregister, and dispose logic coherently.Added a new test to verify improved features and Survivability.
Fixes a few other areas where NRE happened after GC collection vs. native utilization race occurred.
Fixes #1091