3
/// A lock that multiple processes on same host can use to restrict access to some shared resource, such as a file.
4
/// It can be locked recursively from the same thread.
5
/// Calls to `lock()` must be balanced by same number of `unlock()` calls.
7
/// - Two instances locking on the same resource won't behave as a recursive lock.
8
/// - If unbalanced `unlock()` call is detected `Error` will be thrown.
9
public final class FileLock {
10
private let fileDescriptor: Int32
11
private let recursiveLock = NSRecursiveLock()
12
private var counter = 0
14
public enum FileLockError: Error, CustomStringConvertible {
16
case unbalancedUnlockCalls
18
public var description: String {
20
case .errno(let code):
21
guard let systemDescription = strerror(code) else { return "Error: errno \(code)" }
22
let message = String(validatingUTF8: systemDescription) ?? "Unknown Error"
23
return "\(message) (errno \(code))"
24
case .unbalancedUnlockCalls:
25
return "Unbalanced number of calls: unlock() has been called more that lock()"
30
public static func named(_ name: String) throws -> FileLock {
31
let tempFolderPath = NSTemporaryDirectory() as NSString
32
return try FileLock(lockFilePath: tempFolderPath.appendingPathComponent(name + ".lock"))
35
public init(lockFilePath: String) throws {
36
fileDescriptor = open(lockFilePath, O_WRONLY | O_CREAT | O_CLOEXEC, 0o666)
37
if fileDescriptor == -1 {
38
throw FileLockError.errno(errno)
46
public func lock() throws {
48
defer { recursiveLock.unlock() }
56
if flock(fileDescriptor, LOCK_EX) == 0 {
59
// Retry if interrupted.
60
if errno == EINTR { continue }
61
throw FileLockError.errno(errno)
65
public func unlock() throws {
67
defer { recursiveLock.unlock() }
69
guard counter > 0 else {
70
throw FileLockError.unbalancedUnlockCalls
76
flock(fileDescriptor, LOCK_UN)
80
/// Attempts to lock. If this method returns `true`, you must `unlock()`.
81
public func tryToLock() -> Bool {
83
defer { recursiveLock.unlock() }
85
let result = flock(fileDescriptor, LOCK_EX | LOCK_NB) == 0
92
public var isLocked: Bool {
94
defer { recursiveLock.unlock() }
99
public func whileLocked<T>(work: () throws -> (T)) throws -> T {
103
let result = try work()