Skip to content

Commit 27e73ed

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 27e73ed

File tree

3 files changed

+30
-14
lines changed

3 files changed

+30
-14
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/Platforms/Subprocess+Windows.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,8 +1166,20 @@ extension Configuration {
11661166
DeleteProcThreadAttributeList(attributeList)
11671167
}
11681168

1169-
var handles = Array(inheritedHandles)
1170-
handles.withUnsafeMutableBufferPointer { inheritedHandlesPtr in
1169+
let handles = Array(inheritedHandles)
1170+
1171+
// Manually allocate an array instead of using withUnsafeMutableBufferPointer, since the
1172+
// UpdateProcThreadAttribute documentation states that the lpValue pointer must persist
1173+
// until the attribute list is destroyed using DeleteProcThreadAttributeList.
1174+
let handlesPointer = UnsafeMutablePointer<HANDLE>.allocate(capacity: handles.count)
1175+
defer {
1176+
handlesPointer.deinitialize(count: handles.count)
1177+
handlesPointer.deallocate()
1178+
}
1179+
handlesPointer.initialize(from: handles, count: handles.count)
1180+
let inheritedHandlesPtr = UnsafeMutableBufferPointer(start: handlesPointer, count: handles.count)
1181+
1182+
do {
11711183
_ = UpdateProcThreadAttribute(
11721184
attributeList,
11731185
0,

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)