Skip to content

Commit 953a27b

Browse files
committed
Allow non-blocking file locks
1 parent 35afcbf commit 953a27b

File tree

2 files changed

+47
-35
lines changed

2 files changed

+47
-35
lines changed

Sources/TSCBasic/FileSystem.swift

+15-10
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,10 @@ public protocol FileSystem: Sendable {
290290
func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws
291291

292292
/// Execute the given block while holding the lock.
293-
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () throws -> T) throws -> T
293+
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, blocking: Bool, _ body: () throws -> T) throws -> T
294294

295295
/// Execute the given block while holding the lock.
296-
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () async throws -> T) async throws -> T
296+
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, blocking: Bool, _ body: () async throws -> T) async throws -> T
297297
}
298298

299299
/// Convenience implementations (default arguments aren't permitted in protocol
@@ -338,11 +338,11 @@ public extension FileSystem {
338338
throw FileSystemError(.unsupported, path)
339339
}
340340

341-
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () throws -> T) throws -> T {
341+
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, blocking: Bool = true, _ body: () throws -> T) throws -> T {
342342
throw FileSystemError(.unsupported, path)
343343
}
344344

345-
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () async throws -> T) async throws -> T {
345+
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, blocking: Bool = true, _ body: () async throws -> T) async throws -> T {
346346
throw FileSystemError(.unsupported, path)
347347
}
348348

@@ -612,16 +612,17 @@ private struct LocalFileSystem: FileSystem {
612612
try FileManager.default.moveItem(at: sourcePath.asURL, to: destinationPath.asURL)
613613
}
614614

615-
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
616-
try FileLock.withLock(fileToLock: path, type: type, body: body)
615+
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, blocking: Bool = true, _ body: () throws -> T) throws -> T {
616+
try FileLock.withLock(fileToLock: path, type: type, blocking: blocking, body: body)
617617
}
618618

619619
func withLock<T>(
620620
on path: AbsolutePath,
621621
type: FileLock.LockType = .exclusive,
622+
blocking: Bool = true,
622623
_ body: () async throws -> T
623624
) async throws -> T {
624-
try await FileLock.withLock(fileToLock: path, type: type, body: body)
625+
try await FileLock.withLock(fileToLock: path, type: type, blocking: blocking, body: body)
625626
}
626627

627628
func itemReplacementDirectories(for path: AbsolutePath) throws -> [AbsolutePath] {
@@ -1066,7 +1067,11 @@ public final class InMemoryFileSystem: FileSystem {
10661067
}
10671068
}
10681069

1069-
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
1070+
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, blocking: Bool = true, _ body: () throws -> T) throws -> T {
1071+
if !blocking {
1072+
throw FileSystemError(.unsupported, path)
1073+
}
1074+
10701075
let resolvedPath: AbsolutePath = try lock.withLock {
10711076
if case let .symlink(destination) = try getNode(path)?.contents {
10721077
return try AbsolutePath(validating: destination, relativeTo: path.parentDirectory)
@@ -1242,8 +1247,8 @@ public final class RerootedFileSystemView: FileSystem {
12421247
try underlyingFileSystem.move(from: formUnderlyingPath(sourcePath), to: formUnderlyingPath(sourcePath))
12431248
}
12441249

1245-
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
1246-
return try underlyingFileSystem.withLock(on: formUnderlyingPath(path), type: type, body)
1250+
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, blocking: Bool = true, _ body: () throws -> T) throws -> T {
1251+
return try underlyingFileSystem.withLock(on: formUnderlyingPath(path), type: type, blocking: blocking, body)
12471252
}
12481253
}
12491254

Sources/TSCBasic/Lock.swift

+32-25
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ extension NSLock {
4545
}
4646
}
4747

