Skip to content

Commit b23163d

Browse files
authored
Handle different ThreadDestructor signatures between Android NDK 27 and NDK 28 (#3249)
This change enables swift-nio to be compiled for Android against both NDK 27 and NDK 28 ### Motivation: Android's NDK had been adopting [nullability annotations](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/ndk.md#nullability) in its C headers, and they have been changing them from release to release. The churn has mostly died down since the release of the NDK 27 (LTS), but one new change in NDK 28 affects the destructor argument for `pthread_create`, making the existing `ThreadDestructor` typealias invalid and leading to the following compile error when the [Android SDK bundle](https://github.com/skiptools/swift-android-toolchain/releases) is installed and linked to NDK 28: ``` $ swiftly run swift build --swift-sdk aarch64-unknown-linux-android28 +6.2-snapshot-2025-05-15-a Building for debugging... /opt/src/github/swift-everywhere/swift-nio/Sources/NIOPosix/ThreadPosix.swift:82:9: error: cannot convert value of type '@convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer?' to expected argument type '@convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer' 80 | &handleLinux, 81 | nil, 82 | destructor, | `- error: cannot convert value of type '@convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer?' to expected argument type '@convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer' 83 | args 84 | ) [4/21] Compiling NIOPosix ThreadPosix.swift ``` This change adapts the parameter we pass to that function to accommodate both NDK 27 and NDK 28. ### Modifications: There's currently no way to determine which version of the NDK is in use at compile time (see swiftlang/swift#81402), so this accommodation is implementing by having two functions named `coerceThreadDestructor` that return values that are suitable either NDK 27 or NDK 28 (using `unsafeBitCast`, but the layout of C functions doesn't know anything about nullability, so this is safe), and then let the type inference for `pthread_create` handle the rest. ### Result: swift-nio can be compiled against both NDK 27 and NDK 28 without errors.
1 parent fec9719 commit b23163d

File tree

1 file changed

+21
-1
lines changed

1 file changed

+21
-1
lines changed

Sources/NIOPosix/ThreadPosix.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,30 @@ private func sysPthread_create(
6060
)
6161
#else
6262
var handleLinux = pthread_t()
63+
#if os(Android)
64+
// NDK 27 signature:
65+
// int pthread_create(pthread_t* _Nonnull __pthread_ptr, pthread_attr_t const* _Nullable __attr, void* _Nonnull (* _Nonnull __start_routine)(void* _Nonnull), void* _Nullable);
66+
func coerceThreadDestructor(
67+
_ destructor: @escaping ThreadDestructor
68+
) -> (@convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer) {
69+
destructor
70+
}
71+
72+
// NDK 28 signature:
73+
// int pthread_create(pthread_t* _Nonnull __pthread_ptr, pthread_attr_t const* _Nullable __attr, void* _Nullable (* _Nonnull __start_routine)(void* _Nullable), void* _Nullable);
74+
func coerceThreadDestructor(
75+
_ destructor: @escaping ThreadDestructor
76+
) -> (@convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer?) {
77+
unsafeBitCast(destructor, to: (@convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer?).self)
78+
}
79+
#else
80+
// Linux doesn't change the signature
81+
func coerceThreadDestructor(_ destructor: ThreadDestructor) -> ThreadDestructor { destructor }
82+
#endif
6383
let result = pthread_create(
6484
&handleLinux,
6585
nil,
66-
destructor,
86+
coerceThreadDestructor(destructor),
6787
args
6888
)
6989
#endif

0 commit comments

Comments
 (0)