cs: fix races with places in the Rumble layer

This commit is contained in:
Matthew Flatt 2018-08-29 18:56:39 -06:00
parent 8b4a17e33e
commit 8c5eebc513
13 changed files with 127 additions and 82 deletions

View File

@ -240,6 +240,7 @@ RUMBLE_SRCS = rumble/define.ss \
rumble/hash.ss \ rumble/hash.ss \
rumble/datum.ss \ rumble/datum.ss \
rumble/lock.ss \ rumble/lock.ss \
rumble/thread-local.ss \
rumble/thread-cell.ss \ rumble/thread-cell.ss \
rumble/parameter.ss \ rumble/parameter.ss \
rumble/begin0.ss \ rumble/begin0.ss \

View File

@ -32,7 +32,7 @@
(define-syntax define (define-syntax define
(syntax-rules () (syntax-rules ()
[(_ id rhs) (set! id rhs)])) [(_ id rhs) (set! id rhs)]))
(check #t (thread? (current-thread))) (check #t (thread? (current-thread)))
(check #t (evt? (current-thread))) (check #t (evt? (current-thread)))
(define s (make-semaphore)) (define s (make-semaphore))

View File

@ -687,8 +687,11 @@
(include "rumble/define.ss") (include "rumble/define.ss")
(include "rumble/virtual-register.ss") (include "rumble/virtual-register.ss")
(include "rumble/version.ss") (include "rumble/begin0.ss")
(include "rumble/syntax-rule.ss") (include "rumble/syntax-rule.ss")
(include "rumble/lock.ss")
(include "rumble/thread-local.ss")
(include "rumble/version.ss")
(include "rumble/check.ss") (include "rumble/check.ss")
(include "rumble/constant.ss") (include "rumble/constant.ss")
(include "rumble/hash-code.ss") (include "rumble/hash-code.ss")
@ -702,11 +705,9 @@
(include "rumble/object-name.ss") (include "rumble/object-name.ss")
(include "rumble/arity.ss") (include "rumble/arity.ss")
(include "rumble/intmap.ss") (include "rumble/intmap.ss")
(include "rumble/lock.ss")
(include "rumble/hash.ss") (include "rumble/hash.ss")
(include "rumble/datum.ss") (include "rumble/datum.ss")
(include "rumble/thread-cell.ss") (include "rumble/thread-cell.ss")
(include "rumble/begin0.ss")
(include "rumble/pthread.ss") (include "rumble/pthread.ss")
(include "rumble/control.ss") (include "rumble/control.ss")
(include "rumble/interrupt.ss") (include "rumble/interrupt.ss")

View File

@ -440,7 +440,7 @@
;; In case the escape handler doesn't escape: ;; In case the escape handler doesn't escape:
(default-error-escape-handler))) (default-error-escape-handler)))
(define link-instantiate-continuations (make-ephemeron-eq-hashtable)) (define-thread-local link-instantiate-continuations (make-ephemeron-eq-hashtable))
;; For `instantiate-linklet` to help report which linklet is being run: ;; For `instantiate-linklet` to help report which linklet is being run:
(define (register-linklet-instantiate-continuation! k name) (define (register-linklet-instantiate-continuation! k name)
@ -450,7 +450,7 @@
;; Convert a contination to a list of function-name and ;; Convert a contination to a list of function-name and
;; source information. Cache the result half-way up the ;; source information. Cache the result half-way up the
;; traversal, so that it's amortized constant time. ;; traversal, so that it's amortized constant time.
(define cached-traces (make-ephemeron-eq-hashtable)) (define-thread-local cached-traces (make-ephemeron-eq-hashtable))
(define (continuation->trace k) (define (continuation->trace k)
(call-with-values (call-with-values
(lambda () (lambda ()

View File

@ -1270,7 +1270,8 @@
(define the-foreign-guardian (make-guardian)) (define the-foreign-guardian (make-guardian))
;; Can be called in any host thread ;; Can be called in any host thread, but all other
;; threads are stopped
(define (poll-foreign-guardian) (define (poll-foreign-guardian)
(let ([v (the-foreign-guardian)]) (let ([v (the-foreign-guardian)])
(when v (when v
@ -1287,8 +1288,8 @@
(set! eval/foreign proc)) (set! eval/foreign proc))
;; Cache generated code for an underlying foreign call or callable shape: ;; Cache generated code for an underlying foreign call or callable shape:
(define ffi-expr->code (make-weak-hash)) ; expr to weak cell of code (define-thread-local ffi-expr->code (make-weak-hash)) ; expr to weak cell of code
(define ffi-code->expr (make-weak-eq-hashtable)) ; keep exprs alive as long as code lives (define-thread-local ffi-code->expr (make-weak-eq-hashtable)) ; keep exprs alive as long as code lives
(define/who ffi-call (define/who ffi-call
(case-lambda (case-lambda
@ -1659,7 +1660,7 @@
(let* ([code (make-code proc)] (let* ([code (make-code proc)]
[cb (create-callback code)]) [cb (create-callback code)])
(lock-object code) (lock-object code)
(the-foreign-guardian cb (lambda () (unlock-object code))) (with-global-lock (the-foreign-guardian cb (lambda () (unlock-object code))))
cb)))])) cb)))]))
;; ---------------------------------------- ;; ----------------------------------------
@ -1714,23 +1715,19 @@
;; ---------------------------------------- ;; ----------------------------------------
(define process-global-table (make-hashtable equal-hash-code equal?)) (define process-global-table (make-hashtable equal-hash-code equal?))
(define process-table-lock (make-mutex))
(define (unsafe-register-process-global key val) (define (unsafe-register-process-global key val)
(with-interrupts-disabled (with-global-lock
(mutex-acquire process-table-lock) (cond
(let ([result (cond [(not val)
[(not val) (hashtable-ref process-global-table key #f)]
(hashtable-ref process-global-table key #f)] [else
[else (let ([old-val (hashtable-ref process-global-table key #f)])
(let ([old-val (hashtable-ref process-global-table key #f)]) (cond
(cond [(not old-val)
[(not old-val) (hashtable-set! process-global-table key val)
(hashtable-set! process-global-table key val) #f]
#f] [else old-val]))])))
[else old-val]))])])
(mutex-release process-table-lock)
result)))
;; ---------------------------------------- ;; ----------------------------------------

View File

@ -29,11 +29,12 @@
[(number? x) (number-hash x)] [(number? x) (number-hash x)]
[(char? x) (char->integer x)] [(char? x) (char->integer x)]
[else [else
(or (eq-hashtable-ref codes x #f) (with-global-lock
(let ([c (fx1+ counter)]) (or (eq-hashtable-ref codes x #f)
(set! counter c) (let ([c (fx1+ counter)])
(eq-hashtable-set! codes x counter) (set! counter c)
c))])) (eq-hashtable-set! codes x counter)
c)))]))
;; Mostly copied from Chez Scheme's "newhash.ss": ;; Mostly copied from Chez Scheme's "newhash.ss":
(define number-hash (define number-hash

View File

@ -8,12 +8,13 @@
(define/who (string->keyword s) (define/who (string->keyword s)
(check who string? s) (check who string? s)
(let ([sym (string->symbol s)]) (let ([sym (string->symbol s)])
(let ([e (eq-hashtable-ref keywords sym #f)]) (with-global-lock
(or (and e (let ([e (eq-hashtable-ref keywords sym #f)])
(ephemeron-value e)) (or (and e
(let ([kw (make-keyword sym)]) (ephemeron-value e))
(eq-hashtable-set! keywords sym (make-ephemeron sym kw)) (let ([kw (make-keyword sym)])
kw))))) (eq-hashtable-set! keywords sym (make-ephemeron sym kw))
kw))))))
(define/who (keyword->string kw) (define/who (keyword->string kw)
(check who keyword? kw) (check who keyword? kw)

View File

@ -6,7 +6,7 @@
;; Detect cycles using the same `slow` tortoise that is used for ;; Detect cycles using the same `slow` tortoise that is used for
;; caching. ;; caching.
(define lists (make-weak-eq-hashtable)) (define-thread-local lists (make-weak-eq-hashtable))
(define (list? v) (define (list? v)
(let loop ([v v] [depth 0]) (let loop ([v v] [depth 0])

View File

@ -1,8 +1,8 @@
;; locking code for hash.ss ;; locking code for hash.ss
(define make-scheduler-lock (lambda () #f)) (define make-scheduler-lock (lambda () #f))
(define scheduler-lock-acquire (lambda (l) (void))) (define scheduler-lock-acquire (lambda (l) (#%void)))
(define scheduler-lock-release (lambda (l) (void))) (define scheduler-lock-release (lambda (l) (#%void)))
(define (set-scheduler-lock-callbacks! make acquire release) (define (set-scheduler-lock-callbacks! make acquire release)
(set! make-scheduler-lock make) (set! make-scheduler-lock make)
@ -37,7 +37,17 @@
(define (lock-release lock) (define (lock-release lock)
(when lock (when lock
(scheduler-lock-release lock)))] (scheduler-lock-release lock)))
;; Use `with-global-lock*` when no lock is needed absent threads
(define-syntax-rule (with-global-lock* e ...)
(begin e ...))
;; Use `with-global-lock` when a lock is needed to prevent
;; engine-based concurrency
(define-syntax-rule (with-global-lock e ...)
(with-interrupts-disabled
e))]
[else [else
;; Using a Chez Scheme build with thread support; make hash-table ;; Using a Chez Scheme build with thread support; make hash-table
;; access thread-safe at that level for `eq?`- and `eqv?`-based ;; access thread-safe at that level for `eq?`- and `eqv?`-based
@ -49,7 +59,7 @@
;; Assume low contention on `eq?`- and `eqv?`-based tables across ;; Assume low contention on `eq?`- and `eqv?`-based tables across
;; Chez Scheme threads, in which case a compare-and-set spinlock is ;; Chez Scheme threads, in which case a compare-and-set spinlock is
;; good enough. ;; good enough.
;; Taking a lock disables interrupts, whcih ensures that the GC ;; Taking a lock disables interrupts, which ensures that the GC
;; callback or other atomic actions can use hash tables without ;; callback or other atomic actions can use hash tables without
;; deadlocking. ;; deadlocking.
(define (make-spinlock) (box #f)) (define (make-spinlock) (box #f))
@ -63,7 +73,7 @@
(define (spinlock-release q) (define (spinlock-release q)
(#%set-box! q #f) (#%set-box! q #f)
(enable-interrupts) (enable-interrupts)
(void)) (#%void))
(define (make-lock for-kind) (define (make-lock for-kind)
(cond (cond
@ -76,14 +86,14 @@
(case-lambda (case-lambda
[(lock) [(lock)
(cond (cond
[(not lock) (void)] [(not lock) (#%void)]
[(spinlock? lock) [(spinlock? lock)
(spinlock-acquire lock)] (spinlock-acquire lock)]
[else [else
(scheduler-lock-acquire lock)])] (scheduler-lock-acquire lock)])]
[(lock block?) [(lock block?)
(cond (cond
[(not lock) (void)] [(not lock) (#%void)]
[(spinlock? lock) [(spinlock? lock)
(spinlock-acquire lock block?)] (spinlock-acquire lock block?)]
[else [else
@ -91,8 +101,18 @@
(define (lock-release lock) (define (lock-release lock)
(cond (cond
[(not lock) (void)] [(not lock) (#%void)]
[(spinlock? lock) [(spinlock? lock)
(spinlock-release lock)] (spinlock-release lock)]
[else [else
(scheduler-lock-release lock)]))]) (scheduler-lock-release lock)]))
(define global-lock (make-spinlock))
(define-syntax-rule (with-global-lock* e ...)
(with-global-lock e ...))
(define-syntax-rule (with-global-lock e ...)
(begin
(spinlock-acquire global-lock)
(begin0
(begin e ...)
(spinlock-release global-lock))))])

View File

@ -58,5 +58,5 @@
(and (and
;; Having an entry in `rtd-props` is a sign that ;; Having an entry in `rtd-props` is a sign that
;; this structure type was created with `make-struct-type`: ;; this structure type was created with `make-struct-type`:
(hashtable-contains? rtd-props rtd) (with-global-lock* (hashtable-contains? rtd-props rtd))
(object-name (record-rtd v))))) (object-name (record-rtd v)))))

View File

@ -92,9 +92,10 @@
pv)) pv))
(do-fail fail v)))])] (do-fail fail v)))])]
[(v) (acc v default-fail)])]) [(v) (acc v default-fail)])])
(hashtable-set! property-accessors (with-global-lock*
acc (hashtable-set! property-accessors
(cons pred can-impersonate?)) acc
(cons pred can-impersonate?)))
(values st (values st
pred pred
acc)))])) acc)))]))
@ -102,14 +103,14 @@
(define (struct-type-property-accessor-procedure? v) (define (struct-type-property-accessor-procedure? v)
(and (procedure? v) (and (procedure? v)
(let ([v (strip-impersonator v)]) (let ([v (strip-impersonator v)])
(hashtable-ref property-accessors v #f)) (with-global-lock* (hashtable-ref property-accessors v #f)))
#t)) #t))
(define (struct-type-property-accessor-procedure-pred v) (define (struct-type-property-accessor-procedure-pred v)
(car (hashtable-ref property-accessors v #f))) (car (with-global-lock (hashtable-ref property-accessors v #f))))
(define (struct-type-property-accessor-procedure-can-impersonate? v) (define (struct-type-property-accessor-procedure-can-impersonate? v)
(cdr (hashtable-ref property-accessors v #f))) (cdr (with-global-lock* (hashtable-ref property-accessors v #f))))
(define (struct-property-ref prop rtd default) (define (struct-property-ref prop rtd default)
(getprop (record-type-uid rtd) prop default)) (getprop (record-type-uid rtd) prop default))
@ -360,44 +361,46 @@
(define struct-field-mutators (make-ephemeron-eq-hashtable)) (define struct-field-mutators (make-ephemeron-eq-hashtable))
(define (register-struct-constructor! p) (define (register-struct-constructor! p)
(hashtable-set! struct-constructors p #t)) (with-global-lock* (hashtable-set! struct-constructors p #t)))
(define (register-struct-predicate! p) (define (register-struct-predicate! p)
(hashtable-set! struct-predicates p #t)) (with-global-lock* (hashtable-set! struct-predicates p #t)))
(define (register-struct-field-accessor! p rtd pos) (define (register-struct-field-accessor! p rtd pos)
(hashtable-set! struct-field-accessors p (cons rtd pos))) (with-global-lock* (hashtable-set! struct-field-accessors p (cons rtd pos))))
(define (register-struct-field-mutator! p rtd pos) (define (register-struct-field-mutator! p rtd pos)
(hashtable-set! struct-field-mutators p (cons rtd pos))) (with-global-lock* (hashtable-set! struct-field-mutators p (cons rtd pos))))
(define (struct-constructor-procedure? v) (define (struct-constructor-procedure? v)
(and (procedure? v) (and (procedure? v)
(hashtable-ref struct-constructors (strip-impersonator v) #f))) (let ([v (strip-impersonator v)])
(with-global-lock* (hashtable-ref struct-constructors v #f)))))
(define (struct-predicate-procedure? v) (define (struct-predicate-procedure? v)
(and (procedure? v) (and (procedure? v)
(hashtable-ref struct-predicates (strip-impersonator v) #f))) (let ([v (strip-impersonator v)])
(with-global-lock* (hashtable-ref struct-predicates v #f)))))
(define (struct-accessor-procedure? v) (define (struct-accessor-procedure? v)
(and (procedure? v) (and (procedure? v)
(let ([v (strip-impersonator v)]) (let ([v (strip-impersonator v)])
(or (position-based-accessor? v) (or (position-based-accessor? v)
(hashtable-ref struct-field-accessors v #f))) (with-global-lock* (hashtable-ref struct-field-accessors v #f))))
#t)) #t))
(define (struct-mutator-procedure? v) (define (struct-mutator-procedure? v)
(and (procedure? v) (and (procedure? v)
(let ([v (strip-impersonator v)]) (let ([v (strip-impersonator v)])
(or (position-based-mutator? v) (or (position-based-mutator? v)
(hashtable-ref struct-field-mutators v #f))) (with-global-lock* (hashtable-ref struct-field-mutators v #f))))
#t)) #t))
(define (struct-accessor-procedure-rtd+pos v) (define (struct-accessor-procedure-rtd+pos v)
(hashtable-ref struct-field-accessors v #f)) (with-global-lock* (hashtable-ref struct-field-accessors v #f)))
(define (struct-mutator-procedure-rtd+pos v) (define (struct-mutator-procedure-rtd+pos v)
(hashtable-ref struct-field-mutators v #f)) (with-global-lock* (hashtable-ref struct-field-mutators v #f)))
;; ---------------------------------------- ;; ----------------------------------------
@ -498,7 +501,7 @@
(let* ([parent-rtd* (strip-impersonator parent-rtd)] (let* ([parent-rtd* (strip-impersonator parent-rtd)]
[parent-props [parent-props
(if parent-rtd* (if parent-rtd*
(hashtable-ref rtd-props parent-rtd* '()) (with-global-lock* (hashtable-ref rtd-props parent-rtd* '()))
'())] '())]
[all-immutables (if (integer? proc-spec) [all-immutables (if (integer? proc-spec)
(cons proc-spec immutables) (cons proc-spec immutables)
@ -508,12 +511,13 @@
(record-type-equal-procedure rtd default-struct-equal?) (record-type-equal-procedure rtd default-struct-equal?)
(record-type-hash-procedure rtd default-struct-hash)) (record-type-hash-procedure rtd default-struct-hash))
;; Record properties implemented by this type: ;; Record properties implemented by this type:
(hashtable-set! rtd-props rtd (let ([props (append (map car props) parent-props)]) (let ([props (let ([props (append (map car props) parent-props)])
(if proc-spec (if proc-spec
(cons prop:procedure props) (cons prop:procedure props)
props))) props))])
(with-global-lock* (hashtable-set! rtd-props rtd props)))
(unless (equal? '#() mutables) (unless (equal? '#() mutables)
(hashtable-set! rtd-mutables rtd mutables)) (with-global-lock* (hashtable-set! rtd-mutables rtd mutables)))
;; Copy parent properties for this type: ;; Copy parent properties for this type:
(for-each (lambda (prop) (for-each (lambda (prop)
(let loop ([prop prop]) (let loop ([prop prop])
@ -548,9 +552,8 @@
(prefab-key+count->rtd (cons prefab-key total*-count))))) (prefab-key+count->rtd (cons prefab-key total*-count)))))
(define (prefab-ref prefab-key+count) (define (prefab-ref prefab-key+count)
(with-interrupts-disabled ; atomic access of `prefabs` (and prefabs
(and prefabs (hash-ref prefabs prefab-key+count #f)))
(hash-ref prefabs prefab-key+count #f))))
(define (prefab-key+count->rtd prefab-key+count) (define (prefab-key+count->rtd prefab-key+count)
(cond (cond
@ -575,16 +578,15 @@
uid #f #f uid #f #f
(make-fields total-count))] (make-fields total-count))]
[mutables (prefab-key-mutables prefab-key)]) [mutables (prefab-key-mutables prefab-key)])
(with-interrupts-disabled (with-global-lock
(cond (cond
[(prefab-ref prefab-key+count) [(prefab-ref prefab-key+count)
;; rtd was created concurrently ;; rtd was created concurrently
=> (lambda (rtd) rtd)] => (lambda (rtd) rtd)]
[else [else
(putprop uid 'prefab-key+count prefab-key+count) (putprop uid 'prefab-key+count prefab-key+count)
(with-interrupts-disabled ; atomic use of `prefabs` table (unless prefabs (set! prefabs (make-weak-hash)))
(unless prefabs (set! prefabs (make-weak-hash))) (hash-set! prefabs prefab-key+count rtd)
(hash-set! prefabs prefab-key+count rtd))
(unless parent-rtd (unless parent-rtd
(record-type-equal-procedure rtd default-struct-equal?) (record-type-equal-procedure rtd default-struct-equal?)
(record-type-hash-procedure rtd default-struct-hash)) (record-type-hash-procedure rtd default-struct-hash))
@ -722,7 +724,7 @@
auto-count auto-count
(make-position-based-accessor rtd* parent-total*-count (+ init-count auto-count)) (make-position-based-accessor rtd* parent-total*-count (+ init-count auto-count))
(make-position-based-mutator rtd* parent-total*-count (+ init-count auto-count)) (make-position-based-mutator rtd* parent-total*-count (+ init-count auto-count))
(mutables->immutables (hashtable-ref rtd-mutables rtd* '#()) init-count) (mutables->immutables (with-global-lock* (hashtable-ref rtd-mutables rtd* '#())) init-count)
next-rtd* next-rtd*
skipped?))]) skipped?))])
(cond (cond
@ -884,7 +886,7 @@
;; ---------------------------------------- ;; ----------------------------------------
(define (struct-type-field-mutable? rtd pos) (define (struct-type-field-mutable? rtd pos)
(let ([mutables (hashtable-ref rtd-mutables rtd '#())]) (let ([mutables (with-global-lock* (hashtable-ref rtd-mutables rtd '#()))])
(let loop ([j (#%vector-length mutables)]) (let loop ([j (#%vector-length mutables)])
(cond (cond
[(fx= j 0) #f] [(fx= j 0) #f]

View File

@ -0,0 +1,22 @@
;; Use `define-thread-local` for an immutable variable containing a
;; mutable value where the value can be created lazily and isn't #f
(define-syntax-rule (define-thread-local id rhs)
(begin
(define cell (make-thread-parameter #f))
(define (init) rhs)
(define-syntax (id stx)
(...
(syntax-case stx ()
[(id arg ...) #'((thread-local-ref cell init) arg ...)]
[_ #'(thread-local-ref cell init)])))))
(define (thread-local-ref c init)
(let ([v (c)])
(or v
(with-interrupts-disabled
(let ([v (c)])
(or v
(let ([v (init)])
(c v)
v)))))))

View File

@ -2,16 +2,16 @@
;; Implements a variant of will executors with polling and a callback ;; Implements a variant of will executors with polling and a callback
;; for when a will becomes ready ;; for when a will becomes ready
(define the-will-guardian (make-guardian)) (define-thread-local the-will-guardian (make-guardian))
(define the-stubborn-will-guardian (make-guardian #t)) (define-thread-local the-stubborn-will-guardian (make-guardian #t))
;; Guardian callbacks are called fifo, but will executors are called ;; Guardian callbacks are called fifo, but will executors are called
;; lifo. The `will-stacks` tables map a finalized value to a list ;; lifo. The `will-stacks` tables map a finalized value to a list
;; of finalizers, where each finalizer is an ephemeron pairing a will ;; of finalizers, where each finalizer is an ephemeron pairing a will
;; executor with a will function (so that the function is not retained ;; executor with a will function (so that the function is not retained
;; if the will executor is dropped) ;; if the will executor is dropped)
(define the-will-stacks (make-weak-eq-hashtable)) (define-thread-local the-will-stacks (make-weak-eq-hashtable))
(define the-stubborn-will-stacks (make-weak-eq-hashtable)) (define-thread-local the-stubborn-will-stacks (make-weak-eq-hashtable))
(define-record-type (will-executor create-will-executor will-executor?) (define-record-type (will-executor create-will-executor will-executor?)
(fields guardian will-stacks (mutable ready) notify)) (fields guardian will-stacks (mutable ready) notify))