diff --git a/pkgs/racket-test-core/tests/racket/hash.rktl b/pkgs/racket-test-core/tests/racket/hash.rktl index d39db5de47..f712b7d8de 100644 --- a/pkgs/racket-test-core/tests/racket/hash.rktl +++ b/pkgs/racket-test-core/tests/racket/hash.rktl @@ -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) diff --git a/racket/src/cs/rumble/hash.ss b/racket/src/cs/rumble/hash.ss index a420a86df6..f4c5810471 100644 --- a/racket/src/cs/rumble/hash.ss +++ b/racket/src/cs/rumble/hash.ss @@ -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]))))