ffi/unsafe/alloc: add decallocation on place exit

Closes #1830
This commit is contained in:
Matthew Flatt 2018-06-20 10:43:16 -06:00
parent 8fb8d3c936
commit f58b99aa74
15 changed files with 281 additions and 54 deletions

View File

@ -12,7 +12,7 @@
(define collection 'multi) (define collection 'multi)
(define version "7.0.0.3") (define version "7.0.0.4")
(define deps `("racket-lib" (define deps `("racket-lib"
["racket" #:version ,version])) ["racket" #:version ,version]))

View File

@ -11,17 +11,40 @@ deallocated.}
@defproc[((allocator [dealloc (any/c . -> . any)]) [alloc procedure?]) procedure?]{ @defproc[((allocator [dealloc (any/c . -> . any)]) [alloc procedure?]) procedure?]{
Produces a procedure that behaves like @racket[alloc], but the result Produces an @deftech{allocator} procedure that behaves like
of @racket[alloc] is given a finalizer that calls @racket[dealloc] on @racket[alloc], but each result @racket[_v] of the @tech{allocator},
a non-@racket[#f] result if it is not otherwise freed through a deallocator (as if not @racket[#f], is given a finalizer that calls @racket[dealloc]
designated with @racket[deallocator]). In addition, @racket[alloc] is on @racket[_v] --- unless the call has been canceled by applying a
called in @tech{atomic mode} (see @racket[call-as-atomic]); its result is @tech{deallocator} (produced by @racket[deallocator]) to @racket[_v].
received and registered in atomic mode, so that the result is reliably Any existing @racket[dealloc] registered for @racket[_v] is canceled.
freed as long as no exception is raised.
The @racket[dealloc] procedure itself need not be specifically The resulting @tech{allocator} calls @racket[alloc] in @tech{atomic
designated a deallocator (via @racket[deallocator]). If a deallocator mode} (see @racket[call-as-atomic]). The result from @racket[alloc] is
is called explicitly, it need not be the same as @racket[dealloc].} received and registered in atomic mode, so that the result is reliably
deallocated as long as no exception is raised.
The @racket[dealloc] procedure will be called in atomic mode, and it
must obey the same constraints as a finalizer procedure provided to
@racket[register-finalizer]. The @racket[dealloc] procedure itself
need not be specifically a @tech{deallocator} produced by
@racket[deallocator]. If a @tech{deallocator} is called explicitly, it
need not be the same as @racket[dealloc].
When a non-main @tech[#:doc reference.scrbl]{place} exits, after all
@tech[#:doc reference.scrbl]{custodian}-shutdown actions, for every
@racket[dealloc] still registered via an @tech{allocator} or
@tech{retainer} (from @racket[allocator] or @racket[retainer]), the
value to deallocate is treated as immediately unreachable. At that
point, @racket[dealloc] functions are called in reverse order of their
registrations. Note that references in a @racket[dealloc] function's
closure do @emph{not} prevent running a @racket[dealloc] function for
any other value. If deallocation needs to proceed in an order
different than reverse of allocation, use a @tech{retainer} to insert
a new deallocation action that will run earlier.
@history[#:changed "7.0.0.4" @elem{Added atomic mode for @racket[dealloc]
and changed non-main place exits to call
all remaining @racket[dealloc]s.}]}
@deftogether[( @deftogether[(
@defproc[((deallocator [get-arg (list? . -> . any/c) car]) [dealloc procedure?]) @defproc[((deallocator [get-arg (list? . -> . any/c) car]) [dealloc procedure?])
@ -30,13 +53,11 @@ is called explicitly, it need not be the same as @racket[dealloc].}
procedure?] procedure?]
)]{ )]{
Produces a procedure that behaves like @racket[dealloc]. The Produces a @deftech{deallocator} procedure that behaves like
@racket[dealloc] procedure is called in @tech{atomic mode} (see @racket[dealloc]. The @tech{deallocator} calls @racket[dealloc] in
@racket[call-as-atomic]), and the reference count on one of its @tech{atomic mode} (see @racket[call-as-atomic]), and for one of its
arguments is decremented; if the reference count reaches zero, no arguments, the it cancels the most recent remaining deallocator
finalizer associated by an @racket[allocator]- or registered by a @tech{allocator} or @tech{retainer}.
@racket[retainer]-wrapped procedure is invoked when the value
becomes inaccessible.
The optional @racket[get-arg] procedure determines which of The optional @racket[get-arg] procedure determines which of
@racket[dealloc]'s arguments correspond to the released object; @racket[dealloc]'s arguments correspond to the released object;
@ -52,19 +73,28 @@ The @racket[releaser] procedure is a synonym for
[retain procedure?]) [retain procedure?])
procedure?]{ procedure?]{
Produces a procedure that behaves like @racket[retain]. The procedure Produces a @deftech{retainer} procedure that behaves like
is called in @tech{atomic mode} (see @racket[call-as-atomic]), and the @racket[retain]. A @tech{retainer} acts the same as an
reference count on one of its arguments is incremented, with @tech{allocator} produced by @racket[allocator], except that
@racket[release] recorded as the corresponding release procedure to be
called by the finalizer on the retained object (unless some
deallocator, as wrapped by @racket[deallocator], is explicitly called
first).
The optional @racket[get-arg] procedure determines which of @itemlist[
@racket[retain]'s arguments correspond to the retained object;
@item{a @tech{retainer} does not cancel any existing @racket[release]
or @racket[_dealloc] registrations when registering
@racket[release]; and}
@item{@racket[release] is registered for a value @racket[_v] that is
is an argument to the @tech{retainer}, instead of the result
for an @tech{allocator}.}
]
The optional @racket[get-arg] procedure determines which of the
@tech{retainer}'s arguments (that is, which of @racket[retain]'s
arguments) correspond to the retained object @racket[_v];
@racket[get-arg] receives a list of arguments passed to @racket[get-arg] receives a list of arguments passed to
@racket[retain], so the default @racket[car] selects the first one. @racket[retain], so the default @racket[car] selects the first one.
The @racket[release] procedure itself need not be specifically @history[#:changed "7.0.0.4" @elem{Added atomic mode for @racket[release]
designated a deallocator (via @racket[deallocator]). If a deallocator and changed non-main place exits to call
is called explicitly, it need not be the same as @racket[release].} all remaining @racket[release]s.}]}

