Skip to content

Commit d308cd6

Browse files
committed
Bring back "Lock scratch directory during tool execution"
This reverts commit e92df2d and brings back the changes from #7269. resolves #6643 rdar://113964179
1 parent 3e8328e commit d308cd6

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

Sources/Basics/FileSystem/FileSystem+Extensions.swift

+13-2
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ extension FileSystem {
196196
}
197197

198198
/// Execute the given block while holding the lock.
199-
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, _ body: () throws -> T) throws -> T {
200-
try self.withLock(on: path.underlying, type: type, body)
199+
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType, blocking: Bool = true, _ body: () throws -> T) throws -> T {
200+
try self.withLock(on: path.underlying, type: type, blocking: blocking, body)
201201
}
202202

203203
/// Returns any known item replacement directories for a given path. These may be used by platform-specific
@@ -616,3 +616,14 @@ extension FileSystem {
616616
try self.removeFileTree(tempDirectory)
617617
}
618618
}
619+
620+
// MARK: - Locking
621+
622+
extension FileLock {
623+
public static func prepareLock(
624+
fileToLock: AbsolutePath,
625+
at lockFilesDirectory: AbsolutePath? = nil
626+
) throws -> FileLock {
627+
return try Self.prepareLock(fileToLock: fileToLock.underlying, at: lockFilesDirectory?.underlying)
628+
}
629+
}

Sources/CoreCommands/SwiftTool.swift

+34
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ import Musl
3939
#endif
4040

4141
import func TSCBasic.exec
42+
import class TSCBasic.FileLock
4243
import protocol TSCBasic.OutputByteStream
4344
import class TSCBasic.Process
4445
import enum TSCBasic.ProcessEnv
46+
import enum TSCBasic.ProcessLockError
4547
import var TSCBasic.stderrStream
4648
import class TSCBasic.TerminalController
4749
import class TSCBasic.ThreadSafeOutputByteStream
@@ -118,6 +120,7 @@ extension SwiftCommand {
118120
} catch {
119121
toolError = error
120122
}
123+
try swiftTool.releaseLockIfNeeded()
121124

122125
// wait for all observability items to process
123126
swiftTool.waitForObservabilityEvents(timeout: .now() + 5)
@@ -396,6 +399,9 @@ public final class SwiftTool {
396399
return workspace
397400
}
398401

402+
// Before creating the workspace, we need to acquire a lock on the build directory.
403+
try self.acquireLockIfNeeded()
404+
399405
if options.resolver.skipDependencyUpdate {
400406
self.observabilityScope.emit(warning: "'--skip-update' option is deprecated and will be removed in a future release")
401407
}
@@ -866,6 +872,34 @@ public final class SwiftTool {
866872
case success
867873
case failure
868874
}
875+
876+
// MARK: - Locking
877+
878+
private var workspaceLock: FileLock?
879+
880+
fileprivate func acquireLockIfNeeded() throws {
881+
guard workspaceLock == nil else {
882+
throw InternalError("acquireLockIfNeeded() called multiple times")
883+
}
884+
let workspaceLock = try FileLock.prepareLock(fileToLock: self.scratchDirectory)
885+
886+
// Try a non-blocking lock first so that we can inform the user about an already running SwiftPM.
887+
do {
888+
try workspaceLock.lock(type: .exclusive, blocking: false)
889+
} catch let ProcessLockError.unableToAquireLock(errno) {
890+
if errno == EWOULDBLOCK {
891+
self.outputStream.write("Another instance of SwiftPM is already running using '\(self.scratchDirectory)', waiting until that process has finished execution...".utf8)
892+
self.outputStream.flush()
893+
}
894+
}
895+
896+
try workspaceLock.lock(type: .exclusive, blocking: true)
897+
self.workspaceLock = workspaceLock
898+
}
899+
900+
fileprivate func releaseLockIfNeeded() {
901+
workspaceLock?.unlock()
902+
}
869903
}
870904

871905
/// Returns path of the nearest directory containing the manifest file w.r.t

0 commit comments

Comments
 (0)