Opened 9 years ago

Closed 9 years ago

#446 closed defect (fixed)

Finalizers for top level bindings don't get executed upon program exit

Reported by: Moritz Heidkamp Owned by:
Priority: minor Milestone:
Component: core libraries Version: 4.6.x
Keywords: Cc:
Estimated difficulty:

Description

The following program does never print "finalized x", neither when interpreted nor compiled:

(define x (list 1))
(set-finalizer! x (lambda (x) (print "finalized x")))

Appending (set! x #f), it does.

Change History (9)

comment:1 Changed 9 years ago by Moritz Heidkamp

Apparently this is not only true for top level bindings. This program also doesn't print anything when interpreted (but in contrast, it does when compiled!):

(let ((x (list 1)))
  (set-finalizer! x (lambda (x) (print "finalized x"))))

comment:2 Changed 9 years ago by felix winkelmann

Finalizers are not called on program exit. Also, in the interpreter, results (including the result of set-finalizer!, which is the finalized object) are stored in the REPL history and so never freed.

comment:3 in reply to:  2 ; Changed 9 years ago by Moritz Heidkamp

Replying to felix:

Finalizers are not called on program exit.

Is there a good reason for this or is it just not implemented? I wonder since ##sys#cleanup-before-exit in library.scm calls ##sys#force-finalizers and is registered as an exit-handler. Can you clarify this?

Also, in the interpreter, results (including the result of set-finalizer!, which is the finalized object) are stored in the REPL history and so never freed.

Yep, when I said "interpreter" I meant a script executed with csi -s, not the REPL.

comment:4 in reply to:  3 Changed 9 years ago by felix winkelmann

Replying to syn:

Replying to felix:

Finalizers are not called on program exit.

Is there a good reason for this or is it just not implemented? I wonder since ##sys#cleanup-before-exit in library.scm calls ##sys#force-finalizers and is registered as an exit-handler. Can you clarify this?

Forcing finalizers will only run those that have become garbage. The fact that a process terminates does not mean all toplevel variables become garbage, which many language implementations handle that way.

I have documented this. Is this acceptable for you?

comment:5 Changed 9 years ago by Moritz Heidkamp

Okay, that makes sense then, thanks! I assumed indeed that all data is considered garbage at program exit. I think this implies that programs relying on finalizers to clean up (possibly remote) state should additionally define an exit handler to be on the safe side then. Just out of curiosity: Would changing the behavior to consider all remaining top level bindings at program exit garbage and run their finalizers mean a big change and would it potentially break a lot of code? Otherwise I'd suggest changing to this behavior and maybe add a command line switch to disable it.

comment:6 Changed 9 years ago by Moritz Heidkamp

I just compiled the current experimental and the let example I mentioned in the first comment here still does not work in the interpreter even when adding a (gc #t) at the end. Compiled it still works as before. Since x is not bound at the top level here, souldn't it indeed be finalized in both cases?

comment:7 in reply to:  5 Changed 9 years ago by felix winkelmann

Replying to syn:

Okay, that makes sense then, thanks! I assumed indeed that all data is considered garbage at program exit. I think this implies that programs relying on finalizers to clean up (possibly remote) state should additionally define an exit handler to be on the safe side then.

Yes, that would be possible.

Just out of curiosity: Would changing the behavior to consider all remaining top level bindings at program exit garbage and run their finalizers mean a big change and would it potentially break a lot of code?

It would be a big change since all sorts of crap would start running once the program terminates. Finalizers are icky. Finalizers give you the non-determinism of threads but without the synchronization and should be avoided unless absolutely necessary (strictly speaking, they should run in a separate threads).

Otherwise I'd suggest changing to this behavior and maybe add a command line switch to disable it.

I probably sound like an asshole, but that is also a bad idea. :-) Making behaviour like this depending on command-line- or configuration options just make maintenance unnecessary hard. I suggest you give an example that explains the motivation for what you seem to request.

comment:8 in reply to:  6 Changed 9 years ago by felix winkelmann

Component: unknowncore libraries
Milestone: 4.7.0
Priority: majorminor

Replying to syn:

I just compiled the current experimental and the let example I mentioned in the first comment here still does not work in the interpreter even when adding a (gc #t) at the end. Compiled it still works as before. Since x is not bound at the top level here, souldn't it indeed be finalized in both cases?

The interpreter uses a simple linked-list environment-representation, and I assume that the finalizer-closure reaches back into the outer environment containing the datum. Something like this should work:

(define (finalized x) ...)
(begin (set-finalizer! x finalized) #f)

comment:9 Changed 9 years ago by felix winkelmann

Resolution: fixed
Status: newclosed
Note: See TracTickets for help on using tickets.