src/DbLockPolicy.bend

mylsm-lsm-store@0.6.0.1 · 105 lines · 13 definitions

1import Base
2import mylsm-lsm-store@0.6.0.1/src/DurableError.bend as DurableError
3
4# Describes the pure outcome of one bounded lock-wait decision.
5type WaitDecision is Data:
6  LockAcquired{}
7  LockRetry{sleep_ms: Nat}
8  LockTimedOut{}
9  LockFatal{}
10
11# Recognizes the host errno values for lock contention.
12def busy_code(+code: U32) -> Bool:
13  U32.is_eq(code, 11) || U32.is_eq(code, 35)
14
15# Recognizes the host errno for a missing path.
16def missing_code(+code: U32) -> Bool:
17  U32.is_eq(code, 2)
18
19# Recognizes unavailable lock or FFI primitives on supported POSIX hosts.
20def unsupported_code(+code: U32) -> Bool:
21  U32.is_eq(code, 45) || U32.is_eq(code, 78) ||
22    U32.is_eq(code, 95) || U32.is_eq(code, 38)
23
24# Maps non-contention lock errors to NotFound or Io.
25def classify_nonbusy_lock_error(
26  missing: Bool,
27  code: U32,
28  message: String,
29  operation: String,
30  path: String,
31) -> DurableError.Error:
32  match missing:
33    case True{}:
34      DurableError.Error{DurableError.NotFound{}, operation, path, code, message}
35    case False{}:
36      DurableError.Error{DurableError.Io{}, operation, path, code, message}
37
38def classify_lock.nonbusy.platform(
39  unsupported: Bool,
40  missing: Bool,
41  code: U32,
42  message: String,
43  operation: String,
44  path: String
45) -> DurableError.Error:
46  match unsupported:
47    case True{}:
48      DurableError.Error{DurableError.UnsupportedPlatform{}, operation,
49        path, code, message}
50    case False{}:
51      classify_nonbusy_lock_error(missing, code, message, operation, path)
52
53# Classifies non-contention lock failures before preserving host details.
54def classify_lock_nonbusy(
55  unsupported: Bool,
56  missing: Bool,
57  code: U32,
58  message: String,
59  operation: String,
60  path: String
61) -> DurableError.Error:
62  classify_lock.nonbusy.platform(unsupported, missing, code, message,
63    operation, path)
64
65# Maps contention to Busy and retains host details.
66def classify_lock_busy(busy: Bool, +code: U32, message: String, operation: String, path: String) -> DurableError.Error:
67  match busy:
68    case True{}:
69      DurableError.Error{DurableError.Busy{}, operation, path, code, message}
70    case False{}:
71      classify_lock_nonbusy(unsupported_code(code), missing_code(code),
72        code, message, operation, path)
73
74# Maps lock errors using the pure platform policy.
75def classify_lock_error(+code: U32, message: String, operation: String, path: String) -> DurableError.Error:
76  classify_lock_busy(busy_code(code), code, message, operation, path)
77
78# Chooses a bounded sleep or timeout after a contended lock attempt.
79def busy_wait.remaining(remaining_ms: Nat, backoff_ms: Nat) -> WaitDecision:
80  match remaining_ms:
81    case 0n:
82      LockTimedOut{}
83    case 1n+rest:
84      LockRetry{Nat.min(backoff_ms, Nat.add(1n, rest))}
85
86# Chooses a bounded sleep or timeout after a contended lock attempt.
87def busy_wait_decision(elapsed_ms: Nat, timeout_ms: Nat, backoff_ms: Nat) -> WaitDecision:
88  busy_wait.remaining(Nat.sub(timeout_ms, elapsed_ms), backoff_ms)
89
90# Selects acquired, retry, timeout, or fatal handling from raw lock facts.
91def wait_decision(
92  acquired: Bool,
93  busy: Bool,
94  elapsed_ms: Nat,
95  timeout_ms: Nat,
96  backoff_ms: Nat
97) -> WaitDecision:
98  Bool.pick(WaitDecision, acquired, LockAcquired{},
99    Bool.pick(WaitDecision, busy,
100      busy_wait_decision(elapsed_ms, timeout_ms, backoff_ms),
101      LockFatal{}))
102
103# Bounds the public timeout and nonzero backoff before any host effect.
104def wait_options_valid(timeout_ms: U32, +backoff_ms: U32) -> Bool:
105  U32.is_le(timeout_ms, 60000) && U32.is_lt(0, backoff_ms) && U32.is_le(backoff_ms, 1000)