Auto call a function when no references to object

Started by iNPRwANG, March 08, 2012, 06:45:18 PM

Previous topic - Next topic

iNPRwANG

For I was often to call some OS native API in newlisp, and alloc some kernel handle.

While I alloc the native handle, I should free then. Or it will cause the leak.



I known the Steel Bank Common Lisp has a extend function named "finalize", it can bind a designated function to a object and let it to be called when there are no more references to object.

I think that's a good idea for mainten FFI's alloced pointer, is it? : )

So I wish newlisp may has the similar mechanism for usage.





Under is the part of the "finalize" function of SBCL's document.



— Function: finalize [sb-ext] object function &key dont-save



    Arrange for the designated function to be called when there are no more references to object, including references in function itself.



    If dont-save is true, the finalizer will be cancelled when save-lisp-and-die is called: this is useful for finalizers deallocating system memory, which might otherwise be called with addresses from the old image.



    In a multithreaded environment function may be called in any thread. In both single and multithreaded environments function may be called in any dynamic scope: consequences are unspecified if function is not fully re-entrant.



    Errors from function are handled and cause a warning to be signalled in whichever thread the function was called in.



    Examples:

;;; GOOD, assuming RELEASE-HANDLE is re-entrant.
(let* ((handle (get-handle))
  (object (make-object handle)))
 (finalize object (lambda () (release-handle handle)))
 object)

;;; BAD, finalizer refers to object being finalized, causing
;;; it to be retained indefinitely!
(let* ((handle (get-handle))
  (object (make-object handle)))
  (finalize object
(lambda ()
 (release-handle (object-handle object)))))

;;; BAD, not re-entrant!
(defvar *rec* nil)

(defun oops ()
 (when *rec*
   (error "recursive OOPS"))
 (let ((*rec* t))
   (gc))) ; or just cons enough to cause one

(progn
  (finalize "oops" #'oops)
  (oops)) ; GC causes re-entry to #'oops due to the finalizer
 ; -> ERROR, caught, WARNING signalled


Lutz

#1
newLISP is single threaded, the 'fork' and 'spawn' built-in functions launch new processes not in-process threads. When a newLISP process dies, all its resources are freed.



When a newLISP process calls an imported function, there is now way it could reenter this imported function again, as newLISP will not execute until the call to that imported function returns. Most built-in primitives in newLISP itself are re-entrant, or if not, handle this case.



To do finalize cleanup in newLISP code use the function 'catch' in its second syntax. When an error occurs in the body of a 'catch' expression, 'catch' expression returns 'nil' and you can execute your finalize procedure. A result variable in the 'catch' expression is used to capture either the result of the body expression or error information.



(unless
    (catch (let (  (handle (get-handle))
                   (object (make-object handle)) )
                (if (null? object) (throw-error "cannot make object")))
           'result)
    (release-handle (object-handle object)))


The code handles errors thrown by newLISP internal functions as well as higher level errors using 'throw-error'.

iNPRwANG

#2
Quote from: "Lutz"newLISP is single threaded, the 'fork' and 'spawn' built-in functions launch new processes not in-process threads. When a newLISP process dies, all its resources are freed.



When a newLISP process calls an imported function, there is now way it could reenter this imported function again, as newLISP will not execute until the call to that imported function returns. Most built-in primitives in newLISP itself are re-entrant, or if not, handle this case.



To do finalize cleanup in newLISP code use the function 'catch' in its second syntax. When an error occurs in the body of a 'catch' expression, 'catch' expression returns 'nil' and you can execute your finalize procedure. A result variable in the 'catch' expression is used to capture either the result of the body expression or error information.



(unless
    (catch (let (  (handle (get-handle))
                   (object (make-object handle)) )
                (if (null? object) (throw-error "cannot make object")))
           'result)
    (release-handle (object-handle object)))


The code handles errors thrown by newLISP internal functions as well as higher level errors using 'throw-error'.


Thanks, I accept these solutions can solve most of problems. But in certain cases, such as to free some resource and do some additional cleanup actions of global variables, it's helpless for me, for example:



I'd written two function for dll export:



void * alloc_mydata() {
   
    MyStruct * pdata = (MyStruct *)malloc(sizeof(MyStruct));
    pdata->hFile = CreateFile(...);
    ...
   
    return (void *)pdata;

}


void free_mydata(MyStruct * pdata) {
     
    assert(pdata);
 
    if (pdata->hFile != INVALID_HANDLE_VALUE) {
       
       DoSomeAdditionAction(pdata->hFile);                // for I must do these addition actions
       CloseHandle(pdata->hFile);
     }

    free(pdata);
}


In before case, while I alloc some data by "alloc_mydata", I must free them use "free_mydata" because of the free operate must do some addition actions.  



Yes, I may use the function "catch" to cleanup them in local state, but while I use these function to alloc many global variable for use, I must check all of these variables if them be freed. It's tired to do this of a large project.



While this case in SBCL, I can easily write a lisp function for wrapper my object and some FFI data with a "finalize" function, and do not care either, It's just like the object's destructor in C plus plus. : )