My own corner of monopam
README.md

cache #

In-memory caches with a time-to-live, shared between Eio fibers.

Programs that ask a remote service the same question every few seconds (a DNS resolver, an OAuth client fetching provider metadata) usually want to keep the answer for a minute and ask again later. Cache keeps each value for a time-to-live (TTL) and forgets it afterwards.

The TTL of an entry is the cache's base TTL plus a small random jitter. Without the jitter, a batch of entries written in the same second would expire in the same second too, and the backend would see all the recomputations at once. Nothing runs in the background: get drops an expired entry when it meets one, and gc removes the others. Each operation holds an Eio.Mutex, so one cache can be shared by several fibers.

Installation #

Install with opam:

$ opam install cache

The package may not be in the public opam-repository yet. In that case, add the overlay first:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install cache

Usage #

A cache with string keys #

Cache.String needs no functor application:

let lookup env =
  let cache = Cache.String.v ~clock:(Eio.Stdenv.clock env) () in
  Cache.String.set cache "user:42" "Alice";
  match Cache.String.get cache "user:42" with
| Some data -> ()
| None -> ()

Computing a missing value #

get_or_compute returns the cached value when there is one. Otherwise it calls the function, stores the result and returns it. The lock is released while the function runs, so two fibers that miss the same key at the same time can both call it, and the value stored last is the one that stays.

(* Returns cached value or computes and caches it *)
let data cache =
  Cache.String.get_or_compute cache "user:42" (fun () -> "Alice")

Other key types #

For other keys, apply Cache.Make to a module with equal and hash:

module IntCache = Cache.Make (struct
  type t = int

  let equal = Int.equal
  let hash = Hashtbl.hash
end)

let cache clock = IntCache.v ~clock ~base_ttl:300.0 ~jitter:0.1 ()

API #

Cache.String and every Cache.Make instance have the same signature. v ~clock ?base_ttl ?jitter () creates an empty cache. The base TTL b defaults to 60 seconds and the jitter j to 0.2, a fraction of b: each entry lives for a time drawn uniformly between b(1 - j/2) and b(1 + j/2), so with the defaults an entry lasts between 54 and 66 seconds.

get, set and remove work as in a hash table, and get_or_compute is described above. gc removes the expired entries and clear removes all of them. stats returns a pair: the number of entries, then how many of those have not expired yet.

Licence #

ISC. See LICENSE.md.