View File

@ -303,7 +303,6 @@ Registers a finalizer procedure @racket[finalizer-proc] with the given
is registered with a will executor; see is registered with a will executor; see
@racket[make-will-executor]. The finalizer is invoked when @racket[make-will-executor]. The finalizer is invoked when
@racket[obj] is about to be collected. @racket[obj] is about to be collected.
See also @racket[register-custodian-shutdown].
The finalizer is invoked in a thread that is in charge of triggering The finalizer is invoked in a thread that is in charge of triggering
will executors for @racket[register-finalizer]. The given will executors for @racket[register-finalizer]. The given
@ -320,9 +319,13 @@ foreign code. Note, however, that the finalizer is registered for the
free a pointer object, then you must be careful to not register free a pointer object, then you must be careful to not register
finalizers for two cpointers that point to the same address. Also, be finalizers for two cpointers that point to the same address. Also, be
careful to not make the finalizer a closure that holds on to the careful to not make the finalizer a closure that holds on to the
object. object. Finally, beware that the finalizer is not guaranteed to
be run when a place exits; see @racketmodname[ffi/unsafe/alloc]
and @racket[register-finalizer-and-custodian-shutdown] for more
complete solutions.
For example, suppose that you're dealing with a foreign function that returns a As an example for @racket[register-finalizer],
suppose that you're dealing with a foreign function that returns a
C string that you should free. Here is an attempt at creating a suitable type: C string that you should free. Here is an attempt at creating a suitable type:
@racketblock[ @racketblock[
@ -337,7 +340,7 @@ C string that you should free. Here is an attempt at creating a suitable type:
The above code is wrong: the finalizer is registered for @racket[x], The above code is wrong: the finalizer is registered for @racket[x],
which is no longer needed after the byte string is created. Changing which is no longer needed after the byte string is created. Changing
the example to register the finalizer for @racket[b] correct the problem, the example to register the finalizer for @racket[b] corrects the problem,
but then @racket[free] is invoked @racket[b] it instead of on @racket[x]. but then @racket[free] is invoked @racket[b] it instead of on @racket[x].
In the process of fixing this problem, we might be careful and log a message In the process of fixing this problem, we might be careful and log a message
for debugging: for debugging:

View File

@ -0,0 +1,70 @@
#lang racket/base
(require racket/place
ffi/unsafe
ffi/unsafe/alloc)
(module+ test
(define-syntax-rule (test expect got)
(let ([e expect]
[g got])
(unless (equal? e g)
(error 'test "failed: ~s => ~v != ~s => ~v" 'expect e 'got g))))
(define (check-allocator
#:retain? [retain? #f]
#:release? [release? #f]
#:deallocate? [deallocate? #f])
(printf "~s\n" (list retain? release? deallocate?))
(define done? #f)
(define released? #f)
(let ([x (((allocator (lambda (v) (set! done? (symbol? v))))
gensym))])
(collect-garbage)
(sync (system-idle-evt))
(test #f done?)
(when retain?
(((retainer (lambda (v) (set! released? (symbol? v)))) void) x))
(when release?
(((releaser) void) x))
(when deallocate?
(((deallocator) void) x))
(void/reference-sink x)
(collect-garbage)
(sync (system-idle-evt))
(when retain?
(collect-garbage)
(sync (system-idle-evt)))
(test (or (not deallocate?) (and retain? (not release?))) done?)
(test (and retain? (not (or deallocate? release?))) released?)))
(unless (eq? 'cgc (system-type 'gc))
(check-allocator)
(check-allocator #:deallocate? #t)
(check-allocator #:retain? #t #:deallocate? #t)
(check-allocator #:retain? #t #:deallocate? #t #:release? #t)
(check-allocator #:retain? #t #:deallocate? #f #:release? #t))
(when (place-enabled?)
;; Make sure deallocators are run when a place exits,
;; and make sure they're run in the right order:
(define p (go-in-place))
(define bstr (make-shared-bytes 2 0))
(place-channel-put p bstr)
(void (place-wait p))
(test #"\x0A\x64" bstr)))
(define (go-in-place)
(place p
(define bstr (place-channel-get p))
;; These 10 should be deallocated later
(define orig
(for/list ([i 10])
(((allocator (lambda (v) (bytes-set! bstr 1 (+ (bytes-ref bstr 1) (bytes-ref bstr 0)))))
gensym))))
;; These 10 should be deallocated earlier:
(define next
(for/list ([i 10])
(((allocator (lambda (v) (bytes-set! bstr 0 (+ (bytes-ref bstr 0) 1))))
gensym))))
(list next orig)))

View File

@ -1,5 +1,9 @@
#lang racket/base #lang racket/base
(require ffi/unsafe (require ffi/unsafe
(only-in '#%unsafe
unsafe-add-post-custodian-shutdown
unsafe-start-atomic
unsafe-end-atomic)
"atomic.rkt") "atomic.rkt")
(provide allocator deallocator retainer (provide allocator deallocator retainer
@ -7,6 +11,12 @@
(define allocated (make-late-weak-hasheq)) (define allocated (make-late-weak-hasheq))
;; A `node` is used to implement a doubly linked list that
;; records the allocation order. That way, deallocators can
;; be called in reverse order when a non-main place exits.
(struct node (weak-val proc [next #:mutable] [prev #:mutable] rest)
#:authentic)
;; A way to show all still-unfinalized values on exit: ;; A way to show all still-unfinalized values on exit:
#; #;
(plumber-add-flush! (current-plumber) (plumber-add-flush! (current-plumber)
@ -15,13 +25,35 @@
(printf "~s\n" k)))) (printf "~s\n" k))))
(define (deallocate v) (define (deallocate v)
;; Called as a finalizer, we we assume that the ;; Called either as finalizer (in the finalizer thread) or
;; enclosing thread will not be interrupted. ;; as a place is about to exit. Run in atomic mode to
;; avoid a race with a place exit.
(unsafe-start-atomic)
(let ([ds (hash-ref allocated v #f)]) (let ([ds (hash-ref allocated v #f)])
(when ds (when ds
(hash-remove! allocated v) (hash-remove! allocated v)
(for ([d (in-list ds)]) (let loop ([ds ds])
(d v))))) (when ds
(remove-node! ds)
((node-proc ds) v)
(loop (node-rest ds))))))
(unsafe-end-atomic))
(define (deallocate-one v expected-ds)
;; Called for a place exit.
(let ([ds (hash-ref allocated v #f)])
(cond
[(eq? ds expected-ds)
(define rest-ds (node-rest ds))
(if rest-ds
(hash-set! allocated v rest-ds)
(hash-remove! allocated v))
(remove-node! ds)
((node-proc ds) v)]
[else
;; Not the expected node. Maybe an allocator
;; replaced existing allocations/retains.
(remove-node! expected-ds)])))
(define ((allocator d) proc) (define ((allocator d) proc)
(rename (rename
@ -30,7 +62,9 @@
(lambda () (lambda ()
(let ([v (apply proc args)]) (let ([v (apply proc args)])
(when v (when v
(hash-set! allocated v (list d)) (define ds (node (make-late-weak-box v) d #f #f #f))
(add-node! ds)
(hash-set! allocated v ds)
(register-finalizer v deallocate)) (register-finalizer v deallocate))
v)))) v))))
proc)) proc))
@ -44,10 +78,12 @@
(let ([v (get-arg args)]) (let ([v (get-arg args)])
(let ([ds (hash-ref allocated v #f)]) (let ([ds (hash-ref allocated v #f)])
(when ds (when ds
(if (null? (cdr ds)) (remove-node! ds)
(hash-remove! allocated v) (define rest-ds (node-rest ds))
(hash-set! allocated v (cdr ds))))))))) (if rest-ds
proc)) (hash-set! allocated v rest-ds)
(hash-remove! allocated v))))))))
proc))
(define ((retainer d [get-arg car]) proc) (define ((retainer d [get-arg car]) proc)
(rename (rename
@ -57,8 +93,12 @@
(begin0 (begin0
(apply proc args) (apply proc args)
(let ([v (get-arg args)]) (let ([v (get-arg args)])
(let ([ds (hash-ref allocated v null)]) (define next-ds (hash-ref allocated v #f))
(hash-set! allocated v (cons d ds)))))))) (define ds (node (make-late-weak-box v) d #f #f next-ds))
(add-node! ds)
(hash-set! allocated v ds)
(unless next-ds
(register-finalizer v deallocate)))))))
proc)) proc))
(define (rename new orig) (define (rename new orig)
@ -70,3 +110,36 @@
(if n (if n
(procedure-rename new n) (procedure-rename new n)
new)))) new))))
;; ----------------------------------------
(define all-nodes #f)
(define (add-node! ds)
(set-node-next! ds all-nodes)
(when all-nodes
(set-node-prev! all-nodes ds))
(set! all-nodes ds))
(define (remove-node! ds)
(define prev (node-prev ds))
(define next (node-next ds))
(if prev
(set-node-next! prev next)
(set! all-nodes next))
(when next
(set-node-prev! next prev)))
(define (release-all)
(define ds all-nodes)
(when ds
(define v (weak-box-value (node-weak-val ds)))
(cond
[v (deallocate-one v ds)]
[else
(log-error "ffi/unsafe/alloc: internal error with a value deallocated by ~s" (node-proc ds))
(remove-node! ds)])
(release-all)))
;; This is a no-op in the main place:
(unsafe-add-post-custodian-shutdown release-all)

View File

@ -30,6 +30,7 @@
unsafe-make-custodian-at-root unsafe-make-custodian-at-root
unsafe-custodian-register unsafe-custodian-register
unsafe-custodian-unregister unsafe-custodian-unregister
unsafe-add-post-custodian-shutdown
unsafe-register-process-global unsafe-register-process-global
unsafe-get-place-table unsafe-get-place-table
unsafe-make-security-guard-at-root unsafe-make-security-guard-at-root

View File

@ -5,6 +5,7 @@
[check-not-unsafe-undefined/assign (known-procedure 4)] [check-not-unsafe-undefined/assign (known-procedure 4)]
[prop:chaperone-unsafe-undefined (known-constant)] [prop:chaperone-unsafe-undefined (known-constant)]
[unsafe-abort-current-continuation/no-wind (known-procedure 4)] [unsafe-abort-current-continuation/no-wind (known-procedure 4)]
[unsafe-add-post-custodian-shutdown (known-procedure 2)]
[unsafe-box*-cas! (known-procedure 8)] [unsafe-box*-cas! (known-procedure 8)]
[unsafe-bytes-length (known-procedure/succeeds 2)] [unsafe-bytes-length (known-procedure/succeeds 2)]
[unsafe-bytes-ref (known-procedure 4)] [unsafe-bytes-ref (known-procedure 4)]

View File

@ -558,6 +558,7 @@
place-channel place-dead-evt place-kill place-message-allowed? place-channel place-dead-evt place-kill place-message-allowed?
dynamic-place place-wait place-pumper-threads place-shared? dynamic-place place-wait place-pumper-threads place-shared?
unsafe-get-place-table unsafe-get-place-table
unsafe-add-post-custodian-shutdown
_bool _bytes _short_bytes _double _double* _fixint _fixnum _float _fpointer _gcpointer _bool _bytes _short_bytes _double _double* _fixint _fixnum _float _fpointer _gcpointer
_int16 _int32 _int64 _int8 _longdouble _pointer _scheme _stdbool _void _int16 _int32 _int64 _int8 _longdouble _pointer _scheme _stdbool _void

View File

@ -21,6 +21,11 @@
[(_ id ...) [(_ id ...)
(begin (define-place-not-yet-available id) ...)])) (begin (define-place-not-yet-available id) ...)]))
;; This operation adds shutdown thunks to a non-main place, so it's a
;; no-op for now:
(define (unsafe-add-post-custodian-shutdown proc)
(void))
(define-place-not-yet-available (define-place-not-yet-available
place-break place-break
place-channel-get place-channel-get

View File

@ -250,6 +250,7 @@ typedef struct Thread_Local_Variables {
intptr_t scheme_current_cont_mark_pos_; intptr_t scheme_current_cont_mark_pos_;
struct Scheme_Custodian *main_custodian_; struct Scheme_Custodian *main_custodian_;
struct Scheme_Hash_Table *limited_custodians_; struct Scheme_Hash_Table *limited_custodians_;
struct Scheme_Object *post_custodian_shutdowns_;
struct Scheme_Plumber *initial_plumber_; struct Scheme_Plumber *initial_plumber_;
struct Scheme_Config *initial_config_; struct Scheme_Config *initial_config_;
struct Scheme_Thread *swap_target_; struct Scheme_Thread *swap_target_;
@ -628,6 +629,7 @@ XFORM_GC_VARIABLE_STACK_THROUGH_THREAD_LOCAL;
#define main_custodian XOA (scheme_get_thread_local_variables()->main_custodian_) #define main_custodian XOA (scheme_get_thread_local_variables()->main_custodian_)
#define last_custodian XOA (scheme_get_thread_local_variables()->last_custodian_) #define last_custodian XOA (scheme_get_thread_local_variables()->last_custodian_)
#define limited_custodians XOA (scheme_get_thread_local_variables()->limited_custodians_) #define limited_custodians XOA (scheme_get_thread_local_variables()->limited_custodians_)
#define post_custodian_shutdowns XOA (scheme_get_thread_local_variables()->post_custodian_shutdowns_)
#define initial_plumber XOA (scheme_get_thread_local_variables()->initial_plumber_) #define initial_plumber XOA (scheme_get_thread_local_variables()->initial_plumber_)
#define initial_config XOA (scheme_get_thread_local_variables()->initial_config_) #define initial_config XOA (scheme_get_thread_local_variables()->initial_config_)
#define swap_target XOA (scheme_get_thread_local_variables()->swap_target_) #define swap_target XOA (scheme_get_thread_local_variables()->swap_target_)

View File

@ -590,6 +590,8 @@ void scheme_place_instance_destroy(int force)
else else
scheme_run_atexit_closers_on_all(force_more_closed_after); scheme_run_atexit_closers_on_all(force_more_closed_after);
scheme_run_post_custodian_shutdown();
scheme_release_fd_semaphores(); scheme_release_fd_semaphores();
scheme_release_file_descriptor(); scheme_release_file_descriptor();

View File

@ -14,7 +14,7 @@
#define USE_COMPILED_STARTUP 1 #define USE_COMPILED_STARTUP 1
#define EXPECTED_PRIM_COUNT 1431 #define EXPECTED_PRIM_COUNT 1432
#ifdef MZSCHEME_SOMETHING_OMITTED #ifdef MZSCHEME_SOMETHING_OMITTED
# undef USE_COMPILED_STARTUP # undef USE_COMPILED_STARTUP

View File

@ -873,6 +873,7 @@ Scheme_Thread *scheme_do_close_managed(Scheme_Custodian *m, Scheme_Exit_Closer_F
Scheme_Custodian *scheme_get_current_custodian(void); Scheme_Custodian *scheme_get_current_custodian(void);
void scheme_run_atexit_closers_on_all(Scheme_Exit_Closer_Func alt); void scheme_run_atexit_closers_on_all(Scheme_Exit_Closer_Func alt);
void scheme_run_atexit_closers(Scheme_Object *o, Scheme_Close_Custodian_Client *f, void *data); void scheme_run_atexit_closers(Scheme_Object *o, Scheme_Close_Custodian_Client *f, void *data);
void scheme_run_post_custodian_shutdown();
typedef struct Scheme_Security_Guard { typedef struct Scheme_Security_Guard {
Scheme_Object so; Scheme_Object so;

View File

@ -13,12 +13,12 @@
consistently.) consistently.)
*/ */
#define MZSCHEME_VERSION "7.0.0.3" #define MZSCHEME_VERSION "7.0.0.4"
#define MZSCHEME_VERSION_X 7 #define MZSCHEME_VERSION_X 7
#define MZSCHEME_VERSION_Y 0 #define MZSCHEME_VERSION_Y 0
#define MZSCHEME_VERSION_Z 0 #define MZSCHEME_VERSION_Z 0
#define MZSCHEME_VERSION_W 3 #define MZSCHEME_VERSION_W 4
#define MZSCHEME_VERSION_MAJOR ((MZSCHEME_VERSION_X * 100) + MZSCHEME_VERSION_Y) #define MZSCHEME_VERSION_MAJOR ((MZSCHEME_VERSION_X * 100) + MZSCHEME_VERSION_Y)
#define MZSCHEME_VERSION_MINOR ((MZSCHEME_VERSION_Z * 1000) + MZSCHEME_VERSION_W) #define MZSCHEME_VERSION_MINOR ((MZSCHEME_VERSION_Z * 1000) + MZSCHEME_VERSION_W)

View File

@ -335,6 +335,8 @@ static Scheme_Object *unsafe_make_custodian_at_root(int argc, Scheme_Object *arg
static Scheme_Object *unsafe_custodian_register(int argc, Scheme_Object *argv[]); static Scheme_Object *unsafe_custodian_register(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_custodian_unregister(int argc, Scheme_Object *argv[]); static Scheme_Object *unsafe_custodian_unregister(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_add_post_custodian_shutdown(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_register_process_global(int argc, Scheme_Object *argv[]); static Scheme_Object *unsafe_register_process_global(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_get_place_table(int argc, Scheme_Object *argv[]); static Scheme_Object *unsafe_get_place_table(int argc, Scheme_Object *argv[]);
static Scheme_Object *unsafe_set_on_atomic_timeout(int argc, Scheme_Object *argv[]); static Scheme_Object *unsafe_set_on_atomic_timeout(int argc, Scheme_Object *argv[]);
@ -478,6 +480,12 @@ extern BOOL WINAPI DllMain(HINSTANCE inst, ULONG reason, LPVOID reserved);
SHARED_OK Scheme_Object *initial_cmdline_vec; SHARED_OK Scheme_Object *initial_cmdline_vec;
#if defined(MZ_USE_PLACES)
# define RUNNING_IN_ORIGINAL_PLACE (scheme_current_place_id == 0)
#else
# define RUNNING_IN_ORIGINAL_PLACE 1
#endif
/*========================================================================*/ /*========================================================================*/
/* initialization */ /* initialization */
/*========================================================================*/ /*========================================================================*/
@ -646,6 +654,8 @@ scheme_init_unsafe_thread (Scheme_Startup_Env *env)
ADD_PRIM_W_ARITY("unsafe-custodian-register", unsafe_custodian_register, 5, 5, env); ADD_PRIM_W_ARITY("unsafe-custodian-register", unsafe_custodian_register, 5, 5, env);
ADD_PRIM_W_ARITY("unsafe-custodian-unregister", unsafe_custodian_unregister, 2, 2, env); ADD_PRIM_W_ARITY("unsafe-custodian-unregister", unsafe_custodian_unregister, 2, 2, env);
ADD_PRIM_W_ARITY("unsafe-add-post-custodian-shutdown", unsafe_add_post_custodian_shutdown, 1, 1, env);
ADD_PRIM_W_ARITY("unsafe-register-process-global", unsafe_register_process_global, 2, 2, env); ADD_PRIM_W_ARITY("unsafe-register-process-global", unsafe_register_process_global, 2, 2, env);
ADD_PRIM_W_ARITY("unsafe-get-place-table", unsafe_get_place_table, 0, 0, env); ADD_PRIM_W_ARITY("unsafe-get-place-table", unsafe_get_place_table, 0, 0, env);
@ -1916,6 +1926,40 @@ void do_run_atexit_closers_on_all()
scheme_run_atexit_closers_on_all(NULL); scheme_run_atexit_closers_on_all(NULL);
} }
static Scheme_Object *unsafe_add_post_custodian_shutdown(int argc, Scheme_Object *argv[])
{
scheme_check_proc_arity("unsafe-add-post-custodian-shutdown", 0, 0, argc, argv);
#if defined(MZ_USE_PLACES)
if (!RUNNING_IN_ORIGINAL_PLACE) {
if (!post_custodian_shutdowns) {
REGISTER_SO(post_custodian_shutdowns);
post_custodian_shutdowns = scheme_null;
}
post_custodian_shutdowns = scheme_make_pair(argv[0], post_custodian_shutdowns);
}
#endif
return scheme_void;
}
void scheme_run_post_custodian_shutdown()
{
#if defined(MZ_USE_PLACES)
if (post_custodian_shutdowns) {
Scheme_Object *proc;
scheme_start_in_scheduler();
while (SCHEME_PAIRP(post_custodian_shutdowns)) {
proc = SCHEME_CAR(post_custodian_shutdowns);
post_custodian_shutdowns = SCHEME_CDR(post_custodian_shutdowns);
_scheme_apply_multi(proc, 0, NULL);
}
scheme_end_in_scheduler();
}
#endif
}
void scheme_set_atexit(Scheme_At_Exit_Proc p) void scheme_set_atexit(Scheme_At_Exit_Proc p)
{ {
replacement_at_exit = p; replacement_at_exit = p;
@ -1923,12 +1967,6 @@ void scheme_set_atexit(Scheme_At_Exit_Proc p)
void scheme_add_atexit_closer(Scheme_Exit_Closer_Func f) void scheme_add_atexit_closer(Scheme_Exit_Closer_Func f)
{ {
#if defined(MZ_USE_PLACES)
# define RUNNING_IN_ORIGINAL_PLACE (scheme_current_place_id == 0)
#else
# define RUNNING_IN_ORIGINAL_PLACE 1
#endif
if (!cust_closers) { if (!cust_closers) {
if (RUNNING_IN_ORIGINAL_PLACE) { if (RUNNING_IN_ORIGINAL_PLACE) {
scheme_atexit(do_run_atexit_closers_on_all); scheme_atexit(do_run_atexit_closers_on_all);