From 32f117deb2f4a12bfe4a3c544aa8dde82773d112 Mon Sep 17 00:00:00 2001 From: Clinton Nkwocha Date: Tue, 9 Dec 2025 18:28:10 +0000 Subject: [PATCH] Clean up `multi_handle`s on the same thread Motivation: `URLSession` elusively crashes due to a race condition in OpenSSL <1.1.0 when calling `curl_multi_cleanup` concurrently in different threads. _Partially resolves #3813_ Modifications: - Adjust `_MultiHandle.deinit` to asynchronously queue `curl_multi_cleanup` calls in `_MultiHandle.queue`. Result: Saves OpenSSL from cleaning up concurrently in different threads, avoiding the crash. Testing: Passing existing tests is sufficient. However, without this patch, the code snippet below crashes about once in 20 runs on Amazon Linux 2 > _curl 8.3.0 (aarch64-koji-linux-gnu) libcurl/8.3.0 OpenSSL/1.0.2k-fips zlib/1.2.7 libidn2/2.3.0 libpsl/0.21.5 (+libidn2/2.3.0) libssh2/1.4.3 nghttp2/1.41.0 OpenLDAP/2.4.44_ > _Release-Date: 2023-09-13_ ``` DispatchQueue.concurrentPerform(iterations: 100) { _ in let session: URLSession = URLSession(configuration: URLSessionConfiguration.default) DispatchQueue.concurrentPerform(iterations: 100) { _ in _ = session } } ``` --- .../URLSession/libcurl/MultiHandle.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift b/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift index f1b3ad6a21..22f42f2562 100644 --- a/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift +++ b/Sources/FoundationNetworking/URLSession/libcurl/MultiHandle.swift @@ -58,7 +58,16 @@ extension URLSession { easyHandles.forEach { try! CFURLSessionMultiHandleRemoveHandle(rawHandle, $0.rawHandle).asError() } - try! CFURLSessionMultiHandleDeinit(rawHandle).asError() + + let rawHandleSendable = Int(bitPattern: rawHandle) + + // To avoid intermittent crashes due to multi-threading issues in OpenSSL (<1.1.0) when calling curl_multi_cleanup, + // run all curl_multi_cleanup calls on the same thread. + // See https://curl.se/libcurl/c/threadsafe.html. + queue.async { + let rawHandleCopy = UnsafeMutableRawPointer(bitPattern: rawHandleSendable)! + try! CFURLSessionMultiHandleDeinit(rawHandleCopy).asError() + } } } }