Hi Christoph, On Sat, Sep 28, 2024 at 4:47 PM Christoph M. Becker <cmbecke...@gmx.de> wrote: > > Besides closing resources and killing processes I've seen them store > > data to disk for caching, remove temp files, call callbacks/dispatch > > events, change state on other objects, dump stored errors to error_log > > in a loop in an error handler... > > Okay. My point is that you cannot know (unless there are no circular > dependencies) *when* a destructor is called by the engine; it may be > called during some GC run, or during the request shutdown sequence. As > it's now, that happens pretty early during shutdown, but that *might* > change when stream resources are converted to objects. So you cannot be > absolutely sure that everything works as expected in destructors. This > is a general issue for garbage collected languages; some of these have > no destructors at all, for such reasons.
I agree. To expand on this, I think that the use of destructors should be discouraged for many reasons: 1. Objects with destructors slow down garbage collection due to potential resurrections. It's not possible to detect resurrections, so in the presence of destructors the GC has to forget most of its state, run destructors, and restart. 2. They may be called in undefined order during GC and shutdown, including after their dependencies have already been destroyed 3. They may cause concurrency issues (even in non-concurrent programs) because they can be called at any time by the GC 4. They give a false sense of security as destructors will not be called in case of fatal errors or crashes 5. Using them to manage non-memory resources (such as file descriptors, temp files, ...) is not a good idea because the GC is only triggered by memory metrics. The process may run out of file descriptors before it triggers a garbage collection, for example. Part of these problems would be solved by disabling the GC, but ensuring that large code bases are free of cycles is not an easy task. Java has deprecated destructors (finalizers) [2] and recommends using Cleaners [1] instead. Cleaners would resolve the 1st issue, and partially the 2nd one (for GC, not shutdown), but I'm not entirely sure they are transposable. Ruby's ObjectSpace.define_finalizer is similar to Cleaners, AFAIK, in that the finalizer must not reference the object. Some use-cases of destructors could be replaced with patterns like Python's with() [3], Java's try-with [4], or Go's defer [5]. [1] https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html [2] https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#finalize-- [3] https://docs.python.org/3/reference/compound_stmts.html#with [4] https://docs.oracle.com/javase/8/docs/technotes/guides/language/try-with-resources.html [5] https://go.dev/doc/effective_go#defer Best Regards, Arnaud