domains / orchestration / apple/container
Atomic Concurrency Contracts
All concurrency-sensitive code must follow an “atomic + lifecycle-valid” contract. **Rules** 1. **Make check-then-act atomic.** If you verify a precondition and then mutate shared state (e.g., “volume not in use => delete”), do both while holding the same synchronization boundary (actor/lock). Prefer closure-based helpers that keep the lock for the whole...
All concurrency-sensitive code must follow an “atomic + lifecycle-valid” contract.
Rules
- Make check-then-act atomic. If you verify a precondition and then mutate shared state (e.g., “volume not in use => delete”), do both while holding the same synchronization boundary (actor/lock). Prefer closure-based helpers that keep the lock for the whole check+mutation.
- Capture decisions under the lock. If later operations depend on a value that may change (e.g., flags/options determined while the container still exists), compute/store it while locked and use the stored value outside the lock.
- Validate lifecycle immediately before async continuation. Async completions (connect callbacks, background tasks) must not call into code paths after teardown/removal. Either (a) guard via “is still active”/state under the proper context, or (b) use a synchronization mechanism that makes completion observe the latest lifecycle state.
- Prevent blocking I/O deadlocks. For tests and IPC involving blocking pipes/FIFOs or child stdout/stderr, ensure producers/consumers are connected concurrently and/or redirect/drain output so the child can’t block.
- Use actor isolation or a single state mutex. Don’t mix partial mutexing with actor isolation in ways that leave invariants unclear; avoid
nonisolated(unsafe)unless you can prove all accesses are synchronized.
Illustrative patterns
Atomic delete check (closure holds lock during check+delete):
try await volumesService.delete(name: volume) // implementation should do:
try await containersService.withContainerList { containers in
guard !containers.contains(where: { $0.isUsingVolume(volume) && $0.isRunning() }) else {
throw VolumeError.volumeInUse(volume)
}
try await volumeStore.delete(volume)
}
Teardown-safe async completion (guard active state before acting):
.whenComplete { result in
switch result {
case .success(let channel):
guard context.channel.isActive else {
channel.close(promise: nil)
return
}
self.glue(channel, context: context)
case .failure(let error):
context.close(promise: nil)
context.fireErrorCaught(error)
}
}
Deadlock-free FIFO test (writer+reader concurrently):
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
defer { group.leave() }
let handle = try! FileHandle(forWritingTo: pipePath)
try! handle.write(contentsOf: "SECRET_KEY=value123\n".data(using: .utf8)!)
try! handle.close()
}
let lines = try Parser.envFile(path: pipePath)
group.wait()