Skip to content

Commit 033ab45

Browse files
authored
Add async overloads of withLock for FS I/O (#420)
When converting parts of the SwiftPM codebase from callbacks to `async`/`await` I've stumbled upon uses of file system locking that have to work across an async closure call. This is an additive change and has no impact on the existing blocking non-async uses of `FileLock.withLock`.
1 parent be6f396 commit 033ab45

File tree

2 files changed

+54
-9
lines changed

2 files changed

+54
-9
lines changed

Sources/TSCBasic/FileSystem.swift

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ public protocol FileSystem: Sendable {
288288

289289
/// Execute the given block while holding the lock.
290290
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () throws -> T) throws -> T
291+
292+
/// Execute the given block while holding the lock.
293+
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () async throws -> T) async throws -> T
291294
}
292295

293296
/// Convenience implementations (default arguments aren't permitted in protocol
@@ -336,6 +339,10 @@ public extension FileSystem {
336339
throw FileSystemError(.unsupported, path)
337340
}
338341

342+
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () async throws -> T) async throws -> T {
343+
throw FileSystemError(.unsupported, path)
344+
}
345+
339346
func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { false }
340347

341348
func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool { false }
@@ -601,12 +608,20 @@ private struct LocalFileSystem: FileSystem {
601608
func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
602609
try FileLock.withLock(fileToLock: path, type: type, body: body)
603610
}
611+
612+
func withLock<T>(
613+
on path: AbsolutePath,
614+
type: FileLock.LockType = .exclusive,
615+
_ body: () async throws -> T
616+
) async throws -> T {
617+
try await FileLock.withLock(fileToLock: path, type: type, body: body)
618+
}
604619
}
605620

606621
/// Concrete FileSystem implementation which simulates an empty disk.
607622
public final class InMemoryFileSystem: FileSystem {
608623
/// Private internal representation of a file system node.
609-
/// Not threadsafe.
624+
/// Not thread-safe.
610625
private class Node {
611626
/// The actual node data.
612627
let contents: NodeContents
@@ -622,7 +637,7 @@ public final class InMemoryFileSystem: FileSystem {
622637
}
623638

624639
/// Private internal representation the contents of a file system node.
625-
/// Not threadsafe.
640+
/// Not thread-safe.
626641
private enum NodeContents {
627642
case file(ByteString)
628643
case directory(DirectoryContents)
@@ -642,7 +657,7 @@ public final class InMemoryFileSystem: FileSystem {
642657
}
643658

644659
/// Private internal representation the contents of a directory.
645-
/// Not threadsafe.
660+
/// Not thread-safe.
646661
private final class DirectoryContents {
647662
var entries: [String: Node]
648663

@@ -697,7 +712,7 @@ public final class InMemoryFileSystem: FileSystem {
697712
}
698713

699714
/// Private function to look up the node corresponding to a path.
700-
/// Not threadsafe.
715+
/// Not thread-safe.
701716
private func getNode(_ path: AbsolutePath, followSymlink: Bool = true) throws -> Node? {
702717
func getNodeInternal(_ path: AbsolutePath) throws -> Node? {
703718
// If this is the root node, return it.
@@ -841,7 +856,7 @@ public final class InMemoryFileSystem: FileSystem {
841856
}
842857
}
843858

844-
/// Not threadsafe.
859+
/// Not thread-safe.
845860
private func _createDirectory(_ path: AbsolutePath, recursive: Bool) throws {
846861
// Ignore if client passes root.
847862
guard !path.isRoot else {
@@ -989,7 +1004,7 @@ public final class InMemoryFileSystem: FileSystem {
9891004
}
9901005

9911006
/// Private implementation of core copying function.
992-
/// Not threadsafe.
1007+
/// Not thread-safe.
9931008
private func _copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
9941009
// Get the source node.
9951010
guard let source = try getNode(sourcePath) else {

Sources/TSCBasic/Lock.swift

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,19 @@ public final class FileLock {
177177
defer { unlock() }
178178
return try body()
179179
}
180-
181-
public static func withLock<T>(fileToLock: AbsolutePath, lockFilesDirectory: AbsolutePath? = nil, type: LockType = .exclusive, body: () throws -> T) throws -> T {
180+
181+
/// 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)
184+
defer { unlock() }
185+
return try await body()
186+
}
187+
188+
private static func prepareLock(
189+
fileToLock: AbsolutePath,
190+
at lockFilesDirectory: AbsolutePath? = nil,
191+
_ type: LockType = .exclusive
192+
) throws -> FileLock {
182193
// unless specified, we use the tempDirectory to store lock files
183194
let lockFilesDirectory = try lockFilesDirectory ?? localFileSystem.tempDirectory
184195
if !localFileSystem.exists(lockFilesDirectory) {
@@ -215,7 +226,26 @@ public final class FileLock {
215226
#endif
216227
let lockFilePath = lockFilesDirectory.appending(component: lockFileName)
217228

218-
let lock = FileLock(at: lockFilePath)
229+
return FileLock(at: lockFilePath)
230+
}
231+
232+
public static func withLock<T>(
233+
fileToLock: AbsolutePath,
234+
lockFilesDirectory: AbsolutePath? = nil,
235+
type: LockType = .exclusive,
236+
body: () throws -> T
237+
) throws -> T {
238+
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory, type)
219239
return try lock.withLock(type: type, body)
220240
}
241+
242+
public static func withLock<T>(
243+
fileToLock: AbsolutePath,
244+
lockFilesDirectory: AbsolutePath? = nil,
245+
type: LockType = .exclusive,
246+
body: () async throws -> T
247+
) async throws -> T {
248+
let lock = try Self.prepareLock(fileToLock: fileToLock, at: lockFilesDirectory, type)
249+
return try await lock.withLock(type: type, body)
250+
}
221251
}

0 commit comments

Comments
 (0)