48-
enum ProcessLockError: Error {
48+
public enum ProcessLockError: Error {
4949
case unableToAquireLock(errno: Int32)
5050
}
5151

@@ -89,7 +89,7 @@ public final class FileLock {
8989
/// Try to acquire a lock. This method will block until lock the already aquired by other process.
9090
///
9191
/// Note: This method can throw if underlying POSIX methods fail.
92-
public func lock(type: LockType = .exclusive) throws {
92+
public func lock(type: LockType = .exclusive, blocking: Bool = true) throws {
9393
#if os(Windows)
9494
if handle == nil {
9595
let h: HANDLE = lockFile.pathString.withCString(encodedAs: UTF16.self, {
@@ -112,17 +112,17 @@ public final class FileLock {
112112
overlapped.Offset = 0
113113
overlapped.OffsetHigh = 0
114114
overlapped.hEvent = nil
115+
var dwFlags = 0
115116
switch type {
116-
case .exclusive:
117-
if !LockFileEx(handle, DWORD(LOCKFILE_EXCLUSIVE_LOCK), 0,
118-
UInt32.max, UInt32.max, &overlapped) {
119-
throw ProcessLockError.unableToAquireLock(errno: Int32(GetLastError()))
120-
}
121-
case .shared:
122-
if !LockFileEx(handle, 0, 0,
123-
UInt32.max, UInt32.max, &overlapped) {
124-
throw ProcessLockError.unableToAquireLock(errno: Int32(GetLastError()))
125-
}
117+
case .exclusive: dwFlags |= LOCKFILE_EXCLUSIVE_LOCK
118+
case .shared: break
119+
}
120+
if !blocking {
121+
dwFlags |= LOCKFILE_FAIL_IMMEDIATELY
122+
}
123+
if !LockFileEx(handle, DWORD(dwFlags), 0,
124+
UInt32.max, UInt32.max, &overlapped) {
125+
throw ProcessLockError.unableToAquireLock(errno: Int32(GetLastError()))
126126
}
127127
#else
128128
// Open the lock file.
@@ -133,11 +133,17 @@ public final class FileLock {
133133
}
134134
self.fileDescriptor = fd
135135
}
136+
var flags = Int32(0)
137+
switch type {
138+
case .exclusive: flags = LOCK_EX
139+
case .shared: flags = LOCK_SH
140+
}
141+
if !blocking {
142+
flags |= LOCK_NB
143+
}
136144
// Aquire lock on the file.
137145
while true {
138-
if type == .exclusive && flock(fileDescriptor!, LOCK_EX) == 0 {
139-
break
140-
} else if type == .shared && flock(fileDescriptor!, LOCK_SH) == 0 {
146+
if flock(fileDescriptor!, flags) == 0 {
141147
break
142148
}
143149
// Retry if interrupted.
@@ -172,23 +178,22 @@ public final class FileLock {
172178
}
173179

174180
/// Execute the given block while holding the lock.
175-
public func withLock<T>(type: LockType = .exclusive, _ body: () throws -> T) throws -> T {
176-
try lock(type: type)
181+
public func withLock<T>(type: LockType = .exclusive, blocking: Bool = true, _ body: () throws -> T) throws -> T {
182+
try lock(type: type, blocking: blocking)
177183
defer { unlock() }
178184
return try body()
179185
}
180186

181187
/// Execute the given block while holding the lock.
182-
public func withLock<T>(type: LockType = .exclusive, _ body: () async throws -> T) async throws -> T {
183-
try lock(type: type)
188+
public func withLock<T>(type: LockType = .exclusive, blocking: Bool = true, _ body: () async throws -> T) async throws -> T {
189+
try lock(type: type, blocking: blocking)
184190
defer { unlock() }
185191
return try await body()
186192
}
187193

188194
private static func prepareLock(
189195
fileToLock: AbsolutePath,
190-
at lockFilesDirectory: AbsolutePath? = nil,
191-
_ type: LockType = .exclusive
196+
at lockFilesDirectory: AbsolutePath? = nil
192197
) throws -> FileLock {
193198
// unless specified, we use the tempDirectory to store lock files
194199
let lockFilesDirectory = try lockFilesDirectory ?? localFileSystem.tempDirectory
@@ -233,19 +238,21 @@ public final class FileLock {
233238
fileToLock: AbsolutePath,
234239
lockFilesDirectory: AbsolutePath? = nil,
235240
type: LockType = .exclusive,
241+
blocking: Bool = true,
236242
body: () throws -> T
237243
) throws -> T {
238-
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory, type)
239-
return try lock.withLock(type: type, body)
244+
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory)
245+
return try lock.withLock(type: type, blocking: blocking, body)
240246
}
241247

242248
public static func withLock<T>(
243249
fileToLock: AbsolutePath,
244250
lockFilesDirectory: AbsolutePath? = nil,
245251
type: LockType = .exclusive,
252+
blocking: Bool = true,
246253
body: () async throws -> T
247254
) async throws -> T {
248-
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory, type)
249-
return try await lock.withLock(type: type, body)
255+
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory)
256+
return try await lock.withLock(type: type, blocking: blocking, body)
250257
}
251258
}

0 commit comments

Comments
 (0)