-
Notifications
You must be signed in to change notification settings - Fork 293
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
Clean up globals defined in Python source, make sure their __del__ is called #988
base: main
Are you sure you want to change the base?
Conversation
The _Foo_count = 0
class Foo:
def __init__(self, name):
global _Foo_count
_Foo_count += 1
self.name = name
print("Creating instance of Foo (Total: {})".format(_Foo_count))
def __del__(self):
global _Foo_count
_Foo_count -= 1
print("Deleting i instance of Foo (Total: {})".format(_Foo_count)) It may be unfortunate, but in the particular case of resource cleanup, I think the burden should fall on the Python developer. For example, if someone opens up a file and wants to make sure it gets flushed to disk, then they should use a This would mean that in #985 the user would be expected to It looks like .NET Framework does a GC collect on app shutdown, but .NET Core does not so unfortunately this means these sorts of problems may be more frequent on .NET Core. I'm not sure if it's possible to mimic the .NET Framework behavior with .NET Core? I would be happy to be convinced otherwise... @BCSharp any thoughts on the subject? |
Yes this is really an issue and a better solution is needed. BTW, It's probably not just missing a GC at the process exit for .NET Core. I tried renaming the public static int Main(string[] args) {
int code = CmdExec(args);
GC.Collect();
GC.WaitForPendingFinalizers();
return code;
} so that local variable shouldn't be the reason for the object not being collected. But the It seems there's a more complicated difference between .NET Framework and .NET Core. |
From https://docs.microsoft.com/en-us/dotnet/api/system.object.finalize#how-finalization-works.
|
I wrote about finalizers before, so here is just the bottom line: every time I see that finalizers are used as a mechanism for anything else than preventing unmanaged resource leak, I get a sense that something is icky. In a way I am not surprised that .NET Core turns out not to run all existing finalizers on shutdown. Since their job is to release unmanaged resources, on process termination the OS will do all that cleanup anyway. And since there is no IronPython uses the .NET garbage collection mechanism, which is different than CPython's, so there are bound to be differences. Since the specific behaviour of GC is not part of the language, proper Python code should not be written in a way that it depends on it. In practice, however, it does. To quote @slozier:
So another thing that works with CPython is that class A:
def __init__(self, name, ref):
self.name = name
self.ref = ref
self.del_called = False
def __del__(self):
print("Deleting {}, is del already called? {}; referring to {}, is that object already deleted? {}".format(
self.name, self.del_called, self.ref.name, self.ref.del_called))
self.del_called = True
a1 = A("a1", None)
a2 = A("a2", a1)
a1.ref = a2
That gave me an idea that builds on the cleanup solution of @jameslan: what if instead actually deleting variables from the module namespace, on shutdown we simply try-invoke |
@BCSharp The example you designed is a circular reference and there's no ideal order of calling. But sometimes it needs the correct order: log = open('log.txt', 'w')
class Foo:
def __del__(self):
log.write('__del__\n')
foo = Foo() After calling I'm wondering whether or how .NET Framework figuring out the order, and how CPython is handling this. |
Here's a thought I had this morning. Instead of trying to delete the world, would it not be enough for practical cases to release references to the |
Yes, I was curious what CPython would do in such "impossible" scenario. It had basically two choices:
If CPython chose 1, I would agree with @slozier that we just let it be as is in IronPython, and put the burden on the developer to explicitly I agree it is not perfect so this is just a heuristic to make the most common scenarios to work. To handle the example for a file object, we could add a rule that objects of types defined lower in module dependency are deleted later than objects of types defined higher up. To make it work perfectly (excluding circular references), we would need to know and trace all existing references and partial-order them. I believe this is what CPython does. But we don't have such information, although obviously the .NET GC does.
I think it is worth investigating. It may be better, or it may be worse. The GC has all the information of referential dependencies, so it is possible for it to do partial ordering and finalize objects in a proper sequence like CPython does. But I have never read any explicit guarantees or even hints that this is what the GC does. On the contrary, I remember warnings against accessing any references an object may hold to other managed objects, from within its finalizer, because they may be gone already. It doesn't mean that the GC doesn't do its best, like CPython. It may still do so, and the exceptions only occur for circular references. If so, we are lucky. But it is also possible that the GC assumes the finalizers only dispose unmanaged resources and totally disregards any ordering, for performance reasons. Bad luck then, even worse than with our heuristics. It is also possible that .NET Framework does orderly finalization and .NET Core not. Or that it changes in the future.
Idem:
|
Fix #985