SQLite engine: named locks — try_lock, SqliteLockHandle, duration guards (task sqlite-engine-locks)
This commit is contained in:
1 parent
513df0b311
commit
1edcb0e27d
5 files changed
+640
-13
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: sqlite-engine-locks
|
||||
name: SQLite engine — named locks (`try_lock`, `Lock` handle, duration guards)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [sqlite-engine-seam-tx]
|
||||
scope: narrow
|
||||
risk: low
|
||||
@@ -35,17 +35,17 @@ lock ops (`lock_acquire`, `lock_release`, `lock_renew`):
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `try_lock` grants exclusively (second acquirer gets `None`);
|
||||
- [x] `try_lock` grants exclusively (second acquirer gets `None`);
|
||||
release makes it acquirable again
|
||||
- [ ] Duration guards: `try_lock`/`renew` with `ttl <= 0` return
|
||||
- [x] Duration guards: `try_lock`/`renew` with `ttl <= 0` return
|
||||
opaque `Err` (`Database`) — never a granted lock, never a
|
||||
panic; substrate guard message preserved in the source chain on
|
||||
`renew` — pinned per ADR-023 §2's backlog row
|
||||
- [ ] `renew` extends from now (full window, not additive); foreign
|
||||
- [x] `renew` extends from now (full window, not additive); foreign
|
||||
owner cannot renew; `false` on lapsed/re-acquired
|
||||
- [ ] `release` removes only the owner's row; `false` = no-op shape
|
||||
- [ ] TTL lapse: after expiry, another owner acquires (silent lapse)
|
||||
- [ ] `cargo test -p alkstore-sqlite`, clippy `-D warnings`, fmt clean
|
||||
- [x] `release` removes only the owner's row; `false` = no-op shape
|
||||
- [x] TTL lapse: after expiry, another owner acquires (silent lapse)
|
||||
- [x] `cargo test -p alkstore-sqlite`, clippy `-D warnings`, fmt clean
|
||||
|
||||
## References
|
||||
|
||||
@@ -57,8 +57,60 @@ lock ops (`lock_acquire`, `lock_release`, `lock_renew`):
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
> Decisions of record the description didn't pin:
|
||||
|
||||
- **Same-owner re-acquire returns `Some`, not `None`** — the
|
||||
substrate's `lock_acquire` is opportunistic-expiry-delete +
|
||||
`INSERT OR IGNORE` + read-back: the same owner's re-acquire keeps
|
||||
the original row (TTL *not* refreshed) and reports granted. Pinned
|
||||
as the inherited shape (engine-sqlite.md's mapping row: "re-acquire
|
||||
does not refresh TTL — inherited deliberately"; the research
|
||||
register's D-4 kept `lock_acquire` verbatim). The handle returned
|
||||
is a fresh boxed `Lock`; `renew` remains the only refresh path.
|
||||
- **Post-lapse release returns `true` while the stale row survives,
|
||||
`false` only once the row is gone/foreign** — the substrate's
|
||||
`lock_release` is a blind `(name, owner)` delete and expired rows
|
||||
persist until the next acquire's opportunistic delete (no general
|
||||
lock-expiry sweep, upstream-inherited). So immediately after a
|
||||
lapse the stale holder's release deletes its own stale row
|
||||
(`true`); after a foreign re-acquire (or any row-less state) it is
|
||||
the no-op `false`. Tests pin both arms; the contract text's "true =
|
||||
was held" reads as row-scoped, which the substrate defines.
|
||||
- **The duration-guard detail rides the source chain, not
|
||||
`Display`** — the engine guard's message
|
||||
("ttl must be a positive duration (seconds), got {ttl}") is the
|
||||
`#[source]` inner error (ADR-008 §5's chain discipline; the
|
||||
`Database` display is `database error` opaque). The substrate's
|
||||
direct-caller message `ttl_s must be positive` is asserted verbatim
|
||||
at the substrate call site (A-2 above).
|
||||
- **Closed-store posture**: `try_lock`/`renew`/`release` check the
|
||||
engine's closed flag (or the writer slot's unavailability) and fail
|
||||
closed with opaque `Database` — the engine-wide shape every wave-3
|
||||
mechanism carries; release preserves the substrate row delete as
|
||||
the truth-teller rather than caching a handle-local "held" flag.
|
||||
- `store/lock_tests.rs` follows the wave-3 bounded-wait posture for
|
||||
TTL expiry (second-precision `unixepoch()`; no tight-race sleeps);
|
||||
the short-window lapse steps stamp short expiries directly through
|
||||
a separate connection (the queue tests' honest cross-connection
|
||||
probe shape) instead of sleeping out multi-second windows.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
> `alkstore-sqlite/src/lock.rs`: the acquire path (`try_lock` —
|
||||
> shared-namespace + non-empty owner validation, the ADR-023 §2
|
||||
> duration guard on `ttl <= 0` engine-side before the substrate, the
|
||||
> closed-store check, then `lock_acquire` through the writer slot,
|
||||
> `Ok(None)` = held elsewhere) and `SqliteLockHandle` (`Lock` impl:
|
||||
> `renew` with the same duration guard over `lock_renew`'s full-window
|
||||
> update; consuming `release(self: Box<Self>)` over `lock_release`'s
|
||||
> owner-scoped delete). `store.rs`'s `try_lock` stub replaced with the
|
||||
> wiring. Six acceptance tests in `store/lock_tests.rs` pin the
|
||||
> exclusion/loser shapes, both duration guards + the substrate's
|
||||
> verbatim message, full-window renew and stale-holder refusals,
|
||||
> owner-scoped release with both boolean arms, the silent
|
||||
> lapse-then-reacquire shape, and the cross-instance re-acquire (the
|
||||
> verification backlog's SQLite re-acquisition pin at engine level;
|
||||
> the contract-suite row remains for the suite itself). Verified:
|
||||
> `cargo build`, `cargo test` (workspace; 177 + 25 + 3 pass),
|
||||
> `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||
> — all clean.
|
||||
Reference in new issue
Block a user