ffi/unsafe/custodian: handle already-shutdown custodians

This commit is contained in:
Matthew Flatt 2017-06-29 14:24:52 -06:00
parent eb24c1e0b1
commit 63146e5451
4 changed files with 51 additions and 13 deletions

View File

@ -17,7 +17,9 @@ registering shutdown callbacks with custodians.}
Registers @racket[callback] to be applied (in atomic mode and an Registers @racket[callback] to be applied (in atomic mode and an
unspecified Racket thread) to @racket[v] when @racket[custodian] is unspecified Racket thread) to @racket[v] when @racket[custodian] is
shutdown. The result is a pointer that can be supplied to shutdown. If @racket[custodian] is already shut down, the result is
@racket[#f] and @racket[v] is not registered. Otherwise, the result is
a pointer that can be supplied to
@racket[unregister-custodian-shutdown] to remove the registration. @racket[unregister-custodian-shutdown] to remove the registration.
If @racket[at-exit?] is true, then @racket[callback] is applied when If @racket[at-exit?] is true, then @racket[callback] is applied when
@ -43,26 +45,35 @@ be no longer registered to the custodian, while the finalizer for
@defproc[(unregister-custodian-shutdown [v any/c] @defproc[(unregister-custodian-shutdown [v any/c]
[registration _cpointer]) [registration cpointer?])
void?]{ void?]{
Cancels a custodian-shutdown registration, where @racket[registration] Cancels a custodian-shutdown registration, where @racket[registration]
is a previous result from @racket[register-custodian-shutdown] applied is a previous result from @racket[register-custodian-shutdown] applied
to @racket[v].} to @racket[v]. If @racket[registration] is @racket[#f], then no action
is taken.}
@defproc[(register-finalizer-and-custodian-shutdown @defproc[(register-finalizer-and-custodian-shutdown
[v any/c] [v any/c]
[callback (any/c . -> . any)] [callback (any/c . -> . any)]
[custodian custodian? (current-custodian)] [custodian custodian? (current-custodian)]
[#:at-exit? at-exit? any/c #f] [#:at-exit? at-exit? any/c #f]
[#:weak? weak? any/c #f]) [#:weak? weak? any/c #f]
void?]{ [#:custodian-unavailable unavailable-callback ((-> void?) -> any) (lambda (reg-fnl) (reg-fnl))])
any]{
Registers @racket[callback] to be applied (in atomic mode) to Registers @racket[callback] to be applied (in atomic mode) to
@racket[v] when @racket[custodian] is shutdown or when @racket[v] is @racket[v] when @racket[custodian] is shutdown or when @racket[v] is
about to be collected by the garbage collector, whichever happens about to be collected by the garbage collector, whichever happens
first. The @racket[callback] is only applied to @racket[v] once. first. The @racket[callback] is only applied to @racket[v] once.
If @racket[custodian] is already shut down, then
@racket[unavailable-callback] is applied in tail position to a
function that registers a finalizer. By default, a finalizer is
registered anyway, but usually a better choice is to report an error.
If @racket[custodian] is not already shut down, then the result
from @racket[register-finalizer-and-custodian-shutdown] is @|void-const|.
@history[#:added "6.1.1.6"]} @history[#:added "6.1.1.6"]}

View File

@ -60,3 +60,20 @@
(for ([i 10]) (for ([i 10])
(unless (go) (unless (go)
(error "shutdown failed"))) (error "shutdown failed")))
;; ----------------------------------------
;; Check that already-shutdown custodians are handled
(when (register-custodian-shutdown 88 void c)
(error "should have been #f due to shutdown"))
(unless (eq? 'cb
(register-finalizer-and-custodian-shutdown
88 void c
#:custodian-unavailable (lambda (proc)
(unless (and (procedure? proc)
(procedure-arity-includes? proc 0))
(error "should have received a thunk"))
'cb)))
(error "custodian-shutdown callback wasn't called"))
(unregister-custodian-shutdown 'anything #f)

View File

@ -20,11 +20,13 @@
(unsafe-custodian-register custodian obj proc at-exit? weak?)) (unsafe-custodian-register custodian obj proc at-exit? weak?))
(define (unregister-custodian-shutdown obj mref) (define (unregister-custodian-shutdown obj mref)
(unsafe-custodian-unregister obj mref)) (when mref
(unsafe-custodian-unregister obj mref)))
(define (register-finalizer-and-custodian-shutdown value callback (define (register-finalizer-and-custodian-shutdown value callback
[custodian (current-custodian)] [custodian (current-custodian)]
#:at-exit? [at-exit? #f]) #:at-exit? [at-exit? #f]
#:custodian-unavailable [custodian-unavailable (lambda (r) (r))])
(define done? #f) (define done? #f)
(define (do-callback obj) ; called in atomic mode (define (do-callback obj) ; called in atomic mode
(unless done? (unless done?
@ -32,9 +34,14 @@
(callback obj))) (callback obj)))
(define registration (define registration
(register-custodian-shutdown value do-callback custodian #:at-exit? at-exit?)) (register-custodian-shutdown value do-callback custodian #:at-exit? at-exit?))
(register-finalizer value (define (do-finalizer)
(lambda (obj) (register-finalizer
(call-as-atomic value
(lambda () (lambda (obj)
(unregister-custodian-shutdown obj registration) (call-as-atomic
(do-callback obj)))))) (lambda ()
(unregister-custodian-shutdown obj registration)
(do-callback obj))))))
(if registration
(do-finalizer)
(custodian-unavailable do-finalizer)))

View File

@ -1378,6 +1378,9 @@ static Scheme_Object *unsafe_custodian_register(int argc, Scheme_Object *argv[])
if (!SCHEME_PROCP(callback)) if (!SCHEME_PROCP(callback))
scheme_wrong_contract("unsafe-custodian-register", "procedure?", 2, argc, argv); scheme_wrong_contract("unsafe-custodian-register", "procedure?", 2, argc, argv);
if (!scheme_custodian_is_available(custodian))
return scheme_false;
if (at_exit) if (at_exit)
mr = scheme_add_managed_close_on_exit(custodian, v, call_registered_callback, callback); mr = scheme_add_managed_close_on_exit(custodian, v, call_registered_callback, callback);
else else