# Using \`\[@poll error\]\` attribute to implement systhread safe data structures

**URL:** <https://discuss.ocaml.org/t/using-poll-error-attribute-to-implement-systhread-safe-data-structures/12804>\
**Category:** Learning\
**Created:** [August 12, 2023, 12:08pm UTC](https://discuss.ocaml.org/t/using-poll-error-attribute-to-implement-systhread-safe-data-structures/12804 "2023-08-12T12:08:19Z")\
**Posts on this page:** 1\
**Showing post:** 1

<div class="post-metadata">

**Author:** ![polytypic](https://avatars.discourse-cdn.com/v4/letter/p/6f9a4e/32.png) [@polytypic](https://discuss.ocaml.org/u/polytypic)\
**Post date:** [August 12, 2023, 12:08pm UTC](https://discuss.ocaml.org/t/using-poll-error-attribute-to-implement-systhread-safe-data-structures/12804/1 "2023-08-12T12:08:19Z")

</div>

For a long time OCaml has supported lightweight threads exposed via the [Thread](https://v2.ocaml.org/api/Thread.html) module. These threads are often called “systhreads”, but I will simply call them “threads” in this post.

The OCaml Stdlib also provides many mutable data structures such as [Hashtbl](https://v2.ocaml.org/api/Hashtbl.html), [Queue](https://v2.ocaml.org/api/Queue.html), and [Stack](https://v2.ocaml.org/api/Stack.html). As the documentation alerts, none of these are thread-safe.

In this post I will very briefly describe an approach to implementing lock-free thread-safe data structures.

In OCaml 4 and in OCaml 5, within a single [Domain](https://v2.ocaml.org/api/Domain.html), only a single thread may run at a time. In other words, threads do not run in parallel except when they run in different domains in OCaml 5. The OCaml runtime schedules threads and semi pre-emptively switches (within a domain) between threads (created within the domain) during “safe points”. In other words, thread switches cannot happen at arbitrary points — they may only happen at safe points. Memory allocations are safe points. Additional safe points (where no actual memory allocation happens) are inserted into various constructs such as loops.

This means that within a block of code where there are no safe points it is possible to make multiple read and write accesses atomically with respect to threads (within the domain).

How does one ensure that a block of code does not include a safe point?

The OCaml compiler provides an annotation [`[@poll error]`](https://github.com/ocaml/ocaml/pull/10462) that one can use on a function to ensure that the function does not include a safe point.

IOW, using `[@poll error]` one can essentially create functions that are executed atomically with respect to threads (within a domain).

With a particular application in mind, I have created a lock-free thread-safe (integer keyed) hash table, [thread-table](https://github.com/ocaml-multicore/thread-table/tree/main).

As mentioned in the README, the implementation has “zero synchronization overhead on lookups”. Indeed, if you look at the [`find` operation](https://github.com/ocaml-multicore/thread-table/blob/d98848de454ff55fd771e0126e6f923bf3c3df36/src/thread_table.ml#L56-L61) implementation

```ocaml
let find t k' =
  let h = Mix.int k' in
  let buckets = t.buckets in
  let n = Array.length buckets in
  let i = h land (n - 1) in
  find k' (Array.unsafe_get buckets i)

```

it includes no synchronization. In this case not even a `[@poll error]` attribute is needed.

For other operations the thread-table implementation uses functions annotated with the `[@poll error]` attribute (to make atomic updates) and familiar lock-free programming patterns such as retry loops and cooperation to avoid starvation. As an example, see the [`add` operation](https://github.com/ocaml-multicore/thread-table/blob/d98848de454ff55fd771e0126e6f923bf3c3df36/src/thread_table.ml#L120-L139) implementation:

```ocaml
let[@poll error] add_atomically t buckets n i before after =
  t.rehash = 0 && buckets == t.buckets
  && before == Array.unsafe_get buckets i
  && begin
       Array.unsafe_set buckets i after;
       let length = t.length + 1 in
       t.length <- length;
       if n < length && n < max_buckets_div_2 then t.rehash <- n * 2;
       true
     end

let rec add t k' v' =
  let h = Mix.int k' in
  maybe_rehash t;
  let buckets = t.buckets in
  let n = Array.length buckets in
  let i = h land (n - 1) in
  let before = Array.unsafe_get buckets i in
  let after = Cons (k', v', before) in
  if not (add_atomically t buckets n i before after) then add t k' v'

```

Compared to e.g. using a Stdlib [`Mutex`](https://v2.ocaml.org/api/Mutex.html) to protect a data structure against concurrent accesses by threads, this sort of lock-free implementation can give better performance (especially for read-only operations) and also allows use of the operations in contexts, such as signal handlers, where locks are not appropriate.

Note that this technique is not sufficient for parallelism-safe implementation of data structures.

---

_[View the full topic](https://discuss.ocaml.org/t/using-poll-error-attribute-to-implement-systhread-safe-data-structures/12804)._
