Skip to content

Commit 6af60bf

Browse files
Fix a deadlock where we call OSLog while on OperationSystemAdaptor.queue, OSLog kicks off async work and waits on a semaphore, but all our async worker threads are busy waiting on the adaptor queue (rdar://146889893)
1 parent cd4fb4e commit 6af60bf

File tree

2 files changed

+21
-22
lines changed

2 files changed

+21
-22
lines changed

Sources/SWBBuildSystem/BuildOperation.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,15 +1850,19 @@ internal final class OperationSystemAdaptor: SWBLLBuild.BuildSystemDelegate, Act
18501850
return
18511851
}
18521852

1853+
guard let outputDelegate = (queue.blocking_sync { self.commandOutputDelegates.removeValue(forKey: command) }) else {
1854+
// If there's no outputDelegate, the command never started (i.e. it was skipped by shouldCommandStart().
1855+
return
1856+
}
1857+
1858+
// We can call this here because we're on an llbuild worker thread. This shouldn't be used while on `self.queue` because we have Swift async work elsewhere which blocks on that queue.
1859+
let sandboxViolations = task.isSandboxed && result == .failed ? task.extractSandboxViolationMessages_ASYNC_UNSAFE(startTime: outputDelegate.startTime) : []
1860+
18531861
queue.async {
1854-
// Find the output delegate, and remove it from the active set.
1855-
guard let outputDelegate = self.commandOutputDelegates.removeValue(forKey: command) else {
1856-
// If there's no outputDelegate, the command never started (i.e. it was skipped by shouldCommandStart().
1857-
return
1862+
for message in sandboxViolations {
1863+
outputDelegate.emit(Diagnostic(behavior: .error, location: .unknown, data: DiagnosticData(message)))
18581864
}
18591865

1860-
outputDelegate.emitSandboxingViolations(task: task, commandResult: result)
1861-
18621866
// This may be updated by commandProcessFinished if it was an
18631867
// ExternalCommand, so only update the exit status in output delegate if
18641868
// it is nil. However, always update the status if the result is failed,

Sources/SWBBuildSystem/SandboxViolations.swift

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,33 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import Foundation
13+
package import Foundation
1414
import SWBUtil
1515
package import SWBCore
16-
package import SWBTaskExecution
1716

1817
#if os(macOS)
1918
import OSLog
2019
#endif
2120

22-
extension TaskOutputDelegate {
23-
package func emitSandboxingViolations(task: any ExecutableTask, commandResult result: CommandResult) {
24-
guard task.isSandboxed && result == .failed else {
25-
return
26-
}
27-
28-
for message in task.extractSandboxViolationMessages(startTime: startTime) {
29-
emit(Diagnostic(behavior: .error, location: .unknown, data: DiagnosticData(message)))
30-
}
31-
}
32-
}
33-
3421
extension ExecutableTask {
3522
/// Whether or not this task is being executed within a sandbox which restricts filesystem access to declared inputs and outputs.
3623
///
3724
/// - note: Currently this will be true for any task whose executable is `sandbox-exec`, not only tasks which were created using _Swift Builds_ sandboxing mechanism and therefore whose diagnostics contain the message sentinel that we look for. However, this shouldn't really matter in practice at this time as if this property is true for a task not sandboxed _by Swift Build_, we'll simply not extract any diagnostics for it. The only way to get into this situation is to create such tasks in the PIF, which end-users can't do.
38-
fileprivate var isSandboxed: Bool {
25+
package var isSandboxed: Bool {
3926
return commandLine.first?.asByteString == ByteString(encodingAsUTF8: "/usr/bin/sandbox-exec")
4027
}
4128

42-
fileprivate func extractSandboxViolationMessages(startTime: Date) -> [String] {
29+
/// This must be called from threads which aren't Swift async worker threads. This func uses OSLog which kicks off async work and waits for it on a semaphore, causing deadlocks when invoked from Swift Concurrency worker threads.
30+
@available(*, noasync)
31+
package func extractSandboxViolationMessages_ASYNC_UNSAFE(startTime: Date) -> [String] {
4332
var res: [String] = []
4433
#if os(macOS)
34+
withUnsafeCurrentTask { task in
35+
if task != nil {
36+
preconditionFailure("This function should not be invoked from the Swift Concurrency thread pool as it may lead to deadlock via thread starvation.")
37+
}
38+
}
39+
4540
if let store = try? OSLogStore.local() {
4641
let query = String("((processID == 0 AND senderImagePath CONTAINS[c] \"/Sandbox\") OR (process == \"sandboxd\" AND subsystem == \"com.apple.sandbox.reporting\")) AND (eventMessage CONTAINS[c] %@)")
4742
let endTime = Date()

0 commit comments

Comments
 (0)