diff --git a/pkgs/base/info.rkt b/pkgs/base/info.rkt index 9c401897f1..eb588aa684 100644 --- a/pkgs/base/info.rkt +++ b/pkgs/base/info.rkt @@ -12,7 +12,7 @@ (define collection 'multi) -(define version "7.8.0.1") +(define version "7.8.0.2") (define deps `("racket-lib" ["racket" #:version ,version])) diff --git a/pkgs/racket-doc/scribblings/reference/concurrency.scrbl b/pkgs/racket-doc/scribblings/reference/concurrency.scrbl index a7e016aa8a..7912f656f6 100644 --- a/pkgs/racket-doc/scribblings/reference/concurrency.scrbl +++ b/pkgs/racket-doc/scribblings/reference/concurrency.scrbl @@ -19,3 +19,4 @@ support for parallelism to improve performance. @include-section["futures.scrbl"] @include-section["places.scrbl"] @include-section["engine.scrbl"] +@include-section["memory-order.scrbl"] diff --git a/pkgs/racket-doc/scribblings/reference/data.scrbl b/pkgs/racket-doc/scribblings/reference/data.scrbl index 74b45cbd1d..c160b085c0 100644 --- a/pkgs/racket-doc/scribblings/reference/data.scrbl +++ b/pkgs/racket-doc/scribblings/reference/data.scrbl @@ -129,7 +129,7 @@ boxes that are not @tech{impersonators}. @racket[box-cas!] is guaranteed to use a hardware @emph{compare and set} operation. Uses of @racket[box-cas!] be performed safely in a @tech{future} (i.e., allowing the future thunk to continue in - parallel).} + parallel). See also @secref["memory-order"].} @; ---------------------------------------------------------------------- @include-section["hashes.scrbl"] diff --git a/pkgs/racket-doc/scribblings/reference/futures.scrbl b/pkgs/racket-doc/scribblings/reference/futures.scrbl index 4275a4f3c0..deee7e34af 100644 --- a/pkgs/racket-doc/scribblings/reference/futures.scrbl +++ b/pkgs/racket-doc/scribblings/reference/futures.scrbl @@ -40,7 +40,8 @@ futures and threads. Furthermore, guarantees about the visibility of effects and ordering are determined by the operating system and hardware---which rarely support, for example, the guarantee of sequential consistency that is provided for @racket[thread]-based -concurrency. At the same time, operations that seem obviously safe may +concurrency; see also @secref["memory-order"]. At the same time, operations +that seem obviously safe may have a complex enough implementation internally that they cannot run in parallel. See also @guidesecref["effective-futures"] in @|Guide|. @@ -153,6 +154,13 @@ the futures may be @racket[touch]ed in any order. are not safe to perform in parallel, and they therefore prevent a computation from continuing in parallel. + Beware of trying to use an fsemaphore to implement a lock. A future + may run concurrently and in parallel to other futures, but a future + that is not demanded by a Racket thread can be suspended at any + time---such as just after it takes a lock and before it releases the + lock. If you must share mutable data among futures, lock-free data + structures are generally a better fit. + } @defproc[(fsemaphore? [v any/c]) boolean?]{ diff --git a/pkgs/racket-doc/scribblings/reference/memory-order.scrbl b/pkgs/racket-doc/scribblings/reference/memory-order.scrbl new file mode 100644 index 0000000000..42c0c8fdf1 --- /dev/null +++ b/pkgs/racket-doc/scribblings/reference/memory-order.scrbl @@ -0,0 +1,44 @@ +#lang scribble/doc +@(require "mz.rkt" + (for-label racket/unsafe/ops)) + +@title[#:tag "memory-order"]{Machine Memory Order} + +Unlike Racket @tech{threads}, futures and places can expose the +underlying machine's memory model, including a weak memory ordering. +For example, when a future writes to multiple slots in a mutable +vector, it's possible on some platforms for another future to observe +the writes in a different order or not at all, unless the futures are +explicitly synchronized. Similarly, shared byte strings or +@tech{fxvectors} can expose the machine's memory model across places. + +Racket ensures that a machine's memory model is not observed in a way +that unsafely exposes the implementation of primitive datatypes. For +example, it is not possible for one future to see a partially +constructed primitive value as a result of reading a vector that is +mutated by another future. + +The @racket[box-cas!], @racket[vector-cas!], +@racket[unsafe-box*-cas!], @racket[unsafe-vector*-cas!], and +@racket[unsafe-struct*-cas!] operations all provide a machine-level +compare-and-set, so they can be used in ways that are specifically +supported by the a machine's memory model. The +@racket[(memory-order-acquire)] and @racket[(memory-order-release)] +operations similarly constrain machine-level stores and loads. +Synchronization operations such as place messages, future +@racket[touch]es, and @tech{future semaphores} imply suitable +machine-level acquire and release ordering. + +@deftogether[( +@defproc[(memory-order-acquire) void?] +@defproc[(memory-order-release) void?] +)]{ + +Those operations implement a machine-level memory fence on platforms +where one is needed for synchronization. The +@racket[memory-order-acquire] operation ensures at least a load--load +and load--store fence at the machine level, and the +@racket[memory-order-release] operation ensures at least a +store--store and store--load fence at the machine level. + +@history[#:added "7.7.0.11"]} diff --git a/pkgs/racket-test/tests/future/fsema-lock.rkt b/pkgs/racket-test/tests/future/fsema-lock.rkt index f76f332e4f..de11e0246d 100644 --- a/pkgs/racket-test/tests/future/fsema-lock.rkt +++ b/pkgs/racket-test/tests/future/fsema-lock.rkt @@ -6,6 +6,12 @@ ;; demanded by a thread (so it must continue to ;; run if it takes a lock) +(define (box-cas!* b old new) + (or (box-cas! b old new) + ;; Try again if failure looks spurious: + (and (eq? (unbox b) old) + (box-cas!* b old new)))) + (for ([N (in-range 4 20)]) (define f (make-fsemaphore 1)) (define working (box 'ok)) @@ -16,10 +22,10 @@ (lambda () (for ([i (in-range 5000)]) (fsemaphore-wait f) - (unless (box-cas! working 'ok 'not-ok) + (unless (box-cas!* working 'ok 'not-ok) (printf "FAIL\n") (exit 1)) - (unless (box-cas! working 'not-ok 'ok) + (unless (box-cas!* working 'not-ok 'ok) (printf "FAIL\n") (exit 1)) (fsemaphore-post f))))) diff --git a/racket/src/cs/compile-file.ss b/racket/src/cs/compile-file.ss index cd6f631f27..9da25abc5f 100644 --- a/racket/src/cs/compile-file.ss +++ b/racket/src/cs/compile-file.ss @@ -2,7 +2,7 @@ ;; Check to make we're using a build of Chez Scheme ;; that has all the features we need. (define-values (need-maj need-min need-sub need-dev) - (values 9 5 3 31)) + (values 9 5 3 32)) (unless (guard (x [else #f]) (eval 'scheme-fork-version-number)) (error 'compile-file diff --git a/racket/src/racket/src/schminc.h b/racket/src/racket/src/schminc.h index e6af7d0e36..795c01412a 100644 --- a/racket/src/racket/src/schminc.h +++ b/racket/src/racket/src/schminc.h @@ -14,7 +14,7 @@ #define USE_COMPILED_STARTUP 1 -#define EXPECTED_PRIM_COUNT 1469 +#define EXPECTED_PRIM_COUNT 1471 #ifdef MZSCHEME_SOMETHING_OMITTED # undef USE_COMPILED_STARTUP diff --git a/racket/src/racket/src/schvers.h b/racket/src/racket/src/schvers.h index f9d61bb1bc..28ab2a0ca2 100644 --- a/racket/src/racket/src/schvers.h +++ b/racket/src/racket/src/schvers.h @@ -16,7 +16,7 @@ #define MZSCHEME_VERSION_X 7 #define MZSCHEME_VERSION_Y 8 #define MZSCHEME_VERSION_Z 0 -#define MZSCHEME_VERSION_W 1 +#define MZSCHEME_VERSION_W 2 /* A level of indirection makes `#` work as needed: */ #define AS_a_STR_HELPER(x) #x diff --git a/racket/src/racket/src/thread.c b/racket/src/racket/src/thread.c index 55b7a318e8..801c4029a7 100644 --- a/racket/src/racket/src/thread.c +++ b/racket/src/racket/src/thread.c @@ -391,6 +391,8 @@ static Scheme_Object *will_executor_sema(Scheme_Object *w, int *repost); static Scheme_Object *check_break_now(int argc, Scheme_Object *args[]); +static Scheme_Object *memory_order(int argc, Scheme_Object *args[]); + static Scheme_Object *unsafe_start_atomic(int argc, Scheme_Object **argv); static Scheme_Object *unsafe_end_atomic(int argc, Scheme_Object **argv); static Scheme_Object *unsafe_start_breakable_atomic(int argc, Scheme_Object **argv); @@ -608,7 +610,9 @@ void scheme_init_thread(Scheme_Startup_Env *env) ADD_PRIM_W_ARITY("custodian-require-memory" , custodian_require_mem, 3, 3, env); ADD_PRIM_W_ARITY("custodian-limit-memory" , custodian_limit_mem , 2, 3, env); ADD_PRIM_W_ARITY("custodian-memory-accounting-available?", custodian_can_mem , 0, 0, env); - + + ADD_FOLDING_PRIM("memory-order-acquire", memory_order, 0, 0, 1, env); + ADD_FOLDING_PRIM("memory-order-release", memory_order, 0, 0, 1, env); ADD_FOLDING_PRIM("evt?" , evt_p , 1, 1 , 1, env); ADD_PRIM_W_ARITY2("sync" , sch_sync , 0, -1, 0, -1, env); @@ -8848,6 +8852,19 @@ static Scheme_Object *will_executor_sema(Scheme_Object *w, int *repost) /* GC preparation and timing */ /*========================================================================*/ +/* We don't currently support threads on a platform with a weaked + memory model than x86, and no memory-order operations are needed on + x86. */ + +static Scheme_Object *memory_order(int argc, Scheme_Object *args[]) +{ + return scheme_void; +} + +/*========================================================================*/ +/* GC preparation and timing */ +/*========================================================================*/ + typedef struct Scheme_GC_Pre_Post_Callback_Desc { /* All pointer fields => allocate with GC_malloc() */ Scheme_Object *boxed_key; diff --git a/racket/src/thread/future-lock.rkt b/racket/src/thread/future-lock.rkt index 6e1a22feaa..018bea0b48 100644 --- a/racket/src/thread/future-lock.rkt +++ b/racket/src/thread/future-lock.rkt @@ -53,12 +53,14 @@ (define (lock-acquire lock) (start-future-uninterrupted) (let loop () - (unless (box-cas! lock 0 1) - (loop)))) + (if (box-cas! lock 0 1) + (memory-order-acquire) + (loop)))) (define (lock-release lock) (cond [(box-cas! lock 1 0) + (memory-order-release) (end-future-uninterrupted)] [(eq? (unbox lock) 0) ;; not just a spurious failure...