Thread/domain local storage

I’ve been using Thread for a long time, and I’m not going to be able to move all my projects to OCaml 5 when it arrives. At the same time, Domain.DLS seems extremely useful to have some global state that is per-thread.

Is there a library for emulating domain/thread-local-storage on OCaml 4.xx and OCaml 5.xx alike? Or are people interested in working on one? I’d love to hear ideas on how to do it. The simplest that comes to my mind is something like 'a Int_map.t Atomic.t indexed by either Thread.id or Domain.id, but that will not GC values when a thread dies.

1 Like

Memprof-limits uses a TLS implementation with non-blocking lookup (a mutex is used for writing but unnecessary for reading). See: https://gitlab.com/gadmm/memprof-limits/-/blob/master/src/thread_map_core.mli.

The values are cleaned-up before a thread dies by exclusively doing mutations in a scope with a with_value wrapper: src/thread_map.mli · master · gadmm / memprof-limits · GitLab.

I had the additional constraint that it had to be protected against asynchronous exceptions.

It would be pretty spiffy if we managed to change the behind-the-scenes implementation of the Thread module similarly to what the JVM is doing with Project Loom i.e. virtual threads.

A more scalable strategy would be to use TLS from C (with a noalloc extern call for reading). When setting the TLS for the first time, malloc+register the value as a global root. Register a callback to unregister the root and free the value. One could use either explicit TLS (pthread_set/get_specific) or implicit TLS (thread_local), so details vary.

Note that TLS and DLS are very different concepts, and are bound to remain so in OCaml 5. I am interested in an efficient TLS for multicore.

I’ve been wondering actually. Would thread-local storage subsume domain-local storage? On linux, a Thread.t maps to an OS thread, so a map from Thread.id to values should also map domain-specific data to values, correct?

In multicore, a (sys)thread still maps to an OS thread, whereas a domain maps to a collection of OS threads (each of which is a systhread). So neither notion subsumes the other. Using Thread.id as a key is incorrect since it will prevent other threads on the same domain from accessing your domain-specific data.

However Thread.id is unique for the whole application, so a global map from Thread.id to values is enough for a TLS in multicore (modulo synchronisation during writes).

1 Like