cs: guard internal hash traversals against GCed keys
Hash iteration can fail if a GC collects a key in between `hash-iterate-next` and `hash-iterate-key` (and similar). Use the optional extra argument internally to detect and handle that case.
This commit is contained in:
parent
9db9991df6
commit
90abbb912d
|
@ -527,6 +527,44 @@
|
|||
(test-hash-ref-key/immut (hasheqv) eq? 'foo 'foo))
|
||||
|
||||
;; ----------------------------------------
|
||||
;;
|
||||
;; Run a GC concurrent to `hash-for-each` or `hash-map`
|
||||
;; to make sure a disappearing key doesn't break the
|
||||
;; iteration
|
||||
|
||||
(define (check-concurrent-gc-of-keys hash-iterate)
|
||||
(define gc-thread
|
||||
(thread
|
||||
(lambda ()
|
||||
(let loop ([n 10])
|
||||
(unless (zero? n)
|
||||
(collect-garbage)
|
||||
(sleep)
|
||||
(loop (sub1 n)))))))
|
||||
|
||||
(let loop ()
|
||||
(unless (thread-dead? gc-thread)
|
||||
(let ([ls (for/list ([i 100])
|
||||
(gensym))])
|
||||
(define ht (make-weak-hasheq))
|
||||
(for ([e (in-list ls)])
|
||||
(hash-set! ht e 0))
|
||||
;; `ls` is unreferenced here here on
|
||||
(define counter 0)
|
||||
(hash-iterate
|
||||
ht
|
||||
(lambda (k v)
|
||||
(set! counter (add1 counter))
|
||||
'ok))
|
||||
'(printf "~s @ ~a\n" counter j))
|
||||
(loop))))
|
||||
|
||||
(check-concurrent-gc-of-keys hash-for-each)
|
||||
(check-concurrent-gc-of-keys hash-map)
|
||||
(check-concurrent-gc-of-keys (lambda (ht proc)
|
||||
(equal? ht (hash-copy ht))))
|
||||
(check-concurrent-gc-of-keys (lambda (ht proc)
|
||||
(equal-hash-code ht)))
|
||||
|
||||
;; ----------------------------------------
|
||||
|
||||
(report-errs)
|
||||
|
|
|
@ -158,9 +158,11 @@
|
|||
(unless (impersonate-hash-clear ht #t)
|
||||
;; fall back to iterated remove
|
||||
(let loop ([i (hash-iterate-first ht)])
|
||||
(when i
|
||||
(hash-remove! ht (hash-iterate-key ht i))
|
||||
(loop (hash-iterate-next ht i)))))]
|
||||
(when i
|
||||
(let ([k (hash-iterate-key ht i none2)])
|
||||
(unless (eq? k none2)
|
||||
(hash-remove! ht k)))
|
||||
(loop (hash-iterate-next ht i)))))]
|
||||
[else (raise-argument-error 'hash-clear! "(and/c hash? (not/c immutable?))" ht)]))
|
||||
|
||||
(define (mutable-hash-clear! ht)
|
||||
|
@ -400,8 +402,9 @@
|
|||
;; mutable, impersonated, and weak-equal:
|
||||
(let loop ([i (hash-iterate-first ht)])
|
||||
(when i
|
||||
(let-values ([(key val) (hash-iterate-key+value ht i)])
|
||||
(|#%app| proc key val))
|
||||
(let-values ([(key val) (hash-iterate-key+value ht i none2)])
|
||||
(unless (eq? key none2)
|
||||
(|#%app| proc key val)))
|
||||
(loop (hash-iterate-next ht i))))])]))
|
||||
|
||||
(define/who hash-map
|
||||
|
@ -421,8 +424,9 @@
|
|||
(if (not i)
|
||||
'()
|
||||
(cons
|
||||
(let-values ([(key val) (hash-iterate-key+value ht i)])
|
||||
(|#%app| proc key val))
|
||||
(let-values ([(key val) (hash-iterate-key+value ht i none2)])
|
||||
(unless (eq? key none2)
|
||||
(|#%app| proc key val)))
|
||||
(loop (hash-iterate-next ht i)))))])]))
|
||||
|
||||
;; In sorted hash-table travesals, make some effort to sort the key.
|
||||
|
@ -551,12 +555,15 @@
|
|||
(cond
|
||||
[(not i) #t]
|
||||
[else
|
||||
(let-values ([(key val) (hash-iterate-key+value ht1 i)])
|
||||
(let ([val2 (hash-ref ht2 key none)])
|
||||
(cond
|
||||
[(eq? val2 none) #f]
|
||||
[else (and (eql? val val2)
|
||||
(loop (hash-iterate-next ht1 i)))])))])))]
|
||||
(let-values ([(key val) (hash-iterate-key+value ht1 i none2)])
|
||||
(if (eq? key none2)
|
||||
;; Ill-timed GC => start over
|
||||
(hash=? ht1 ht2 eql?)
|
||||
(let ([val2 (hash-ref ht2 key none)])
|
||||
(cond
|
||||
[(eq? val2 none) #f]
|
||||
[else (and (eql? val val2)
|
||||
(loop (hash-iterate-next ht1 i)))]))))])))]
|
||||
[else #f]))
|
||||
|
||||
|
||||
|
@ -572,14 +579,16 @@
|
|||
[else
|
||||
(let* ([eq-key? (hash-eq? ht)]
|
||||
[eqv-key? (and (not eq?) (hash-eqv? ht))])
|
||||
(let-values ([(key val) (hash-iterate-key+value ht i)])
|
||||
(let ([hc (hash-code-combine-unordered hc
|
||||
(cond
|
||||
[eq-key? (eq-hash-code key)]
|
||||
[eqv-key? (eqv-hash-code key)]
|
||||
[else (hash key)]))])
|
||||
(loop (hash-code-combine-unordered hc (hash val))
|
||||
(hash-iterate-next ht i)))))]))]))
|
||||
(let-values ([(key val) (hash-iterate-key+value ht i none2)])
|
||||
(if (eq? key none2)
|
||||
(loop hc (hash-iterate-next ht i))
|
||||
(let ([hc (hash-code-combine-unordered hc
|
||||
(cond
|
||||
[eq-key? (eq-hash-code key)]
|
||||
[eqv-key? (eqv-hash-code key)]
|
||||
[else (hash key)]))])
|
||||
(loop (hash-code-combine-unordered hc (hash val))
|
||||
(hash-iterate-next ht i))))))]))]))
|
||||
|
||||
|
||||
;; Start by getting just a few cells via `hashtable-cells`,
|
||||
|
@ -1354,8 +1363,9 @@
|
|||
[else (make-hash)])])])
|
||||
(let loop ([i (hash-iterate-first ht)])
|
||||
(cond
|
||||
[i (let-values ([(key val) (hash-iterate-key+value ht i)])
|
||||
(hash-set! new-ht key val)
|
||||
[i (let-values ([(key val) (hash-iterate-key+value ht i none2)])
|
||||
(unless (eq? key none2)
|
||||
(hash-set! new-ht key val))
|
||||
(loop (hash-iterate-next ht i)))]
|
||||
[else new-ht]))))
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user