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.