summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Uko Kokņevičs2025-10-19 10:12:04 +0300
committerGravatar Uko Kokņevičs2025-10-19 10:12:04 +0300
commit81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9 (patch)
treeea9bcc2f1465f6695f3c6062c0a8edd922b8f117 /src
parentWork on launching scripts a bit (diff)
downloadukkoclot-81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9.tar.gz
ukkoclot-81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9.tar.xz
ukkoclot-81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9.zip
Make the R/W locks somewhat more fair
Diffstat (limited to '')
-rw-r--r--src/rw-lock.lisp71
1 files changed, 44 insertions, 27 deletions
diff --git a/src/rw-lock.lisp b/src/rw-lock.lisp
index bd2606c..dc8850d 100644
--- a/src/rw-lock.lisp
+++ b/src/rw-lock.lisp
@@ -3,7 +3,7 @@
3(defpackage :ukkoclot/src/rw-lock 3(defpackage :ukkoclot/src/rw-lock
4 (:documentation "Implementation of a shared/read-write lock.") 4 (:documentation "Implementation of a shared/read-write lock.")
5 (:use :c2cl :bt2 :iterate) 5 (:use :c2cl :bt2 :iterate)
6 (:import-from :alexandria :with-gensyms) 6 (:import-from :alexandria :whichever :with-gensyms)
7 (:import-from :com.dieggsy.f-string :enable-f-strings) 7 (:import-from :com.dieggsy.f-string :enable-f-strings)
8 (:import-from :sb-sys 8 (:import-from :sb-sys
9 :allow-with-interrupts :with-local-interrupts :without-interrupts) 9 :allow-with-interrupts :with-local-interrupts :without-interrupts)
@@ -22,20 +22,22 @@
22(eval-when (:compile-toplevel :load-toplevel :execute) 22(eval-when (:compile-toplevel :load-toplevel :execute)
23 (enable-f-strings)) 23 (enable-f-strings))
24 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 25;; TODO: Use atomic-integer in best-case for read locks to decrease contention
28(defstruct (rw-lock (:constructor make-rw-lock%)) 26(defstruct (rw-lock (:constructor make-rw-lock%))
29 (lock nil :type lock :read-only t) 27 (lock nil :type lock :read-only t)
30 (unlocked nil :type condition-variable :read-only t) 28 (reader-cv nil :type condition-variable :read-only t)
31 (readers 0 :type integer) 29 (writer-cv nil :type condition-variable :read-only t)
32 (has-writer nil :type boolean)) 30 (waiting-readers 0 :type (integer 0))
31 (waiting-writers 0 :type (integer 0))
32 (active-readers 0 :type (integer 0))
33 (active-writer nil :type boolean))
33 34
34(defvar *counter-lock* (make-lock :name "rw-lock::*counter-lock*")) 35(defvar *counter-lock* (make-lock :name "rw-lock::*counter-lock*"))
35 36
36(defvar *counter* 0) 37(defvar *counter* 0)
37 38
38(defun gen-name () 39(defun gen-name ()
40 "Generate a name for a rw-lock"
39 (format nil "Read-Write Lock ~A" 41 (format nil "Read-Write Lock ~A"
40 (with-lock-held (*counter-lock*) 42 (with-lock-held (*counter-lock*)
41 (incf *counter*)))) 43 (incf *counter*))))
@@ -44,38 +46,51 @@
44 (check-type name string) 46 (check-type name string)
45 (make-rw-lock% 47 (make-rw-lock%
46 :lock (make-lock :name #f"{name}'s internal lock") 48 :lock (make-lock :name #f"{name}'s internal lock")
47 :unlocked (make-condition-variable :name #f"{name}'s internal unlocked"))) 49 :reader-cv (make-condition-variable :name #f"{name}'s internal reader-cv")
50 :writer-cv (make-condition-variable :name #f"{name}'s internal writer-cv")))
51
52(defun wakeup-waiters (rw-lock)
53 ;; NOTE: RW-LOCK-LOCK HAS TO BE TAKEN!
54 (declare (type rw-lock rw-lock))
55 (with-slots (reader-cv writer-cv waiting-readers waiting-writers) rw-lock
56 (cond
57 ((zerop waiting-readers) (condition-notify writer-cv))
58 ((zerop waiting-writers) (condition-broadcast reader-cv))
59 (t (whichever (condition-notify writer-cv)
60 (condition-broadcast reader-cv))))))
48 61
49(defun acquire-read-lock (rw-lock &key (wait t)) 62(defun acquire-read-lock (rw-lock &key (wait t))
50 ;; TODO: timeout 63 ;; TODO: timeout
51 (check-type rw-lock rw-lock) 64 (check-type rw-lock rw-lock)
52 (with-slots (lock unlocked readers has-writer) rw-lock 65 (with-slots (lock reader-cv active-readers active-writer waiting-readers) rw-lock
53 (let ((lock-acquired)) 66 (let ((lock-acquired))
54 (without-interrupts 67 (without-interrupts
55 (unwind-protect 68 (unwind-protect
56 (when (setf lock-acquired 69 (when (setf lock-acquired
57 (allow-with-interrupts 70 (allow-with-interrupts
58 (acquire-lock lock :wait wait))) 71 (acquire-lock lock :wait wait)))
72 (incf waiting-readers)
59 (with-local-interrupts 73 (with-local-interrupts
60 (when (if wait 74 (when (if wait
61 (iter 75 (iter
62 (unless has-writer 76 (unless active-writer
63 (return t)) 77 (return t))
64 (condition-wait unlocked lock)) 78 (condition-wait reader-cv lock))
65 (not has-writer)) 79 (not active-writer))
66 (incf readers) 80 (incf active-readers)
67 t))) 81 t)))
68 (when lock-acquired 82 (when lock-acquired
83 (decf waiting-readers)
69 (release-lock lock))))))) 84 (release-lock lock)))))))
70 85
71(defun release-read-lock (rw-lock) 86(defun release-read-lock (rw-lock)
72 (check-type rw-lock rw-lock) 87 (check-type rw-lock rw-lock)
73 (with-slots (lock unlocked readers has-writer) rw-lock 88 (with-slots (lock active-readers active-writer) rw-lock
74 (with-lock-held (lock) 89 (with-lock-held (lock)
75 (assert (> readers 0) nil "Trying to release a read lock that's not taken!") 90 (assert (> active-readers 0) nil "Trying to release a read lock that's not taken!")
76 (decf readers) 91 (decf active-readers)
77 (when (zerop readers) 92 (when (zerop active-readers)
78 (condition-broadcast unlocked)))) 93 (wakeup-waiters rw-lock))))
79 rw-lock) 94 rw-lock)
80 95
81(defmacro with-read-lock ((rw-lock) &body body) 96(defmacro with-read-lock ((rw-lock) &body body)
@@ -95,32 +110,34 @@
95(defun acquire-write-lock (rw-lock &key (wait t)) 110(defun acquire-write-lock (rw-lock &key (wait t))
96 ;; TODO: timeout 111 ;; TODO: timeout
97 (check-type rw-lock rw-lock) 112 (check-type rw-lock rw-lock)
98 (with-slots (lock unlocked readers has-writer) rw-lock 113 (with-slots (lock writer-cv active-readers active-writer waiting-writers) rw-lock
99 (let ((lock-acquired)) 114 (let ((lock-acquired))
100 (without-interrupts 115 (without-interrupts
101 (unwind-protect 116 (unwind-protect
102 (when (setf lock-acquired 117 (when (setf lock-acquired
103 (allow-with-interrupts 118 (allow-with-interrupts
104 (acquire-lock lock :wait wait))) 119 (acquire-lock lock :wait wait)))
120 (incf waiting-writers)
105 (with-local-interrupts 121 (with-local-interrupts
106 (when (if wait 122 (when (if wait
107 (iter 123 (iter
108 (unless (or (> readers 0) has-writer) 124 (unless (or (> active-readers 0) active-writer)
109 (return t)) 125 (return t))
110 (condition-wait unlocked lock)) 126 (condition-wait writer-cv lock))
111 (and (zerop readers) (not has-writer))) 127 (and (zerop active-readers) (not active-writer)))
112 (setf has-writer t) 128 (setf active-writer t)
113 t))) 129 t)))
114 (when lock-acquired 130 (when lock-acquired
131 (decf waiting-writers)
115 (release-lock lock))))))) 132 (release-lock lock)))))))
116 133
117(defun release-write-lock (rw-lock) 134(defun release-write-lock (rw-lock)
118 (check-type rw-lock rw-lock) 135 (check-type rw-lock rw-lock)
119 (with-slots (lock unlocked readers has-writer) rw-lock 136 (with-slots (lock active-readers active-writer) rw-lock
120 (with-lock-held (lock) 137 (with-lock-held (lock)
121 (assert has-writer nil "Trying to release a write lock that's not taken!") 138 (assert active-writer nil "Trying to release a write lock that's not taken!")
122 (setf has-writer nil) 139 (setf active-writer nil)
123 (condition-broadcast unlocked))) 140 (wakeup-waiters rw-lock)))
124 rw-lock) 141 rw-lock)
125 142
126(defmacro with-write-lock ((rw-lock) &body body) 143(defmacro with-write-lock ((rw-lock) &body body)