id,summary,reporter,owner,description,type,status,priority,milestone,component,version,resolution,keywords,cc
896,Finalizer ordering,rpav,gb,"As per discussion on #ccl:

Finalizer ordering is currently arbitrary or backwards where it seems
it should not be.  (Note the definition of FINALIZE is essentially
lifted from TRIVIAL-GARBAGE.)

{{{
;; This is a demonstration of finalization order in CCL.  When I run this,
;; I get the following output:
;;
;;     Finalize FOO 1
;;     Finalize FOO 2
;;     I would like #S(FOO) 2
;;     Finalize BAR 2
;;
;; This unfortunately means that if the FOO finalizer does something destructive
;; and permanent (like freeing a foreign value), BAR's finalizer can cause a
;; crash.

(defstruct (foo (:constructor %make-foo)))
(defstruct (bar (:constructor %make-bar)))

(defun finalize (object function)
  (ccl:terminate-when-unreachable object
                                  (lambda (obj)
                                    (declare (ignore obj))
                                    (funcall function)))
  object)

(defun make-foo (id)
  (let* ((foo (%make-foo)))
    (finalize foo (lambda () (format t ""~&Finalize FOO ~A~%"" id)))))

(defun make-bar (id)
  (let* ((foo (make-foo id))
         (bar (%make-bar)))
    (finalize bar
              (lambda ()
                (format t ""~&I would like ~A ~A~%"" foo id)
                (format t ""~&Finalize BAR ~A~%"" id)))))

(make-foo 1)
(make-bar 2)
(gc)
}}}

One would '''expect''' that FOO 2 would not be finalized until after
BAR 2, because FOO 2 is referenced by BAR 2's finalizer lambda.  I
surmise that this is because the ''only'' reference to a finalizer
is via its object, and therefore, anything the finalizer references
becomes eligible for collection as soon as the object itself does.

A simple workaround is to simply strongly reference the lambda, and
then make it dereference itself:

{{{
(defvar *strong-finalizers* (make-hash-table))

(defstruct (foo (:constructor %make-foo)))
(defstruct (bar (:constructor %make-bar)))

(defun finalize (object function)
  (setf (gethash function *strong-finalizers*) t)
  (ccl:terminate-when-unreachable object
                                  (lambda (obj)
                                    (declare (ignore obj))
                                    (funcall function)
                                    (remhash function *strong-finalizers*)))
  object)

(defun make-foo (id)
  (let* ((foo (%make-foo)))
    (finalize foo (lambda () (format t ""~&Finalize FOO ~A~%"" id)))))

(defun make-bar (id)
  (let* ((foo (make-foo id))
         (bar (%make-bar)))
    (finalize bar
              (lambda ()
                (format t ""~&I would like ~A ~A~%"" foo id)
                (format t ""~&Finalize BAR ~A~%"" id)))))

(make-bar 2)
(gc)

(make-foo 1)
(gc)
(gc)
(gc)
}}}

In this case, the finalizer for FOO 2 is not called until the next
cycle (though it appears to take some allocation and a cycle or two to make it notice).",defect,closed,normal,,"Runtime (threads, GC)",1.7,fixed,finalizer,
