ADR-015 Async RAII for Locks
Status: Accepted Date: 2025-10 Tags: api-design, disposal, typescript
Problem
Lock management requires cleanup on all code paths. Manual try/finally is error-prone. JavaScript's await using (AsyncDisposable, Node.js ≥20) provides RAII for automatic cleanup, but integration required design decisions around error handling, signal propagation, and state management.
Decision
Integrate AsyncDisposable into all acquire() results:
- All results implement
Symbol.asyncDisposeforawait usingcompatibility - Two config patterns: backend-level for
await using, lock-level forlock()helper - Stateless handle design—delegate idempotency to backend
- Handle methods accept optional
AbortSignalfor per-operation cancellation onReleaseErrorcallback for disposal failures (disposal never throws)- Manual
release()/extend()throw on system errors (consistent with backend API)
Alternatives (brief)
- Separate disposable wrapper — extra API surface
- Mutable released flag — race conditions, complexity
- Throw from disposal — violates AsyncDisposable contract
Impact
- Positive: Correctness guarantee, ergonomic API, error resilience, composable
- Negative/Risks: None—additive to existing API
Links
- Code/Docs:
common/disposable.ts,docs/specs/interface.md(Resource Management) - Related ADRs: ADR-016 (disposal timeout)