Two additional, somewhat related, issues are
register-finalizer
, to arrange for a function (the second
argument) to be called when the first argument is garbage
collected. More precisely, if after a garbage collection the only
reference to the first argument is through its registration in the
finalization system, then at the end of the garbage collection process
the object's finalization function is called with the object as its
argument . The object itself will not actually be reclaimed until the
following garbage collection, assuming no new references to it where
created by the finalization function. At present there is no way to
remove a registration, and there is no check to make sure an object is
registered only once.
As an example,
> (register-finalizer (copy-seq "hello") 'print) NIL > (gc) "hello" NILThe
copy-seq
is needed since the expression itself is saved as the
value of the +
variable.
Errors in finalization functions currently print a message to the listener but don't interrupt processing of pending finalizations. This needs to be thought through a bit more. ****
(register-saver object fun)with
fun
a function of one argument, the object
. A saver can
be removed with
(unregister-saver object)At the other end, if an object requires some processing to restore it, register a restoration function with
(register-restorer object fun)and remove the registration with
(unregister-restorer object)
The savers are called by save-workspace
before committing the
save. The restorers are called in the initialization process after
the standard initialization function. This may not be quite the best
place---it needs to be thought through a bit more. ****
Here is a quick example:
> (load "svrstr") ; loading svrstr.fsl T > (setf x "fred") "fred" > (register-saver x #'(lambda (x) (format t "saving ~s~%" x))) #<Closure: #4006d0e0> > (register-restorer x #'(lambda (x) (format t "restoring ~s~%" x))) #<Closure: #4006ceb0> > (save-workspace "test.wks") saving "fred" nokomis% xlisp -wtest.wks XLISP-PLUS version 3.0 Portions Copyright (c) 1988, by David Betz. Modified by Thomas Almy and others. XLISP-STAT Release 3.50 (Beta). Copyright (c) 1989-1994, by Luke Tierney. restoring "fred" >
C
mechanism for
installing exit handlers. In particular, xlatexit
is used
just like C
's atexit
.
Eventually it would probably be useful to have Lisp exit handlers, but
I'm not quite sure exactly where they should go at this point since
there are several ways to exit (calling exit
and signaling an
EOF
to the listener on UNIX, for example) and it isn't clear how
to handle both of these. ****
patch < finalize.patchat the root of the source tree and
make
.
patch < atexit.patchat the root of the source tree and
make
.
svrstr.lsp
and create a new saved workspace.
xldmem.c
and
adding an entry to the function table. At the moment these changes
will only work for the generational collector. I don't use the
mark-and-sweep collector, but it would be fairly simple to make things
work there to. This implementation produces a patch file to be applied
to the 3.50 sources.
At the moment, finalization entries are stored in
finalize_registered
as a simple list of (object . fun)
pairs. The external function for installing a pair is
<xregfinal
>= (U->)
> LVAL xregfinal(V)
> {
> LVAL arg, fun;
> arg = xlgetarg();
> fun = xlgetarg();
> xllastarg();
> finalize_registered = cons(cons(arg, fun), finalize_registered);
> return NIL;
> }
Definesxregfinal
(links are to index).
The declarations in xlbfun.h
need to be changed,
<add xregfinal
declaration>= (U->)
< xgc(V),xexpand(V),xalloc(V),xmem(V);
---
> xgc(V),xexpand(V),xalloc(V),xmem(V),xregfinal(V);
Definesxregfinal
(links are to index).
and the xlftab.c
entry for register-finalizer
needs to be added,
<add register-finalizer
function table entry>= (U->)
> { "REGISTER-FINALIZER",S, xregfinal },
Definesregister-finalizer
(links are to index).
In the garbage collection, after all other roots have been completely
processed we check the registered objects to see if any have no
references other that the one from their registration -- these are the
objects for which is_new_node
returns TRUE
. The corresponding
list entries are removed from the registrations list and put on the
finalize_pending
list. These operations are destructive so no
allocation occurs.
<check_finalize
>= (U->)
> static VOID check_finalize(V)
> {
> LVAL last = NIL, next = finalize_registered, head, tail;
>
> while (consp(next)) {
> if (is_new_node(car(car(next)))) {
> head = next;
> tail = cdr(next);
> if (null(last))
> finalize_registered = tail;
> else
> Rplacd(last, tail);
> next = tail;
> Rplacd(head, finalize_pending);
> finalize_pending = head;
> }
> else {
> last = next;
> next = cdr(next);
> }
> }
> }
Definescheck_finalize
(links are to index).
After check_finalize
is called, if any objects are registered or
pending their lists need to be processed and any resulting additional
forwarding has to take place. **** The tests for NIL
could be
skipped.
<check for finalization and process pending and registered lists>= (U->) > check_finalize(); > if (finalize_registered != NIL || finalize_pending != NIL) { > forward_node(finalize_registered); > forward_node(finalize_pending); > while ((tmp = forwarded_nodes) != NIL) { > forwarded_nodes = NEXT_NODE_PTR(tmp); > forward_children(tmp); > TENURE_NODE(tmp); > unset_to_new_node(tmp); > } > }
At the end of the garbage collection the pending finalizations are processed.
<process pending finalizations>= (U->) > if (! null(finalize_pending)) > do_finalize();
The do_finalize
function puts up a context for catching errors and
non-local exits and then successively removes each pending entry and
processes it. This ought to be modified to eat the error message, as
in ignore-errors
.
<do_finalize
>= (U->)
> static VOID do_finalize(V)
> {
> CONTEXT cntxt;
> LVAL next;
>
> xlsave1(next);
> xlbegin(&cntxt,CF_UNWIND|CF_ERROR,NIL);
> setjmp(cntxt.c_jmpbuf);
> while (consp(finalize_pending)) {
> next = finalize_pending;
> finalize_pending = cdr(next);
> xlapp1(cdr(car(next)), car(car(next)));
> }
> xlend(&cntxt);
> xlpop();
> }
Definesdo_finalize
(links are to index).
The declarations added to xldmem.c
are
<new declarations for xldmem.c
>= (U->)
> static LVAL finalize_registered, finalize_pending;
> static VOID check_finalize(V);
> static VOID do_finalize(V);
Definescheck_finalize
,do_finalize
,finalize_pending
,finalize_registered
(links are to index).
and the registration and pending lists are initialized by
<initialize registration and pending lists>= (U->) > finalize_registered = NIL; > finalize_pending = NIL;
<finalize.patch>= Index: xldmem.c =================================================================== RCS file: /NOKOMIS/users/luke/SRC/xlispstat/xldmem.c,v retrieving revision 1.29 diff -r1.29 xldmem.c 9a10,13 <new declarations forxldmem.c
> > 311a316,318 > <initialize registration and pending lists> 1107a1115,1125 <check for finalization and process pending and registered lists> 1133a1152,1153 <process pending finalizations> 1630a1651,1653 > <initialize registration and pending lists> 2864a2888,2937 > <check_finalize
> > <do_finalize
> > <xregfinal
> Index: xlftab.c =================================================================== RCS file: /NOKOMIS/users/luke/SRC/xlispstat/xlftab.c,v retrieving revision 1.20 diff -r1.20 xlftab.c 309a310 <addregister-finalizer
function table entry> 826d826 < { NULL, S, xnotimp }, Index: xlftab.h =================================================================== RCS file: /NOKOMIS/users/luke/SRC/xlispstat/xlftab.h,v retrieving revision 1.21 diff -r1.21 xlftab.h 124c124 <addxregfinal
declaration>
**** At the moment, the finalization happens under the interrupt suspension and with the gc cursor active -- should that be changed?
xlatexit
macro to call
atexit
. The following patch puts this define into xlisp.h
.
Since the implementation currently does not use any exit handlers all
the minimum 32 handlers mandated by ANSI/ISO C
should still be
available.
<atexit.patch>= Index: xlisp.h =================================================================== RCS file: /NOKOMIS/users/luke/SRC/xlispstat/xlisp.h,v retrieving revision 1.58 diff -r1.58 xlisp.h 1849a1850,1851 > #define xlatexit(f) atexit(f) >
Definesxlatexit
(links are to index).
Eventually I will probably expand this to make sure that space for
additional exit handlers is allocated if needed even if the base
system atexit
does not do this. In addition, on the Mac it may be
useful to be more aggressive about making sure exit handlers are called
even in abnormal exit situations (assuming the OS survives). Tcl 8.0
seems to go to great lengths on this -- I'll need to look into that
more carefully. ****
<set up saver and restorer hash tables>= (U->) (defvar *saver-table* (make-hash-table)) (defvar *restorer-table* (make-hash-table))
Defines*restorer-table*
,*saver-table*
(links are to index).
Savers are installed and removed by
<defineregister-saver
andunregister-saver
>= (U->) (defun register-saver (arg fun) (setf (gethash arg *saver-table*) fun)) (defun unregister-saver (arg) (remhash arg *saver-table*))
Definesregister-saver
,unregister-saver
(links are to index).
Similarly, restorers are installed and removed by
<defineregister-restorer
andunregister-restorer
>= (U->) (defun register-restorer (arg fun) (setf (gethash arg *restorer-table*) fun)) (defun unregister-restorer (arg) (remhash arg *restorer-table*))
Definesregister-restorer
,unregister-restorer
(links are to index).
The new definition of save-workspace
inserts a maphash
over
the savers after removing the hardware objects. Eventually the
hardware object removal can be pushed into the savers.
<redefine save-workspace
>= (U->)
(defun save-workspace (name)
(dolist (h (copy-list *hardware-objects*))
(send (third h) :remove))
(maphash #'(lambda (arg fun) (funcall fun arg)) *saver-table*)
(save name)
(exit))
Definessave-workspace
(links are to index).
The function to run the restorers is
<define run-restorers
>= (U->)
(defun run-restorers ()
(maphash #'(lambda (arg fun) (funcall fun arg)) *restorer-table*))
Definesrun-restorers
(links are to index).
and it is placed in the *startup-functions*
queue by
<install run-restorers
in initialization functions>= (U->)
(unless (member 'run-restorers *startup-functions*)
(setf *startup-functions*
(append *startup-functions* '(run-restorers))))
**** This may not be the best place for it --- think it through.
The file to install all this is svrstr.lsp
. Eventually this should
be put in standard system files. For now, just load this file and make
a new saved workspace.
<svrstr.lsp>= (in-package "XLISP") (export '(register-saver unregister-saver register-restorer unregister-restorer)) <set up saver and restorer hash tables> <defineregister-saver
andunregister-saver
> <defineregister-restorer
andunregister-restorer
> <redefinesave-workspace
> <definerun-restorers
> <installrun-restorers
in initialization functions>
**** One problem with this lisp-only implementation is that finalization and save/restore handling don't work together quite right: if you register a save/restore handler the object will never be GC'd. One way around this would be tom make the save/restore handlers an internal root that is only processed after finalizations are checked for. Another possibility would be to add some form of weak pointers or weak hash tables. I'm not sure about the right approach.
register-finalizer
function table entry>: D1, U2
xregfinal
declaration>: D1, U2
check_finalize
>: D1, U2
register-restorer
and unregister-restorer
>: D1, U2
register-saver
and unregister-saver
>: D1, U2
run-restorers
>: D1, U2
do_finalize
>: D1, U2
run-restorers
in initialization functions>: D1, U2
xldmem.c
>: D1, U2
save-workspace
>: D1, U2
xregfinal
>: D1, U2