1 import Base 2 import ./DurableError.bend as DurableError 3 4 # Describes the pure outcome of one bounded lock-wait decision. 5 type WaitDecision is Data: 6 LockAcquired{} 7 LockRetry{sleep_ms: Nat} 8 LockTimedOut{} 9 LockFatal{} 10 11 # Recognizes the host errno values for lock contention. 12 def 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. 16 def missing_code(+code: U32) -> Bool: 17 U32.is_eq(code, 2) 18 19 # Recognizes unavailable lock or FFI primitives on supported POSIX hosts. 20 def 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. 25 def 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 38 def 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. 54 def 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. 66 def 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. 75 def 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. 79 def 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. 87 def 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. 91 def 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. 104 def 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)