A Swift port of Python's filelock library, providing file-based locking using flock(2).
- Process-safe file locking via
flock(2) - Safe concurrent access from multiple instances - all
FileLockinstances for the same path share state - Reentrant locking within async task hierarchies
- Automatic lock release on scope exit or error
- Configurable retry behavior, timeouts, and blocking mode
- Cross-platform: macOS, iOS, and Linux
A naive flock(2) implementation has a subtle but critical bug: flock() locks are per file descriptor, not per file. If multiple parts of your code each create their own lock instance for the same path, they each open their own file descriptor, and each can acquire the "lock" simultaneously—completely defeating the purpose.
This library solves this by using a singleton pattern with shared context: all FileLock instances pointing to the same path share a single FileLockContext that manages the underlying file descriptor. This ensures true mutual exclusion even when locks are created independently across your codebase.
// These two locks coordinate correctly, even though they're separate instances
let lockPath = URL(filePath: "/tmp/resource.lock")
// Task 1
Task {
let lock1 = await FileLock(lockPath: lockPath)
try await lock1.withLock {
// Has exclusive access
}
}
// Task 2 - will wait for Task 1 to release
Task {
let lock2 = await FileLock(lockPath: lockPath)
try await lock2.withLock {
// Properly waits, then gets exclusive access
}
}Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/DePasqualeOrg/swift-filelock", from: "0.1.0"),
]import FileLock
let lock = await FileLock(lockPath: URL(filePath: "/tmp/myfile.lock"))
try await lock.withLock {
// Exclusive access to the protected resource
try data.write(to: targetPath)
}// Wait up to 5 minutes (300 retries × 1 second)
let lock = await FileLock(lockPath: lockPath, maxRetries: 300, retryDelay: 1.0)
// Wait indefinitely
let lock = await FileLock(lockPath: lockPath, maxRetries: nil)// Fail immediately if lock is held
let lock = await FileLock(lockPath: lockPath, blocking: false)
do {
try await lock.withLock {
// Exclusive access
}
} catch FileLockError.acquisitionFailed {
// Lock was held by another process
}
// Or override per-call
let lock = await FileLock(lockPath: lockPath) // blocking by default
try await lock.withLock(blocking: false) {
// Fails immediately if unavailable
}Locks are reentrant within the same async task hierarchy:
let lock = await FileLock(lockPath: lockPath)
try await lock.withLock {
// Outer lock acquired
try await lock.withLock {
// Inner lock succeeds (same task owns outer lock)
}
}