Capsule_expertCapsules are a mechanism for safely having uncontended access to mutable data from multiple threads. The interface in this module ensures that only one thread can have uncontended access to that data at a time.
We consider every piece of mutable data in the program to live inside of some capsule. This might be an explicit capsule created by the user using this interface, or an implicit capsule created when a new thread is started. Whenever some thread is executing a function it has uncontended access to a single capsule and any new mutable data it creates is created within that capsule. We say that the function is "running within" the capsule.
Capsules are only ever associated with one thread at a time, which ensures there are no data races in the program. The implicit capsules created when a new thread is started are only ever associated with that thread. Explicit capsules can change which threads have uncontended access to them using synchronization primitives.
Each explicit capsule is associated with a type "brand" -- written 'k throughout this interface -- which allows us to statically reason about access to the capsule within the type system. In the documentation of this interface we will often use 'k to refer to the capsule associated with that brand.
When you create a new capsule 'k via create, the library returns a unique 'k Key.t that grants you exclusive ownership of that capsule. You can temporarily hand out a Password.t (or Password.Shared.t) to functions or threads that need to access the capsule. This ensures that mutable data is only ever accessed in accordance with the capsule's concurrency rules.
Using a 'k Key.t more than once turns it into an aliased key. Aliased global keys indicate that the corresponding capsule has been permanently shared across threads with read-only access.
This module only provides interfaces that statically rule out data races. The Await library augments capsules with various synchronization primitives that prevent races at runtime.
Currently, it is possible to break the soundness guarantees of the capsule API by defining an exception which contains mutable state or nonportable functions, and "smuggling" values out of a capsule by raising that exception out of one of the callbacks in this module. In the medium-term, we plan to distinguish portable exception constructors, whose contents cross portability and contention, from nonportable exception constructors. In the meantime we are consciously leaving this soundness gap for the sake of improved ergonomics over encapsulating exceptions.
module Access : sig ... endAn Access.t allows wrapping and unwrapping Data.t values from the current capsule.
val current : unit -> Access.packed @@ portableObtain an Access.t for the current capsule. Since we do not know the brand for the current capsule, we receive a fresh one.
val initial : initial Access.boxedAn Access.t for the initial capsule
val access_initial :
(initial Access.boxed option @ local ->
'a @ local unique portable contended) @ local once portable ->
'a @ local unique portable contended @@ portableaccess_initial ~f calls f (This initial) if run on the initial thread, or f Null otherwise.
val access_initial_domain :
(initial Access.boxed option @ local ->
'a @ local unique portable contended) @ local once portable ->
'a @ local unique portable contended @@ portableaccess_initial_domain ~f calls f (This initial) if run on the initial domain, or f Null otherwise.
module Password : sig ... endPasswords represent permission to get access to a capsule.
module Key : sig ... endKeys represent the ownership of the capsule.
val create : unit -> Key.packed @ unique @@ portablecreate () creates a new capsule with an associated key.
val access :
'a 'k. password:'k Password.t @ local ->
(f:('k Access.t -> 'a @ unique once portable contended) @ local once portable ->
'a @ unique once portable contended) @ local @@ portableaccess ~password ~f runs f within the capsule 'k, providing it with an Access.t for 'k. The result is within 'k so it must be portable and it is marked contended.
val access_local :
'a 'k. password:'k Password.t @ local ->
(f:
('k Access.t -> 'a @ local unique portable contended) @ local once portable ->
'a @ local unique portable contended) @ local @@ portableAs access, but returns a local value.
shared_access ~password ~f runs f within the capsule 'k, providing it with a shared Access.t for 'k. The result is within 'k so it must be portable and it is marked contended.
As access_shared, but returns a local value.
module Data : sig ... endPointers to data within a capsule.