fix custodian weak reference and memory-limit unregistration

For Racket CS, weaken references from managed objects to managing
custodians. Otherwise, a custodian with any managed values cannot
be GCed. Also, fix `collect-garbage` call that is triggered by
a memory-limit shutdown to happen after the current thread
(likely shutdown) is swapped out.

For traditional Racket, fix custodian shutdown on a memory-limited
custodian so that it is unregistered as having a limit.
This commit is contained in:
Matthew Flatt 2019-10-18 22:31:35 -06:00
parent 3c2efafbf5
commit 77023aeaba
3 changed files with 82 additions and 34 deletions

View File

@ -0,0 +1,24 @@
#lang racket/base
(define (go n)
(for ([i n])
(define c (make-custodian))
(custodian-limit-memory c (* 1024 1024 100) c)
(parameterize ([current-custodian c])
(thread (lambda () (sync (make-semaphore)))))
(sync (system-idle-evt))
(custodian-shutdown-all c)))
(define (get-use)
(sync (system-idle-evt))
(collect-garbage)
(current-memory-use))
(go 1000)
(define start-use (current-memory-use))
(go 100000)
(define end-use (current-memory-use))
(unless (end-use . < . (* 2 start-use))
(error "something accumulated too much: " start-use end-use))

View File

@ -855,7 +855,7 @@ static void adjust_limit_table(Scheme_Custodian *c)
{ {
/* If a custodian has a limit and any object or children, then it /* If a custodian has a limit and any object or children, then it
must not be collected and merged with its parent. To prevent must not be collected and merged with its parent. To prevent
collection, we register the custodian in the `limite_custodians' collection, we register the custodian in the `limited_custodians'
table. */ table. */
if (c->has_limit) { if (c->has_limit) {
if (c->elems || CUSTODIAN_FAM(c->children)) { if (c->elems || CUSTODIAN_FAM(c->children)) {
@ -1576,8 +1576,10 @@ Scheme_Thread *scheme_do_close_managed(Scheme_Custodian *m, Scheme_Exit_Closer_F
m->mrefs = NULL; m->mrefs = NULL;
m->shut_down = 1; m->shut_down = 1;
if (SAME_OBJ(m, start)) if (SAME_OBJ(m, start)) {
adjust_limit_table(m);
break; break;
}
next_m = CUSTODIAN_FAM(m->global_prev); next_m = CUSTODIAN_FAM(m->global_prev);
/* Remove this custodian from its parent */ /* Remove this custodian from its parent */

View File

@ -65,8 +65,8 @@
;; Reporting registration in a custodian through this indirection ;; Reporting registration in a custodian through this indirection
;; enables GCing custodians that aren't directly referenced, merging ;; enables GCing custodians that aren't directly referenced, merging
;; the managed objects into the parent. To support multiple moves, ;; the managed objects into the parent. To support multiple moves,
;; `c` can be another reference ;; `weak-c` can be another reference.
(struct custodian-reference ([c #:mutable]) (struct custodian-reference ([weak-c #:mutable])
#:authentic) #:authentic)
(define-place-local custodian-will-executor (host:make-late-will-executor void #f)) (define-place-local custodian-will-executor (host:make-late-will-executor void #f))
@ -82,8 +82,7 @@
(define (set-root-custodian! c) (define (set-root-custodian! c)
(set! root-custodian c) (set! root-custodian c)
(current-custodian c) (current-custodian c)
(set! custodian-will-executor (host:make-late-will-executor void #f)) (set! custodian-will-executor (host:make-late-will-executor void #f)))
(set! custodians-with-limits (make-hasheq)))
(define/who (make-custodian [parent (current-custodian)]) (define/who (make-custodian [parent (current-custodian)])
(check who custodian? parent) (check who custodian? parent)
@ -95,6 +94,7 @@
(lambda (c) (lambda (c)
(reference-sink children) (reference-sink children)
(do-custodian-shutdown-all c))) (do-custodian-shutdown-all c)))
#:weak? #t
#:at-exit? #t #:at-exit? #t
#:gc-root? #t)) #:gc-root? #t))
(set-custodian-parent-reference! c cref) (set-custodian-parent-reference! c cref)
@ -141,7 +141,7 @@
(hash-set! (custodian-gc-roots cust) obj #t) (hash-set! (custodian-gc-roots cust) obj #t)
(host:enable-interrupts)) (host:enable-interrupts))
(or (custodian-self-reference cust) (or (custodian-self-reference cust)
(let ([cref (custodian-reference cust)]) (let ([cref (custodian-reference (make-weak-box cust))])
(set-custodian-self-reference! cust cref) (set-custodian-self-reference! cust cref)
cref))]))) cref))])))
@ -174,7 +174,8 @@
(define parent (custodian-reference->custodian p-cref)) (define parent (custodian-reference->custodian p-cref))
(define gc-roots (custodian-gc-roots c)) (define gc-roots (custodian-gc-roots c))
(unsafe-custodian-unregister c p-cref) (unsafe-custodian-unregister c p-cref)
(for ([(child callback) (in-hash (custodian-children c))]) (for ([(child callback) (in-hash (custodian-children c) #f)])
(when child
(define gc-root? (and gc-roots (hash-ref gc-roots child #f) #t)) (define gc-root? (and gc-roots (hash-ref gc-roots child #f) #t))
(cond (cond
[(willed-callback? callback) [(willed-callback? callback)
@ -183,10 +184,10 @@
#:gc-root? gc-root?)] #:gc-root? gc-root?)]
[else [else
(do-custodian-register parent child callback (do-custodian-register parent child callback
#:gc-root? gc-root?)])) #:gc-root? gc-root?)])))
(define self-ref (custodian-self-reference c)) (define self-ref (custodian-self-reference c))
(when self-ref (when self-ref
(set-custodian-reference-c! self-ref (custodian-self-reference parent))) (set-custodian-reference-weak-c! self-ref (custodian-self-reference parent)))
(hash-clear! (custodian-children c)) (hash-clear! (custodian-children c))
(set-custodian-post-shutdown! parent (set-custodian-post-shutdown! parent
(append (custodian-post-shutdown c) (append (custodian-post-shutdown c)
@ -236,6 +237,9 @@
;; Called in atomic mode by the scheduler ;; Called in atomic mode by the scheduler
(define (check-queued-custodian-shutdown) (define (check-queued-custodian-shutdown)
(when gc-on-check-queue?
(set! gc-on-check-queue? #f)
(collect-garbage))
(cond (cond
[(null? queued-shutdowns) #f] [(null? queued-shutdowns) #f]
[else [else
@ -260,10 +264,16 @@
;; major GC to clean up and reset the expected heap size. ;; major GC to clean up and reset the expected heap size.
;; Otherwise, if another limit is put in place, it will be ;; Otherwise, if another limit is put in place, it will be
;; checked (on a major GC) even later, which will set the ;; checked (on a major GC) even later, which will set the
;; major-GC trigger even higher, and so on. ;; major-GC trigger even higher, and so on. Since the
(collect-garbage) ;; scheduler is likely working in the context of a thread
;; that was shut down by the custodian, defer the GC to
;; the scheduler's next time around.
(set! gc-on-check-queue? #t)
#t])) #t]))
;; Pending GC due to memory-limit custodian shutdown?
(define-place-local gc-on-check-queue? #f)
(define place-ensure-wakeup! (lambda () #f)) ; call before enabling shutdowns (define place-ensure-wakeup! (lambda () #f)) ; call before enabling shutdowns
(define place-wakeup-initial void) (define place-wakeup-initial void)
(define place-wakeup void) (define place-wakeup void)
@ -281,10 +291,11 @@
(set-custodian-shut-down! c) (set-custodian-shut-down! c)
(when (custodian-sync-futures? c) (when (custodian-sync-futures? c)
(futures-sync-for-custodian-shutdown)) (futures-sync-for-custodian-shutdown))
(for ([(child callback) (in-hash (custodian-children c))]) (for ([(child callback) (in-hash (custodian-children c) #f)])
(when child
(if (procedure-arity-includes? callback 2) (if (procedure-arity-includes? callback 2)
(callback child c) (callback child c)
(callback child))) (callback child))))
(hash-clear! (custodian-children c)) (hash-clear! (custodian-children c))
(for ([proc (in-list (custodian-post-shutdown c))]) (for ([proc (in-list (custodian-post-shutdown c))])
(proc)) (proc))
@ -295,7 +306,8 @@
(define p-cref (custodian-parent-reference c)) (define p-cref (custodian-parent-reference c))
(when p-cref (when p-cref
(unsafe-custodian-unregister c p-cref)) (unsafe-custodian-unregister c p-cref))
(hash-remove! custodians-with-limits c))) (set-custodian-memory-limits! c null)
(remove-limit-custodian! c)))
(define (custodian-get-shutdown-sema c) (define (custodian-get-shutdown-sema c)
(atomically (atomically
@ -329,17 +341,17 @@
(custodian-subordinate? ref-c c))) (custodian-subordinate? ref-c c)))
(define (custodian-reference->custodian cref) (define (custodian-reference->custodian cref)
(define c (custodian-reference-c cref)) (define c (custodian-reference-weak-c cref))
(cond (cond
[(custodian-reference? c) [(custodian-reference? c)
(define next-c (custodian-reference-c c)) (define next-c (custodian-reference-weak-c c))
(cond (cond
[(custodian-reference? next-c) [(custodian-reference? next-c)
;; shrink the chain ;; shrink the chain
(set-custodian-reference-c! cref next-c) (set-custodian-reference-weak-c! cref next-c)
(custodian-reference->custodian cref)] (custodian-reference->custodian cref)]
[else next-c])] [else (weak-box-value next-c)])]
[else c])) [else (weak-box-value c)]))
(define/who (custodian-managed-list c super-c) (define/who (custodian-managed-list c super-c)
(check who custodian? c) (check who custodian? c)
@ -374,14 +386,24 @@
(define old-limit (custodian-immediate-limit limit-cust)) (define old-limit (custodian-immediate-limit limit-cust))
(when (or (not old-limit) (old-limit . > . need-amt)) (when (or (not old-limit) (old-limit . > . need-amt))
(set-custodian-immediate-limit! limit-cust need-amt))) (set-custodian-immediate-limit! limit-cust need-amt)))
(hash-set! custodians-with-limits limit-cust #t) (host:disable-interrupts)
(host:mutex-acquire memory-limit-lock) (host:mutex-acquire memory-limit-lock)
(hash-set! custodians-with-limits limit-cust #t)
(set! compute-memory-sizes (max compute-memory-sizes 1)) (set! compute-memory-sizes (max compute-memory-sizes 1))
(host:mutex-release memory-limit-lock))) (host:mutex-release memory-limit-lock)
(host:enable-interrupts)))
;; Ensures that custodians with memory limits are not treated as ;; Ensures that custodians with memory limits are not treated as
;; inaccessible and merged: ;; inaccessible and merged; use only while holding the memory-limit
(define-place-local custodians-with-limits (make-hasheq)) ;; lock and with interrupts disabled (or be in a GC)
(define custodians-with-limits (make-hasheq))
(define (remove-limit-custodian! c)
(host:disable-interrupts)
(host:mutex-acquire memory-limit-lock)
(hash-remove! custodians-with-limits c)
(host:mutex-release memory-limit-lock)
(host:enable-interrupts))
;; ---------------------------------------- ;; ----------------------------------------