Module Picos_std_awaitable

Basic futex-like awaitable atomic location for Picos.

Modules

module Awaitable : sig ... end

An awaitable atomic location.

Examples

We first open the library to bring the Awaitable module into scope:

# open Picos_std_awaitable

Mutex

Here is a basic mutex implementation using awaitables:

module Mutex = struct
  type t = int Awaitable.t

  let create ?padded () = Awaitable.make ?padded 0

  let lock t =
    if not (Awaitable.compare_and_set t 0 1) then
      while Awaitable.exchange t 2 <> 0 do
        Awaitable.await t 2
      done

  let unlock t =
    let before = Awaitable.fetch_and_add t (-1) in
    if before = 2 then begin
      Awaitable.set t 0;
      Awaitable.signal t
    end
end

The above mutex outperforms most other mutexes under both no/low and high contention scenarios. In no/low contention scenarios the use of fetch_and_add provides low overhead. In high contention scenarios the above mutex allows unfairness, which avoids performance degradation due to the lock convoy phenomena.

Condition

Let's also implement a condition variable. For that we'll also make use of low level abstractions and operations from the Picos core library:

# open Picos

To implement a condition variable, we'll use the Awaiter API:

module Condition = struct
  type t = unit Awaitable.t

  let create () = Awaitable.make ()

  let wait t mutex =
    let trigger = Trigger.create () in
    let awaiter = Awaitable.Awaiter.add t trigger in
    Mutex.unlock mutex;
    let lock_forbidden mutex =
      let fiber = Fiber.current () in
      let forbid = Fiber.exchange fiber ~forbid:true in
      Mutex.lock mutex;
      Fiber.set fiber ~forbid
    in
    match Trigger.await trigger with
    | None -> lock_forbidden mutex
    | Some exn_bt ->
        Awaitable.Awaiter.remove awaiter;
        lock_forbidden mutex;
        Printexc.raise_with_backtrace (fst exn_bt) (snd exn_bt)

  let signal = Awaitable.signal
  let broadcast = Awaitable.broadcast
end

Notice that the awaitable location used in the above condition variable implementation is never mutated. We just reuse the signaling mechanism of awaitables.