Skip to content

Commit 6545be4

Browse files
committed
Check mli files with MDX
1 parent 8dd3d01 commit 6545be4

File tree

10 files changed

+157
-130
lines changed

10 files changed

+157
-130
lines changed

dune-project

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
(multicore-magic (>= 1.0.0))
1717
(domain_shims (and (>= 0.1.0) :with-test))
1818
(alcotest (and (>= 1.7.0) :with-test))
19-
(mdx (and (>= 1.10.0) :with-test))
19+
(mdx (and (>= 2.3.0) :with-test))
2020
(odoc (and (>= 2.2.0) :with-doc))))
2121
(package (name kcas_data)
2222
(synopsis "Compositional lock-free data structures and primitives for communication and synchronization")
@@ -27,7 +27,7 @@
2727
(domain_shims (and (>= 0.1.0) :with-test))
2828
(mtime (and (>= 2.0.0) :with-test))
2929
(alcotest (and (>= 1.7.0) :with-test))
30-
(mdx (and (>= 1.10.0) :with-test))
30+
(mdx (and (>= 2.3.0) :with-test))
3131
(yojson (and (>= 2.1.0) :with-test))
3232
(odoc (and (>= 2.2.0) :with-doc))))
3333
(using mdx 0.2)

kcas.opam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ depends: [
2020
"multicore-magic" {>= "1.0.0"}
2121
"domain_shims" {>= "0.1.0" & with-test}
2222
"alcotest" {>= "1.7.0" & with-test}
23-
"mdx" {>= "1.10.0" & with-test}
23+
"mdx" {>= "2.3.0" & with-test}
2424
"odoc" {>= "2.2.0" & with-doc}
2525
]
2626
build: [

kcas_data.opam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ depends: [
1919
"domain_shims" {>= "0.1.0" & with-test}
2020
"mtime" {>= "2.0.0" & with-test}
2121
"alcotest" {>= "1.7.0" & with-test}
22-
"mdx" {>= "1.10.0" & with-test}
22+
"mdx" {>= "2.3.0" & with-test}
2323
"yojson" {>= "2.1.0" & with-test}
2424
"odoc" {>= "2.2.0" & with-doc}
2525
]

src/kcas/dune

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@
1111
(action
1212
(progn
1313
(copy domain.ocaml4.ml domain.ml))))
14+
15+
(mdx
16+
(package kcas)
17+
(deps
18+
(package kcas))
19+
(preludes kcas.prelude.ml)
20+
(libraries kcas domain_shims)
21+
(files kcas.mli))

