[Openmcl-devel] Voodoo: Callbacks and Closures

David Steuber david at david-steuber.com
Wed May 4 19:03:53 PDT 2005


On May 3, 2005, at 1:43 AM, Gary Byers wrote:

> If you want to associate a CLOS object with a particular (foreign) 
> function
> pointer, that seems fairly straightforward:
>
> (defun make-callback-with-object (thing)
>   (let ((fn))
>     (declare (special fn))
>     (defcallback fn (:int arg :int) ; for instance
>       (some-random-method thing arg)
>       (slot-value thing 'whatever))
>     fn))

This is exactly what I wanted and what I wrote is below with its usage. 
  I am still rather baffled about how it works with the garbage 
collector though.  I don't know if I've programmed in a slow memory 
leak or not.

My function that returns a C callback as a closure:

(defun make-event-target-callback (et)
   (let (fn)
     (declare (special fn))
     (ccl:defcallback fn
         (:<e>vent<h>andler<c>all<r>ef next-handler :<e>vent<r>ef event 
(:* t) user-data :<oss>tatus)
       (let ((class (#_GetEventClass event))
             (kind  (#_GetEventKind  event)))
         (declare (dynamic-extent class kind))
         (debug-log "Callback CARBON-EVENT-HANDLER: event-handler-ref = 
~S; Class: '~A' Kind: ~A~%"
                    (slot-value et 'event-handler-ref) (int32-to-string 
class) kind)
         (multiple-value-bind (r c)
             (ignore-errors
               (handle-event et class kind next-handler event user-data))
           (declare (dynamic-extent r c))
           (when c
             (debug-log "Condition signaled from CARBON-EVENT-HANDLER: < 
~A >~%" c))
           (if r #$noErr #$eventNotHandledErr))))
     fn))

The method that uses it:

(defmethod install-event-handler ((et event-target) target 
event-type-specs userdata)
   (let* ((num-specs (length event-type-specs))
          (offset 0)
          (event-specs (ccl::malloc (* num-specs (ccl::record-length 
:<e>vent<t>ype<s>pec)))))
     (dolist (ets event-type-specs)
       (setf (ccl::%get-unsigned-long event-specs offset) 
(ets-event-class ets))
       (incf offset (ccl::record-length :unsigned))
       (setf (ccl::%get-unsigned-long event-specs offset) 
(ets-event-kind ets))
       (incf offset (ccl::record-length :unsigned)))
     (rlet ((ehr :<e>vent<h>andler<r>ef))
       (let ((retval (#_InstallEventHandler target
                                            (#_NewEventHandlerUPP 
(make-event-target-callback et))
                                            num-specs
                                            event-specs
                                            userdata
                                            ehr)))
         (ccl::free event-specs)
         (with-slots (user-data event-handler-ref) et
           (setf user-data userdata)
           (setf event-handler-ref (ccl::%get-ptr ehr))
           (debug-log "Installed event handler: ~S~%" event-handler-ref))
         retval))))

The value returned from make-event-target-callback is handed off to the 
Carbon function NewEventHandlerUPP and then not ever used or saved 
again.  What I think is happening is that the value is simply held in a 
MACPTR that is free to be garbage collected.  Somehow the closure 
itself survives this (this is the voodoo bit).  I started thinking 
perhaps I was creating an immortal object leading to a slow memory leak 
if I did this many times.  I tried testing in the slime repl to see 
with the following forms:

(defvar foo (make-instance 'cl-carbon::event-target))
(cl-carbon::make-event-target-callback foo)
(time (dotimes (i 1000) (cl-carbon::make-event-target-callback foo)))
(room t)
(ccl:gc)

I noted that each time make-event-target-callback is called, a 
different macptr is returned.  The dotimes loop showed me that this is 
an expensive function call (not that it matters).  What i was trying to 
do here was see if room would show me with less and less memory after 
running the gc.  What I'm not clear on is if that is at all a reliable 
way to see if I am leaking memory.  Room does seem to show less 
available memory when I run it.  After I do a gc call, I don't seem to 
have as much as before the dotimes loop.

Should I be worried about a memory leak?
What magic keeps the closure alive?




More information about the Openmcl-devel mailing list