diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/rw-lock.lisp | 136 |
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))))))) | ||