summaryrefslogtreecommitdiff
path: root/src/rw-lock.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rw-lock.lisp')
-rw-r--r--src/rw-lock.lisp136
1 files changed, 136 insertions, 0 deletions
diff --git a/src/rw-lock.lisp b/src/rw-lock.lisp
new file mode 100644
index 0000000..b32d3d0
--- /dev/null
+++ b/src/rw-lock.lisp
@@ -0,0 +1,136 @@
1;; SPDX-License-Identifier: EUPL-1.2
2;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com>
3(defpackage :ukkoclot/src/rw-lock
4 (:documentation "Implementation of a shared/read-write lock.")
5 (:use :c2cl :bt2 :iterate)
6 (:import-from :alexandria :with-gensyms)
7 (:import-from :com.dieggsy.f-string :enable-f-strings)
8 (:import-from :sb-sys
9 :allow-with-interrupts :with-local-interrupts :without-interrupts)
10 (:export
11 #:rw-lock
12 #:rw-lock-p
13 #:make-rw-lock
14 #:acquire-read-lock
15 #:release-read-lock
16 #:with-read-lock
17 #:acquire-write-lock
18 #:release-write-lock
19 #:with-write-lock))
20(in-package :ukkoclot/src/rw-lock)
21
22(eval-when (:compile-toplevel :load-toplevel :execute)
23 (enable-f-strings))
24
25;; TODO: Get rid of broadcasting for writers, instead have separate condvars
26;; TODO: Make it a bit more fair, 50% chance of waking up writers or readers
27;; TODO: Use atomic-integer in best-case for read locks to decrease contention
28(defstruct (rw-lock (:constructor make-rw-lock%))
29 (lock nil :type lock :read-only t)
30 (unlocked nil :type condition-variable :read-only t)
31 (readers 0 :type integer)
32 (has-writer nil :type boolean))
33
34(defvar *counter-lock* (make-lock :name "rw-lock::*counter-lock*"))
35
36(defvar *counter* 0)
37
38(defun gen-name ()
39 (format nil "Read-Write Lock ~A"
40 (with-lock-held (*counter-lock*)
41 (incf *counter*))))
42
43(defun make-rw-lock (&key (name (gen-name)))
44 (check-type name string)
45 (make-rw-lock%
46 :lock (make-lock :name #f"{name}'s internal lock")
47 :unlocked (make-condition-variable :name #f"{name}'s internal unlocked")))
48
49(defun acquire-read-lock (rw-lock &key (wait t))
50 ;; TODO: timeout
51 (check-type rw-lock rw-lock)
52 (with-slots (lock unlocked readers has-writer) rw-lock
53 (let ((lock-acquired))
54 (without-interrupts
55 (unwind-protect
56 (when (setf lock-acquired
57 (allow-with-interrupts
58 (acquire-lock lock :wait wait)))
59 (with-local-interrupts
60 (when (if wait
61 (iter
62 (unless has-writer
63 (return t))
64 (condition-wait unlocked lock))
65 (not has-writer))
66 (incf readers)
67 t)))
68 (when lock-acquired
69 (release-lock lock)))))))
70
71(defun release-read-lock (rw-lock)
72 (check-type rw-lock rw-lock)
73 (with-slots (lock unlocked readers has-writer) rw-lock
74 (with-lock-held (lock)
75 (decf readers)
76 (when (zerop readers)
77 (condition-broadcast unlocked))))
78 rw-lock)
79
80(defmacro with-read-lock ((rw-lock) &body body)
81 ;; TODO: timeout
82 (with-gensyms (lock-acquired lock-value)
83 `(let ((,lock-acquired)
84 (,lock-value ,rw-lock))
85 (without-interrupts
86 (unwind-protect
87 (when (setf ,lock-acquired
88 (allow-with-interrupts
89 (acquire-read-lock ,lock-value)))
90 (with-local-interrupts ,@body))
91 (when ,lock-acquired
92 (release-read-lock ,lock-value)))))))
93
94(defun acquire-write-lock (rw-lock &key (wait t))
95 ;; TODO: timeout
96 (check-type rw-lock rw-lock)
97 (with-slots (lock unlocked readers has-writer) rw-lock
98 (let ((lock-acquired))
99 (without-interrupts
100 (unwind-protect
101 (when (setf lock-acquired
102 (allow-with-interrupts
103 (acquire-lock lock :wait wait)))
104 (with-local-interrupts
105 (when (if wait
106 (iter
107 (unless (or (> readers 0) has-writer)
108 (return t))
109 (condition-wait unlocked lock))
110 (and (zerop readers) (not has-writer)))
111 (setf has-writer t)
112 t)))
113 (when lock-acquired
114 (release-lock lock)))))))
115
116(defun release-write-lock (rw-lock)
117 (check-type rw-lock rw-lock)
118 (with-slots (lock unlocked readers has-writer) rw-lock
119 (with-lock-held (lock)
120 (setf has-writer nil)
121 (condition-broadcast unlocked)))
122 rw-lock)
123
124(defmacro with-write-lock ((rw-lock) &body body)
125 ;; TODO: timeout
126 (with-gensyms (lock-acquired lock-value)
127 `(let ((,lock-acquired)
128 (,lock-value ,rw-lock))
129 (without-interrupts
130 (unwind-protect
131 (when (setf ,lock-acquired
132 (allow-with-interrupts
133 (acquire-write-lock ,lock-value)))
134 (with-local-interrupts ,@body))
135 (when ,lock-acquired
136 (release-write-lock ,lock-value)))))))