Skip to content

Commit 928879f

Browse files
committed
Don't suspend/resume the thread while holding an exclusive inout reference
We pass WorkQueue.queue 'by reference' through inout parameters and across thread suspension points. We noticed a stale value issue in release builds when passing Array with inout, likely due to the copy-on-write mechanism. To avoid stale values after a thread resumes from suspension, sleep the worker thread only after releasing the exclusive inout reference, and re-obtain it after resuming. Resolves #182
1 parent 43158de commit 928879f

File tree

4 files changed

+25
-13
lines changed

4 files changed

+25
-13
lines changed

.github/workflows/pull_request.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ jobs:
2828
# Test dependencies
2929
yum install -y procps
3030
fi
31-
linux_build_command: 'swift-format lint -s -r --configuration ./.swift-format . && swift test && swift test --disable-default-traits'
31+
linux_build_command: 'swift-format lint -s -r --configuration ./.swift-format . && swift test && swift test -c release && swift test --disable-default-traits'
3232
windows_swift_versions: '["6.1", "nightly-main"]'
3333
windows_build_command: |
3434
Invoke-Program swift test
35+
Invoke-Program swift test -c release
3536
Invoke-Program swift test --disable-default-traits
3637
enable_macos_checks: true
3738
macos_xcode_versions: '["16.3"]'
38-
macos_build_command: 'xcrun swift-format lint -s -r --configuration ./.swift-format . && xcrun swift test && xcrun swift test --disable-default-traits'
39+
macos_build_command: 'xcrun swift-format lint -s -r --configuration ./.swift-format . && xcrun swift test && xcrun swift test -c release && xcrun swift test --disable-default-traits'
3940
enable_linux_static_sdk_build: true
4041
linux_static_sdk_versions: '["6.1", "nightly-6.2"]'
4142
linux_static_sdk_build_command: |

Sources/Subprocess/Error.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ extension SubprocessError {
5151
case failedToResume
5252
case failedToCreatePipe
5353
case invalidWindowsPath(String)
54+
case failedToCreateProcessThreadAttributeList
5455
}
5556

5657
/// The numeric value of this code.
@@ -86,6 +87,8 @@ extension SubprocessError {
8687
return 13
8788
case .invalidWindowsPath(_):
8889
return 14
90+
case .failedToCreateProcessThreadAttributeList:
91+
return 15
8992
}
9093
}
9194

@@ -132,6 +135,8 @@ extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible
132135
return "Failed to create a pipe to communicate to child process."
133136
case .invalidWindowsPath(let badPath):
134137
return "\"\(badPath)\" is not a valid Windows path."
138+
case .failedToCreateProcessThreadAttributeList:
139+
return "Failed to create process thread attribute list."
135140
}
136141
}
137142

Sources/Subprocess/Platforms/Subprocess+Windows.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,10 @@ extension Configuration {
155155
spawnContext.startupInfo.pointer(to: \.StartupInfo)!,
156156
&processInfo
157157
)
158-
return (result, processInfo, GetLastError())
158+
if result == ERROR_INVALID_PARAMETER {
159+
fatalError("applicationNameW=\(applicationNameW), commandAndArgsW=\(commandAndArgsW), spawnContext.createProcessFlags=\(spawnContext.createProcessFlags), environmentW=\(environmentW), intendedWorkingDirW=\(intendedWorkingDirW), StartupInfo=\(spawnContext.startupInfo.pointer(to: \.StartupInfo)!)")
160+
}
161+
return (result, processInfo, result)
159162
}
160163
}
161164
}

Sources/Subprocess/Thread.swift

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ private final class WorkQueue: Sendable {
101101
}
102102

103103
private func withUnsafeUnderlyingLock<R>(
104-
_ body: (UnsafeMutablePointer<MutexType>, inout [BackgroundWorkItem]) throws -> R
104+
condition: (inout [BackgroundWorkItem]) -> Bool = { _ in false },
105+
body: (UnsafeMutablePointer<MutexType>, inout [BackgroundWorkItem]) throws -> R
105106
) rethrows -> R {
106107
#if canImport(WinSDK)
107108
EnterCriticalSection(self.mutex)
@@ -114,20 +115,22 @@ private final class WorkQueue: Sendable {
114115
pthread_mutex_unlock(mutex)
115116
}
116117
#endif
118+
if condition(&queue) {
119+
#if canImport(WinSDK)
120+
SleepConditionVariableCS(self.waitCondition, mutex, INFINITE)
121+
#else
122+
pthread_cond_wait(self.waitCondition, mutex)
123+
#endif
124+
}
117125
return try body(mutex, &queue)
118126
}
119127

120128
// Only called in worker thread. Sleeps the thread if there's no more item
121129
func dequeue() -> BackgroundWorkItem? {
122-
return self.withUnsafeUnderlyingLock { mutex, queue in
123-
if queue.isEmpty {
124-
// Sleep the worker thread if there's no more work
125-
#if canImport(WinSDK)
126-
SleepConditionVariableCS(self.waitCondition, mutex, INFINITE)
127-
#else
128-
pthread_cond_wait(self.waitCondition, mutex)
129-
#endif
130-
}
130+
return self.withUnsafeUnderlyingLock { queue in
131+
// Sleep the worker thread if there's no more work
132+
queue.isEmpty
133+
} body: { mutex, queue in
131134
guard !queue.isEmpty else {
132135
return nil
133136
}

0 commit comments

Comments
 (0)