src/kcas/kcas.mli

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ end
369369
operations like {!Xt.get} and {!Xt.set} are then recorded in that log. To
370370
actually remove a node, we need to commit the transaction
371371
372-
{[
372+
{@ocaml skip[
373373
Xt.commit { tx = remove node }
374374
]}
375375
@@ -440,7 +440,7 @@ module Xt : sig
440440
val compare_and_swap : xt:'x t -> 'a Loc.t -> 'a -> 'a -> 'a
441441
(** [compare_and_swap ~xt r before after] is equivalent to
442442
443-
{[
443+
{@ocaml skip[
444444
update ~xt r @@ fun actual ->
445445
if actual == before then after else actual
446446
]} *)

src/kcas/kcas.prelude.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
open Kcas

src/kcas_data/dune

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,12 @@
1111
(action
1212
(progn
1313
(copy domain.ocaml4.ml domain.ml))))
14+
15+
(mdx
16+
(package kcas_data)
17+
(deps
18+
(package kcas)
19+
(package kcas_data))
20+
(preludes kcas_data.prelude.ml)
21+
(libraries kcas kcas_data)
22+
(files kcas_data.mli))

src/kcas_data/kcas_data.ml

Lines changed: 0 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,7 @@
1-
(** This is a library of compositional lock-free data structures and primitives
2-
for communication and synchronization implemented using {!Kcas}.
3-
4-
All data structure implementations in this library are concurrency and
5-
parallelism safe and should strive to provide the following guarantees:
6-
7-
- Provided operations are {i strictly serializable} (i.e. both
8-
{{:https://en.wikipedia.org/wiki/Linearizability}linerizable} and
9-
{{:https://en.wikipedia.org/wiki/Serializability}serializable}).
10-
- Provided operations are efficient, either
11-
({{:https://en.wikipedia.org/wiki/Amortized_analysis}amortized}) constant
12-
time, [O(1)], or logarithmic time, [O(log(n))].
13-
- Provided operations are
14-
{{:https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom}lock-free}
15-
and designed to avoid
16-
{{:https://en.wikipedia.org/wiki/Starvation_(computer_science)}starvation}
17-
under moderate contention.
18-
- Provided read-only operations scale perfectly when only read-only
19-
operations are performed in parallel.
20-
21-
Unobvious exceptions to the above guarantees should be clearly and
22-
explicitly documented.
23-
24-
The main feature of these data structure implementations is their
25-
compositionality. If your application does not need compositionality, then
26-
other concurrency and parallelism safe data structure libraries may
27-
potentially offer better performance.
28-
29-
But why should you care about composability?
30-
31-
As an example, consider the implementation of a least-recently-used (LRU)
32-
cache or a bounded associative map. A simple sequential approach to
33-
implement a LRU cache is to use a hash table and a doubly-linked list and
34-
keep track of the amount of space in the cache:
35-
36-
{[
37-
type ('k, 'v) cache =
38-
{ space: int Loc.t;
39-
table: ('k, 'k Dllist.node * 'v) Hashtbl.t;
40-
order: 'k Dllist.t }
41-
]}
42-
43-
On a cache lookup the doubly-linked list node corresponding to the accessed
44-
key is moved to the left end of the list:
45-
46-
{[
47-
let get_opt {table; order; _} key =
48-
Hashtbl.find_opt table key
49-
|> Option.map @@ fun (node, datum) ->
50-
Dllist.move_l node order; datum
51-
]}
52-
53-
On a cache update, in case of overflow, the association corresponding to the
54-
node on the right end of the list is dropped:
55-
56-
{[
57-
let set {table; order; space; _} key datum =
58-
let node =
59-
match Hashtbl.find_opt table key with
60-
| None ->
61-
if 0 = Loc.update space (fun n -> max 0 (n-1))
62-
then Dllist.take_opt_r order
63-
|> Option.iter (Hashtbl.remove table);
64-
Dllist.add_l key order
65-
| Some (node, _) -> Dllist.move_l node order; node
66-
in
67-
Hashtbl.replace table key (node, datum)
68-
]}
69-
70-
Sequential algorithms such as the above are so common that one does not even
71-
think about them. Unfortunately, in a concurrent setting the above doesn't
72-
work even if the individual operations on lists and hash tables were atomic
73-
as they are in this library.
74-
75-
But how would one make the operations on a cache atomic as a whole? As
76-
explained by Maurice Herlihy in one of his talks on
77-
{{:https://youtu.be/ZkUrl8BZHjk?t=1503} Transactional Memory} adding locks
78-
to protect the atomicity of the operation is far from trivial.
79-
80-
Fortunately, rather than having to e.g. wrap the cache implementation behind
81-
a {{:https://en.wikipedia.org/wiki/Lock_(computer_science)} mutex} and make
82-
another individually atomic yet uncomposable data structure, or having to
83-
learn a completely different programming model and rewrite the cache
84-
implementation, we can use the transactional programming model provided by
85-
the {!Kcas} library and the transactional data structures provided by this
86-
library to trivially convert the previous implementation to a lock-free
87-
composable transactional data structure.
88-
89-
To make it so, we simply use transactional versions, [*.Xt.*], of operations
90-
on the data structures and explicitly pass a transaction log, [~xt], to the
91-
operations. For the [get_opt] operation we end up with
92-
93-
{[
94-
let get_opt ~xt {table; order; _} key =
95-
Hashtbl.Xt.find_opt ~xt table key
96-
|> Option.map @@ fun (node, datum) ->
97-
Dllist.Xt.move_l ~xt node order; datum
98-
]}
99-
100-
and the [set] operation is just as easy to convert to a transactional
101-
version. One way to think about transactions is that they give us back the
102-
ability to compose programs such as the above. *)
103-
104-
(** {1 [Stdlib] style data structures}
105-
106-
The data structures in this section are designed to closely mimic the
107-
corresponding unsynchronized data structures in the OCaml [Stdlib]. Each of
108-
these provide a non-compositional, but concurrency and parallelism safe,
109-
interface that is close to the [Stdlib] equivalent. Additionally,
110-
compositional transactional interfaces are provided for some operations.
111-
112-
These implementations will use more space than the corresponding [Stdlib]
113-
data structures. Performance, when accessed concurrently, should be
114-
competitive or superior compared to naïve locking. *)
115-
1161
module Hashtbl = Hashtbl
1172
module Queue = Queue
1183
module Stack = Stack
119-
120-
(** {1 Communication and synchronization primitives} *)
121-
1224
module Mvar = Mvar
1235
module Promise = Promise
124-
125-
(** {1 Linked data structures} *)
126-
1276
module Dllist = Dllist
128-
129-
(** {1 Utilities} *)
130-
1317
module Accumulator = Accumulator

src/kcas_data/kcas_data.mli

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
(** This is a library of compositional lock-free data structures and primitives
2+
for communication and synchronization implemented using {!Kcas}.
3+
4+
All data structure implementations in this library are concurrency and
5+
parallelism safe and should strive to provide the following guarantees:
6+
7+
- Provided operations are {i strictly serializable} (i.e. both
8+
{{:https://en.wikipedia.org/wiki/Linearizability}linerizable} and
9+
{{:https://en.wikipedia.org/wiki/Serializability}serializable}).
10+
- Provided operations are efficient, either
11+
({{:https://en.wikipedia.org/wiki/Amortized_analysis}amortized}) constant
12+
time, [O(1)], or logarithmic time, [O(log(n))].
13+
- Provided operations are
14+
{{:https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom}lock-free}
15+
and designed to avoid
16+
{{:https://en.wikipedia.org/wiki/Starvation_(computer_science)}starvation}
17+
under moderate contention.
18+
- Provided read-only operations scale perfectly when only read-only
19+
operations are performed in parallel.
20+
21+
Unobvious exceptions to the above guarantees should be clearly and
22+
explicitly documented.
23+
24+
The main feature of these data structure implementations is their
25+
compositionality. If your application does not need compositionality, then
26+
other concurrency and parallelism safe data structure libraries may
27+
potentially offer better performance.
28+
29+
But why should you care about composability?
30+
31+
As an example, consider the implementation of a least-recently-used (LRU)
32+
cache or a bounded associative map. A simple sequential approach to
33+
implement a LRU cache is to use a hash table and a doubly-linked list and
34+
keep track of the amount of space in the cache:
35+
36+
{[
37+
type ('k, 'v) cache =
38+
{ space: int Loc.t;
39+
table: ('k, 'k Dllist.node * 'v) Hashtbl.t;
40+
order: 'k Dllist.t }
41+
]}
42+
43+
On a cache lookup the doubly-linked list node corresponding to the accessed
44+
key is moved to the left end of the list:
45+
46+
{[
47+
let get_opt {table; order; _} key =
48+
Hashtbl.find_opt table key
49+
|> Option.map @@ fun (node, datum) ->
50+
Dllist.move_l node order; datum
51+
]}
52+
53+
On a cache update, in case of overflow, the association corresponding to the
54+
node on the right end of the list is dropped:
55+
56+
{[
57+
let set {table; order; space; _} key datum =
58+
let node =
59+
match Hashtbl.find_opt table key with
60+
| None ->
61+
if 0 = Loc.update space (fun n -> max 0 (n-1))
62+
then Dllist.take_opt_r order
63+
|> Option.iter (Hashtbl.remove table);
64+
Dllist.add_l key order
65+
| Some (node, _) -> Dllist.move_l node order; node
66+
in
67+
Hashtbl.replace table key (node, datum)
68+
]}
69+
70+
Sequential algorithms such as the above are so common that one does not even
71+
think about them. Unfortunately, in a concurrent setting the above doesn't
72+
work even if the individual operations on lists and hash tables were atomic
73+
as they are in this library.
74+
75+
But how would one make the operations on a cache atomic as a whole? As
76+
explained by Maurice Herlihy in one of his talks on
77+
{{:https://youtu.be/ZkUrl8BZHjk?t=1503} Transactional Memory} adding locks
78+
to protect the atomicity of the operation is far from trivial.
79+
80+
Fortunately, rather than having to e.g. wrap the cache implementation behind
81+
a {{:https://en.wikipedia.org/wiki/Lock_(computer_science)} mutex} and make
82+
another individually atomic yet uncomposable data structure, or having to
83+
learn a completely different programming model and rewrite the cache
84+
implementation, we can use the transactional programming model provided by
85+
the {!Kcas} library and the transactional data structures provided by this
86+
library to trivially convert the previous implementation to a lock-free
87+
composable transactional data structure.
88+
89+
To make it so, we simply use transactional versions, [*.Xt.*], of operations
90+
on the data structures and explicitly pass a transaction log, [~xt], to the
91+
operations. For the [get_opt] operation we end up with
92+
93+
{[
94+
let get_opt ~xt {table; order; _} key =
95+
Hashtbl.Xt.find_opt ~xt table key
96+
|> Option.map @@ fun (node, datum) ->
97+
Dllist.Xt.move_l ~xt node order; datum
98+
]}
99+
100+
and the [set] operation is just as easy to convert to a transactional
101+
version. One way to think about transactions is that they give us back the
102+
ability to compose programs such as the above. *)
103+
104+
(** {1 [Stdlib] style data structures}
105+
106+
The data structures in this section are designed to closely mimic the
107+
corresponding unsynchronized data structures in the OCaml [Stdlib]. Each of
108+
these provide a non-compositional, but concurrency and parallelism safe,
109+
interface that is close to the [Stdlib] equivalent. Additionally,
110+
compositional transactional interfaces are provided for some operations.
111+
112+
These implementations will use more space than the corresponding [Stdlib]
113+
data structures. Performance, when accessed concurrently, should be
114+
competitive or superior compared to naïve locking. *)
115+
116+
module Hashtbl = Hashtbl
117+
module Queue = Queue
118+
module Stack = Stack
119+
120+
(** {1 Communication and synchronization primitives} *)
121+
122+
module Mvar = Mvar
123+
module Promise = Promise
124+
125+
(** {1 Linked data structures} *)
126+
127+
module Dllist = Dllist
128+
129+
(** {1 Utilities} *)
130+
131+
module Accumulator = Accumulator

src/kcas_data/kcas_data.prelude.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
open Kcas
2+
open Kcas_data

0 commit comments

Comments
 (0)