In process locking implemented, not sqlite locking.

This commit is contained in:
2026-08-31 15:37:54 +02:00
parent 3f468504a6
commit 0b45c8b286
4 changed files with 89 additions and 7 deletions
+3
View File
@@ -56,5 +56,8 @@
(define/public (key-values)
(ks-key-values ksh))
(define/public (with-lock proc)
(ks-with-lock ksh proc))
)
)
+25 -2
View File
@@ -27,8 +27,10 @@
ks-abort-transaction
)
(define (any? a) #t)
(define-struct keystore
(file path dbh)
(file path dbh lock-sem (in-lock #:mutable) (entered #:mutable))
#:transparent
)
@@ -46,7 +48,7 @@
(let ((dbh (sqlite3-connect #:database path #:mode 'create)))
(query-exec dbh "CREATE TABLE IF NOT EXISTS keystore(key varchar primary key, value varchar, str_key varchar);")
(query-exec dbh "CREATE INDEX IF NOT EXISTS keystore_idx on keystore(str_key);")
(make-keystore file path dbh))))
(make-keystore file path dbh (make-semaphore 1) #f 0))))
(define/contract (ks-close ksh)
(-> keystore? boolean?)
@@ -54,6 +56,27 @@
(disconnect dbh)
#t))
(define/contract (ks-with-lock ksh proc)
(-> keystore? procedure? any?)
(dynamic-wind
(λ ()
(cond
((eq? (keystore-in-lock ksh) (current-thread))
(set-keystore-entered! ksh (+ keystore-entered ksh) 1))
(else
(semaphore-wait (keystore-lock-sem ksh))
(set-keystore-entered! ksh 1)
))
(set-keystore-in-lock! ksh (current-thread)))
proc
(λ ()
(set-keystore-entered! (- (keystore-entered ksh) 1))
(when (= (keystore-entered ksh) 0)
(set-keystore-in-lock! ksh #f)
(semaphore-post (keystore-lock-sem ksh))))
)
)
(define/contract (ks-set! ksh key value)
(-> keystore? any/c any/c boolean?)
(ks-set!* (keystore-dbh ksh) (key->string key) (format "~a" key) (value->string value)))
+21
View File
@@ -83,6 +83,18 @@ Returns all keys.
Returns all key-value pairs.
}
@defmethod[(with-lock [proc (-> any/c)]) any/c]{
Calls @racket[proc] while holding the lock belonging to this keystore
object and returns the value produced by @racket[proc]. Other threads
using @method[keystore% with-lock] on the same object wait until the lock is
released.
The lock is reentrant for nested calls on the same thread and is
released when control leaves @racket[proc], including when an exception
is raised. Keystore methods do not acquire this lock automatically.
}
}
@section{Examples}
@@ -116,3 +128,12 @@ Returns all key-value pairs.
(send ks glob "*b*")
(send ks glob-kv "*b*")
]
@subsection{Locking}
@racketblock[
(send ks with-lock
(λ ()
(send ks set! 'counter
(add1 (send ks get 'counter 0)))))
]
+37 -2
View File
@@ -19,11 +19,18 @@ stringified lowercase form, which allows glob-style queries.
@defstruct*[keystore
([file any/c]
[path path?]
[dbh any/c])]{
[dbh any/c]
[lock-sem semaphore?]
[in-lock (or/c thread? #f)]
[entered exact-nonnegative-integer?])]{
Represents an open keystore. The @racket[file] field contains the
original argument, @racket[path] is the resolved database path, and
@racket[dbh] is the SQLite connection.
@racket[dbh] is the SQLite connection. The remaining fields implement
the reentrant lock used by @racket[ks-with-lock]: @racket[lock-sem]
serializes access, @racket[in-lock] records the thread that holds the
lock, and @racket[entered] records that thread's nesting depth. The
@racket[in-lock] and @racket[entered] fields are mutable internal state.
The @tt{keystore?} predicate is altered to also check if the
database connection is (still) valid.
@@ -151,6 +158,24 @@ Commits a transaction with "COMMIT".
Aborts a transaction with "ROLLBACK".
}
@section{Locking}
@defproc[(ks-with-lock [ks keystore?] [proc (-> any/c)]) any/c]{
Calls @racket[proc] while holding the lock belonging to @racket[ks] and
returns the value produced by @racket[proc]. Other threads using
@racket[ks-with-lock] with the same keystore handle wait until the lock
is released.
The lock is reentrant: @racket[proc] may call @racket[ks-with-lock]
again on the same handle and thread. The lock is released when control
leaves @racket[proc], including when an exception is raised.
The lock coordinates only code that uses the same keystore handle and
explicitly calls @racket[ks-with-lock]. Individual keystore operations
do not acquire it automatically.
}
@section{Examples}
@subsection{Basic Usage}
@@ -181,3 +206,13 @@ Aborts a transaction with "ROLLBACK".
@racketblock[
(ks-keys-glob ks "*b*")
]
@subsection{Locking Example}
@racketblock[
(ks-with-lock
ks
(λ ()
(ks-set! ks 'counter
(add1 (ks-get ks 'counter 0)))))
]