Skip to content

Fix the build with Musl libc #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
windows_swift_versions: '["6.1", "nightly-main"]'
enable_macos_checks: true
macos_xcode_versions: '["16.3"]'
enable_linux_static_sdk_build: true
linux_static_sdk_versions: '["6.1", "nightly-6.2"]'

soundness:
name: Soundness
Expand Down
130 changes: 63 additions & 67 deletions Sources/Subprocess/IO/AsyncIO+Linux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import Glibc
#elseif canImport(Android)
import Android
import posix_filesystem.sys_epoll
#elseif canImport(Musl)
import Musl
#endif
Expand All @@ -40,7 +41,7 @@ final class AsyncIO: Sendable {

typealias OutputStream = AsyncThrowingStream<AsyncBufferSequence.Buffer, any Error>

private final class MonitorThreadContext {
private struct MonitorThreadContext: Sendable {
let epollFileDescriptor: CInt
let shutdownFileDescriptor: CInt

Expand Down Expand Up @@ -95,7 +96,7 @@ final class AsyncIO: Sendable {
events: EPOLLIN.rawValue,
data: epoll_data(fd: shutdownFileDescriptor)
)
var rc = epoll_ctl(
let rc = epoll_ctl(
epollFileDescriptor,
EPOLL_CTL_ADD,
shutdownFileDescriptor,
Expand All @@ -117,76 +118,71 @@ final class AsyncIO: Sendable {
epollFileDescriptor: epollFileDescriptor,
shutdownFileDescriptor: shutdownFileDescriptor
)
let threadContext = Unmanaged.passRetained(context)
var thread: pthread_t = pthread_t()
rc = pthread_create(&thread, nil, { args in
func reportError(_ error: SubprocessError) {
_registration.withLock { store in
for continuation in store.values {
continuation.finish(throwing: error)
let thread: pthread_t
do {
thread = try pthread_create {
func reportError(_ error: SubprocessError) {
_registration.withLock { store in
for continuation in store.values {
continuation.finish(throwing: error)
}
}
}
}

let unmanaged = Unmanaged<MonitorThreadContext>.fromOpaque(args!)
let context = unmanaged.takeRetainedValue()

var events: [epoll_event] = Array(
repeating: epoll_event(events: 0, data: epoll_data(fd: 0)),
count: _epollEventSize
)

// Enter the monitor loop
monitorLoop: while true {
let eventCount = epoll_wait(
context.epollFileDescriptor,
&events,
CInt(events.count),
-1
var events: [epoll_event] = Array(
repeating: epoll_event(events: 0, data: epoll_data(fd: 0)),
count: _epollEventSize
)
if eventCount < 0 {
if errno == EINTR || errno == EAGAIN {
continue // interrupted by signal; try again
}
// Report other errors
let error = SubprocessError(
code: .init(.asyncIOFailed(
"epoll_wait failed")
),
underlyingError: .init(rawValue: errno)
)
reportError(error)
break monitorLoop
}

for index in 0 ..< Int(eventCount) {
let event = events[index]
let targetFileDescriptor = event.data.fd
// Breakout the monitor loop if we received shutdown
// from the shutdownFD
if targetFileDescriptor == context.shutdownFileDescriptor {
var buf: UInt64 = 0
_ = _SubprocessCShims.read(context.shutdownFileDescriptor, &buf, MemoryLayout<UInt64>.size)
// Enter the monitor loop
monitorLoop: while true {
let eventCount = epoll_wait(
context.epollFileDescriptor,
&events,
CInt(events.count),
-1
)
if eventCount < 0 {
if errno == EINTR || errno == EAGAIN {
continue // interrupted by signal; try again
}
// Report other errors
let error = SubprocessError(
code: .init(.asyncIOFailed(
"epoll_wait failed")
),
underlyingError: .init(rawValue: errno)
)
reportError(error)
break monitorLoop
}

// Notify the continuation
let continuation = _registration.withLock { store -> SignalStream.Continuation? in
if let continuation = store[targetFileDescriptor] {
return continuation
for index in 0 ..< Int(eventCount) {
let event = events[index]
let targetFileDescriptor = event.data.fd
// Breakout the monitor loop if we received shutdown
// from the shutdownFD
if targetFileDescriptor == context.shutdownFileDescriptor {
var buf: UInt64 = 0
_ = _subprocess_read(context.shutdownFileDescriptor, &buf, MemoryLayout<UInt64>.size)
break monitorLoop
}
return nil

// Notify the continuation
let continuation = _registration.withLock { store -> SignalStream.Continuation? in
if let continuation = store[targetFileDescriptor] {
return continuation
}
return nil
}
continuation?.yield(true)
}
continuation?.yield(true)
}
}

return nil
}, threadContext.toOpaque())
guard rc == 0 else {
} catch let underlyingError {
let error = SubprocessError(
code: .init(.asyncIOFailed("Failed to create monitor thread")),
underlyingError: .init(rawValue: rc)
underlyingError: underlyingError
)
self.state = .failure(error)
return
Expand All @@ -211,14 +207,14 @@ final class AsyncIO: Sendable {

var one: UInt64 = 1
// Wake up the thread for shutdown
_ = _SubprocessCShims.write(currentState.shutdownFileDescriptor, &one, MemoryLayout<UInt64>.stride)
_ = _subprocess_write(currentState.shutdownFileDescriptor, &one, MemoryLayout<UInt64>.stride)
// Cleanup the monitor thread
pthread_join(currentState.monitorThread, nil)
var closeError: CInt = 0
if _SubprocessCShims.close(currentState.epollFileDescriptor) != 0 {
if _subprocess_close(currentState.epollFileDescriptor) != 0 {
closeError = errno
}
if _SubprocessCShims.close(currentState.shutdownFileDescriptor) != 0 {
if _subprocess_close(currentState.shutdownFileDescriptor) != 0 {
closeError = errno
}
if closeError != 0 {
Expand All @@ -231,7 +227,7 @@ final class AsyncIO: Sendable {
_ fileDescriptor: FileDescriptor,
for event: Event
) -> SignalStream {
return SignalStream { continuation in
return SignalStream { (continuation: SignalStream.Continuation) -> () in
// If setup failed, nothing much we can do
switch self.state {
case .success(let state):
Expand Down Expand Up @@ -261,9 +257,9 @@ final class AsyncIO: Sendable {
let targetEvent: EPOLL_EVENTS
switch event {
case .read:
targetEvent = EPOLLIN
targetEvent = EPOLL_EVENTS(EPOLLIN)
case .write:
targetEvent = EPOLLOUT
targetEvent = EPOLL_EVENTS(EPOLLOUT)
}

var event = epoll_event(
Expand Down Expand Up @@ -369,7 +365,7 @@ extension AsyncIO {
let offsetAddress = bufferPointer.baseAddress!.advanced(by: readLength)

// Read directly into the buffer at the offset
return _SubprocessCShims.read(fileDescriptor.rawValue, offsetAddress, targetCount)
return _subprocess_read(fileDescriptor.rawValue, offsetAddress, targetCount)
}
if bytesRead > 0 {
// Read some data
Expand Down Expand Up @@ -435,7 +431,7 @@ extension AsyncIO {
let written = bytes.withUnsafeBytes { ptr in
let remainingLength = ptr.count - writtenLength
let startPtr = ptr.baseAddress!.advanced(by: writtenLength)
return _SubprocessCShims.write(fileDescriptor.rawValue, startPtr, remainingLength)
return _subprocess_write(fileDescriptor.rawValue, startPtr, remainingLength)
}
if written > 0 {
writtenLength += written
Expand Down Expand Up @@ -478,7 +474,7 @@ extension AsyncIO {
let written = span.withUnsafeBytes { ptr in
let remainingLength = ptr.count - writtenLength
let startPtr = ptr.baseAddress!.advanced(by: writtenLength)
return _SubprocessCShims.write(fileDescriptor.rawValue, startPtr, remainingLength)
return _subprocess_write(fileDescriptor.rawValue, startPtr, remainingLength)
}
if written > 0 {
writtenLength += written
Expand Down
Loading