diff options
| author | 2025-10-19 10:12:04 +0300 | |
|---|---|---|
| committer | 2025-10-19 10:12:04 +0300 | |
| commit | 81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9 (patch) | |
| tree | ea9bcc2f1465f6695f3c6062c0a8edd922b8f117 /src | |
| parent | Work on launching scripts a bit (diff) | |
| download | ukkoclot-81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9.tar.gz ukkoclot-81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9.tar.xz ukkoclot-81e69b85c8bcd9ab8da04c01bc30aaea129b7ff9.zip | |
Make the R/W locks somewhat more fair
Diffstat (limited to 'src')
| -rw-r--r-- | src/rw-lock.lisp | 71 |
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) |