@@ -30,7 +30,7 @@ module Mutex : sig
30
30
[~checked:false] on an operation may prevent error checking also on a
31
31
subsequent operation.
32
32
33
- See also {!Lock}. *)
33
+ See also {!Lock}, and {!Rwlock} . *)
34
34
35
35
type t
36
36
(* * Represents a mutual-exclusion lock or mutex. *)
@@ -162,9 +162,10 @@ module Lock : sig
162
162
163
163
🏎️ This uses a low overhead, optimistic, and unfair implementation that
164
164
also does not perform runtime ownership error checking. In most cases this
165
- should be the mutual exclusion lock you will want to use.
165
+ should be the mutual exclusion lock you will want to use. Consider using
166
+ {!Rwlock} in case most operations are reads.
166
167
167
- See also {!Mutex}. *)
168
+ See also {!Mutex}, and {!Rwlock} . *)
168
169
169
170
type t
170
171
(* * Represents a poisonable mutual exclusion lock. *)
@@ -247,6 +248,177 @@ module Lock : sig
247
248
in case the [lock] is not currently held exclusively. *)
248
249
end
249
250
251
+ module Rwlock : sig
252
+ (* * A poisonable, freezable, read-write lock.
253
+
254
+ 🏎️ This uses a low overhead, optimistic, and unfair implementation that
255
+ also does not perform runtime ownership error checking. In most cases this
256
+ should be the read-write lock you will want to use and should give roughly
257
+ equal or better performance than {!Lock} in cases where the majority of
258
+ operations are reads.
259
+
260
+ 🐌 This is a "slim" lock. Acquiring the lock in read mode has low overhead,
261
+ but limited scalability. For highly parallel use cases you will either
262
+ want to use sharding or a "fat" scalable read-write lock.
263
+
264
+ ⚠️ The current implementation allows readers to bypass the queue and does
265
+ not prevent writers from starvation. For example, a pair of readers
266
+ running concurrently, acquiring and releasing the lock such that there is
267
+ never a point where the lock is fully released, prevents writers from
268
+ acquiring the lock. This might be changed in the future such that neither
269
+ readers nor writers should starve assuming no single party holds the lock
270
+ indefinitely.
271
+
272
+ See also {!Lock}, and {!Mutex}. *)
273
+
274
+ type t
275
+ (* * Represents a read-write lock. *)
276
+
277
+ (* * {1 Basic API} *)
278
+
279
+ val create : ?padded : bool -> unit -> t
280
+ (* * [create ()] returns a new read-write lock that is initially unlocked. *)
281
+
282
+ exception Poisoned
283
+ (* * Exception raised in case the read-write lock has been
284
+ {{!poison} poisoned}. *)
285
+
286
+ val sharing : t -> (unit -> 'a ) -> 'a
287
+ (* * [sharing rwlock thunk] acquires a shared hold on the [rwlock] and calls
288
+ [thunk ()]. Whether [thunk ()] returns a value or raises an exception, the
289
+ shared hold on the [rwlock] will be released.
290
+
291
+ A single fiber may acquire a shared hold on a specific [rwlock] multiple
292
+ times and other fibers may concurrently acquire shared holds on the
293
+ [rwlock] as well.
294
+
295
+ @raise Poisoned in case the [rwlock] has been {{!poison} poisoned}. *)
296
+
297
+ exception Frozen
298
+ (* * Exception raised in case the read-write lock has been {{!freeze} frozen}.
299
+ *)
300
+
301
+ val holding : t -> (unit -> 'a ) -> 'a
302
+ (* * [holding rwlock thunk] acquires an exclusive hold on the [rwlock] and
303
+ calls [thunk ()]. In case [thunk ()] returns a value, the read-write lock
304
+ is released and the value is returned. Otherwise the read-write lock will
305
+ be {{!poison} poisoned} and the exception reraised.
306
+
307
+ @raise Poisoned in case the [rwlock] has been {{!poison} poisoned}.
308
+ @raise Frozen in case the [rwlock] has been {{!freeze} frozen}. *)
309
+
310
+ val freeze : t -> unit
311
+ (* * [freeze rwlock] marks a [rwlock] as frozen, which means that one can no
312
+ longer acquire an exclusive hold on the [rwlock].
313
+
314
+ ℹ️ No exclusive hold can be obtained on a frozen lock.
315
+
316
+ 🐌 Freezing a [rwlock] does not improve the scalability of acquiring shared
317
+ hold on the [rwlock].
318
+
319
+ @raise Poisoned in case the [rwlock] has been {{!poison} poisoned}. *)
320
+
321
+ val protect : t -> (unit -> 'a ) -> 'a
322
+ (* * [protect rwlock thunk] acquires an exclusive hold on the [rwlock], runs
323
+ [thunk ()], and releases the [rwlock] after [thunk ()] returns or raises.
324
+
325
+ @raise Poisoned in case the lock has been {{!poison} poisoned}.
326
+ @raise Frozen in case the [rwlock] has been {{!freeze} frozen}. *)
327
+
328
+ module Condition : sig
329
+ (* * A condition variable. *)
330
+
331
+ include Intf. Condition with type lock = t
332
+
333
+ val wait_shared : t -> lock -> unit
334
+ (* * [wait_shared condition rwlock] releases the shared hold on [rwlock],
335
+ waits for the [condition], and acquires the shared hold on [rwlock]
336
+ before returning or raising due to the operation being canceled.
337
+
338
+ ℹ️ If the lock is {{!poison} poisoned} during the {!wait_shared}, then
339
+ the {!Poisoned} exception will be raised. *)
340
+ end
341
+
342
+ (* * {1 State query API} *)
343
+
344
+ val is_locked_shared : t -> bool
345
+ (* * [is_locked_shared rwlock] determines whether the [rwlock] is currently
346
+ held shared or not.
347
+
348
+ ⚠️ [is_locked_shared rwlock] will return [false] in case the [rwlock] is
349
+ held exclusively. *)
350
+
351
+ val is_frozen : t -> bool
352
+ (* * [is_frozen rwlock] determines whether the [rwlock] has been
353
+ {{!freeze} frozen}. *)
354
+
355
+ val is_locked : t -> bool
356
+ (* * [is_locked rwlock] determines whether the [rwlock] is currently held
357
+ exclusively or not.
358
+
359
+ ⚠️ [is_locked rwlock] will return [false] in case the [rwlock] is held
360
+ shared. *)
361
+
362
+ val is_poisoned : t -> bool
363
+ (* * [is_poisoned rwlock] determines whether the [rwlock] has been
364
+ {{!poison} poisoned}. *)
365
+
366
+ (* * {1 Expert API}
367
+
368
+ ⚠️ The calls in this section must be matched correctly or the state of the
369
+ read-write lock may become corrupted. *)
370
+
371
+ val acquire_shared : t -> unit
372
+ (* * [acquire_shared rwlock] acquires a shared hold on the [rwlock].
373
+
374
+ A single fiber may acquire a shared hold on a specific [rwlock] multiple
375
+ times and other fibers may concurrently acquire shared holds on the
376
+ [rwlock] as well.
377
+
378
+ @raise Poisoned in case the [rwlock] has been {{!poison} poisoned}. *)
379
+
380
+ val try_acquire_shared : t -> bool
381
+ (* * [try_acquire_shared rwlock] attempts to acquire a shared hold on the
382
+ [rwlock]. Returns [true] in case of success and [false] in case of
383
+ failure.
384
+
385
+ @raise Poisoned in case the [rwlock] has been {{!poison} poisoned}. *)
386
+
387
+ val release_shared : t -> unit
388
+ (* * [release_shared rwlock] releases one shared hold on the [rwlock] or does
389
+ nothing in case the [rwlock] has been {{!poison} poisoned}. *)
390
+
391
+ val acquire : t -> unit
392
+ (* * [acquire rwlock] acquires an exclusive hold on the [rwlock].
393
+
394
+ A fiber may acquire an exclusive hold on a specific [rwlock] once at a
395
+ time.
396
+
397
+ @raise Poisoned in case the [rwlock] has been {{!poison} poisoned}.
398
+ @raise Frozen in case the [rwlock] has been {{!freeze} frozen}. *)
399
+
400
+ val try_acquire : t -> bool
401
+ (* * [try_acquire rwlock] attempts to acquire an exclusive hold on the
402
+ [rwlock]. Returns [true] in case of success and [false] in case of
403
+ failure.
404
+
405
+ @raise Poisoned in case the [rwlock] has been {{!poison} poisoned}.
406
+ @raise Frozen in case the [rwlock] has been {{!freeze} frozen}. *)
407
+
408
+ val release : t -> unit
409
+ (* * [release rwlock] releases the exclusive hold on the [rwlock] or does
410
+ nothing in case the [rwlock] has been {{!freeze} frozen} or
411
+ {{!poison} poisoned}. *)
412
+
413
+ val poison : t -> unit
414
+ (* * [poison rwlock] marks an exclusively held [rwlock] as poisoned.
415
+
416
+ ℹ️ Neither shared nor exclusive hold can be obtained on a poisoned lock.
417
+
418
+ @raise Invalid_argument
419
+ in case the [rwlock] is not currently write locked. *)
420
+ end
421
+
250
422
module Sem : sig
251
423
(* * A poisonable counting semaphore.
252
424
0 commit comments