File: mutex.md

package info (click to toggle)
ocaml-eio 1.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,548 kB
  • sloc: ml: 14,608; ansic: 1,237; makefile: 25
file content (256 lines) | stat: -rw-r--r-- 5,073 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# Setting up the environment

```ocaml
# #require "eio_main";;
```

```ocaml
open Eio.Std

module M = Eio.Mutex

let run fn =
  Eio_main.run @@ fun _ ->
  fn ()

let lock t =
  traceln "Locking";
  M.lock t;
  traceln "Locked"

let unlock t =
  traceln "Unlocking";
  M.unlock t;
  traceln "Unlocked"
```

# Test cases

Simple case

```ocaml
# run @@ fun () ->
  let t = M.create () in
  lock t;
  unlock t;
  lock t;
  unlock t;;
+Locking
+Locked
+Unlocking
+Unlocked
+Locking
+Locked
+Unlocking
+Unlocked
- : unit = ()
```

Concurrent access to the mutex


```ocaml
# run @@ fun () ->
  let t = M.create () in
  let fn () =
    lock t;
    Eio.Fiber.yield ();
    unlock t
  in
  List.init 4 (fun _ -> fn)
  |> Fiber.all;;
+Locking
+Locked
+Locking
+Locking
+Locking
+Unlocking
+Unlocked
+Locked
+Unlocking
+Unlocked
+Locked
+Unlocking
+Unlocked
+Locked
+Unlocking
+Unlocked
- : unit = ()
```

Double unlock raises an exception

```ocaml
# run @@ fun () ->
  let t = M.create () in
  M.lock t;
  M.unlock t;
  begin
    try M.unlock t
    with Sys_error msg -> traceln "Caught: %s" msg
  end;
  traceln "Trying to use lock after error...";
  M.lock t;;
+Caught: Eio.Mutex.unlock: already unlocked!
+Trying to use lock after error...
Exception:
Eio__Eio_mutex.Poisoned (Sys_error "Eio.Mutex.unlock: already unlocked!").
```

## Read-write access

Successful use; only one critical section is active at once:

```ocaml
# run @@ fun () ->
  let t = M.create () in
  let fn () =
    traceln "Entered critical section";
    Fiber.yield ();
    traceln "Leaving critical section"
  in
  Fiber.both
    (fun () -> M.use_rw ~protect:true t fn)
    (fun () -> M.use_rw ~protect:true t fn);;
+Entered critical section
+Leaving critical section
+Entered critical section
+Leaving critical section
- : unit = ()
```

A failed critical section will poison the mutex:

```ocaml
# run @@ fun () ->
  let t = M.create () in
  try
    M.use_rw ~protect:true t (fun () -> failwith "Simulated error");
  with Failure _ ->
    traceln "Trying to use the failed lock again fails:";
    M.lock t;;
+Trying to use the failed lock again fails:
Exception: Eio__Eio_mutex.Poisoned (Failure "Simulated error").
```

## Protection

We can prevent cancellation during a critical section:

```ocaml
# run @@ fun () ->
  let t = M.create () in
  Fiber.both
    (fun () ->
       M.use_rw ~protect:true t (fun () -> Fiber.yield (); traceln "Restored invariant");
       Fiber.check ();
       traceln "Error: not cancelled!";
    )
    (fun () -> traceln "Cancelling..."; failwith "Simulated error");;
+Cancelling...
+Restored invariant
Exception: Failure "Simulated error".
```

Or allow interruption and disable the mutex:

```ocaml
# run @@ fun () ->
  let t = M.create () in
  try
    Fiber.both
      (fun () ->
         M.use_rw ~protect:false t (fun () -> Fiber.yield (); traceln "Restored invariant")
      )
      (fun () -> traceln "Cancelling..."; failwith "Simulated error");
   with ex ->
     traceln "Trying to reuse the failed mutex...";
     M.use_ro t (fun () -> assert false);;
+Cancelling...
+Trying to reuse the failed mutex...
Exception:
Eio__Eio_mutex.Poisoned
 (Eio__core__Exn.Cancelled (Failure "Simulated error")).
```

Protection doesn't prevent cancellation while we're still waiting for the lock, though:

```ocaml
# run @@ fun () ->
  let t = M.create () in
  M.lock t;
  try
    Fiber.both
      (fun () -> M.use_rw ~protect:true t (fun () -> assert false))
      (fun () -> traceln "Cancelling..."; failwith "Simulated error")
  with Failure _ ->
    M.unlock t;
    M.use_ro t (fun () -> traceln "Can reuse the mutex");;
+Cancelling...
+Can reuse the mutex
- : unit = ()
```

Poisoning wakes any wakers:

```ocaml
# run @@ fun () ->
  let t = M.create () in
  Fiber.both
    (fun () ->
       try
         M.use_rw ~protect:false t (fun () ->
            Fiber.yield ();
            traceln "Poisoning mutex";
            failwith "Simulated error"
         )
       with Failure _ -> ()
    )
    (fun () -> traceln "Waiting for lock..."; M.use_ro t (fun () -> assert false));;
+Waiting for lock...
+Poisoning mutex
Exception: Eio__Eio_mutex.Poisoned (Failure "Simulated error").
```


## Read-only access

If the resource isn't being mutated, we can just unlock on error:

```ocaml
# run @@ fun () ->
  let t = M.create () in
  try
    M.use_ro t (fun () -> failwith "Simulated error");
  with Failure msg ->
    traceln "Caught: %s" msg;
    traceln "Trying to use the lock again is OK:";
    M.lock t;;
+Caught: Simulated error
+Trying to use the lock again is OK:
- : unit = ()
```

## Try_lock

```ocaml
# run @@ fun () ->
  let t = M.create () in
  let fn () =
    match M.try_lock t with
    | true ->
      traceln "Entered critical section";
      Fiber.yield ();
      traceln "Leaving critical section";
      M.unlock t
    | false ->
      traceln "Failed to get lock"
  in
  Fiber.both fn fn;
  M.use_ro t (fun () -> traceln "Lock still works");;
+Entered critical section
+Failed to get lock
+Leaving critical section
+Lock still works
- : unit = ()
```