From 9319eecf14e12ffd732638e4091867d12680a8ad Mon Sep 17 00:00:00 2001 From: Dmitry Kovba Date: Thu, 4 Sep 2025 23:07:11 -0700 Subject: [PATCH] Prevent a leak detection crash when `close()` fails (#280) When an image is broken, `container-core-images` may crash with: ``` Application Specific Information: _NIOFileSystem/SystemFileHandle.swift:131: Fatal error: Leaking file descriptor: the handle for '/Users/Dmitry/Library/Application Support/com.apple.container/content/ingest/03144D38-D98D-4833-9E0C-60229BF653D9/.tmp-MIB1oc' MUST be closed or detached with 'close()' or 'detachUnsafeFileDescriptor()' before the final reference to the handle is dropped. Thread 9 Crashed: 0 libswiftCore.dylib 0x1adbfe110 _assertionFailure(_:_:file:line:flags:) + 176 1 container-core-images 0x105773628 closure #1 in SystemFileHandle.deinit + 424 (SystemFileHandle.swift:131) 2 container-core-images 0x10577365c partial apply for closure #1 in SystemFileHandle.deinit + 20 3 container-core-images 0x104ebd994 closure #1 in LockStorage.withLockedValue(_:) + 124 (NIOLock.swift:183) 4 container-core-images 0x104ebde38 partial apply for closure #1 in LockStorage.withLockedValue(_:) + 52 5 container-core-images 0x104c387fc ManagedBuffer<>.withUnsafeMutablePointers(_:) + 152 6 container-core-images 0x104ebd8ec LockStorage.withLockedValue(_:) + 172 (NIOLock.swift:180) 7 container-core-images 0x104ebe088 NIOLockedValueBox.withLockedValue(_:) + 104 (NIOLockedValueBox.swift:38) 8 container-core-images 0x105773438 SystemFileHandle.deinit + 112 (SystemFileHandle.swift:128) 9 container-core-images 0x105773684 SystemFileHandle.__deallocating_deinit + 28 10 libswiftCore.dylib 0x1adaeef50 _swift_release_dealloc + 56 11 libswiftCore.dylib 0x1adaefabc bool swift::RefCounts>::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 152 12 container-core-images 0x104b81aac RegistryClient.fetchBlob(name:descriptor:into:progress:) + 180 (RegistryClient+Fetch.swift:200) 13 container-core-images 0x104b9f481 protocol witness for ContentClient.fetchBlob(name:descriptor:into:progress:) in conformance RegistryClient + 1 14 container-core-images 0x1049b6b29 ImageStore.ImportOperation.fetchBlob(_:) + 1 (ImageStore+Import.swift:167) 15 container-core-images 0x1049b5c6d ImageStore.ImportOperation.fetch(_:) + 1 (ImageStore+Import.swift:153) 16 container-core-images 0x1049b4f69 closure #1 in closure #1 in ImageStore.ImportOperation.fetchAll(_:) + 1 (ImageStore+Import.swift:126) 17 container-core-images 0x1049bb08d partial apply for closure #1 in closure #1 in ImageStore.ImportOperation.fetchAll(_:) + 1 18 libswift_Concurrency.dylib 0x2945e05f9 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 ``` Replacing the `SwiftNIO` file handle with the `Foundation` file handle resolved the crash. However, this led to a worse performance: ``` Debug version: SwiftNIO: 1:05s Foundation: 1:18s (1.2 times worse) Release version: SwiftNIO: 0:34s Foundation: 1:00s (1.8 times worse) ``` This PR attempts to prevent the crash without switching back to the `Foundation` file handle. --- .../ContainerizationOCI/Client/RegistryClient+Fetch.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/ContainerizationOCI/Client/RegistryClient+Fetch.swift b/Sources/ContainerizationOCI/Client/RegistryClient+Fetch.swift index 286c42cc..488ed237 100644 --- a/Sources/ContainerizationOCI/Client/RegistryClient+Fetch.swift +++ b/Sources/ContainerizationOCI/Client/RegistryClient+Fetch.swift @@ -195,7 +195,12 @@ extension RegistryClient { try await writer.flush() try await handle.close() } catch { - try? await handle.close() + do { + try await handle.close() + } catch { + // Use `detachUnsafeFileDescriptor()` as suggested by the error message to prevent a leak detection crash when `close()` fails. + _ = try handle.detachUnsafeFileDescriptor() + } throw error } let computedDigest = hasher.finalize()