Crash with `NIOLockedValueBox`
Expected behavior
It does not crash
Actual behavior
It crashes with EXC_BAD_ACCESS. See https://github.com/vapor/vapor/pull/3302 for more details
Steps to reproduce
-
git clone https://github.com/vapor/vapor -
cd vapor -
git switch nio-lockedvaluebox-crash -
swift test --filter testRepeatedAccessCausesNoStackOverflow
SwiftNIO version/commit hash
2.81.0
System & version information
Swift 6.1, macOS 15.4
Backtrace
(lldb) bt
* thread #7, name = 'NIO-SGLTN-0-#1', stop reason = EXC_BAD_ACCESS (code=2, address=0x1700bffe0)
frame #0: 0x000000019cfdb9e0 dyld`mach_o::Platform::Platform(unsigned int) + 4
frame #1: 0x000000019cf9420c dyld`dyld4::APIs::dyld_get_base_platform(unsigned int) + 40
frame #2: 0x000000019d01328c libxpc.dylib`_availability_version_check + 40
frame #3: 0x00000001aebb31ac libswiftCore.dylib`__isPlatformVersionAtLeast + 92
frame #4: 0x00000001aeadca90 libswiftCore.dylib`swift::swift_slowAllocTyped(unsigned long, unsigned long, unsigned long long) + 56
frame #5: 0x00000001aeadcf90 libswiftCore.dylib`_swift_allocObject_ + 1100
frame #6: 0x000000010a4fe76c VaporTests`closure #1 in ServiceTests.testRepeatedAccessCausesNoStackOverflow(_0=0x0000600002a3c230, myFakeService=VaporTests.MyTestService @ 0x0000600001429150) at ServiceTests.swift:0
frame #13295: 0x000000010b830208 VaporTests`Application.Service.service.getter() at Service.swift:46:20
* frame #13296: 0x000000010a4fe8a4 VaporTests`closure #2 in ServiceTests.testRepeatedAccessCausesNoStackOverflow(app=0x0000600002a3c230) at ServiceTests.swift:95:48
frame #13299: 0x000000010a8f900c VaporTests`closure #1 in EventLoopFuture.whenSuccess(, callback=0x000000010a9042f8 VaporTests`partial apply forwarder for reabstraction thunk helper <A><A1> from @escaping @callee_guaranteed @Sendable (@in_guaranteed A) -> (@out ()) to @escaping @callee_guaranteed @Sendable (@in_guaranteed A) -> () at <compiler-generated>) at EventLoopFuture.swift:836:17
frame #13300: 0x000000010a8f5a8c VaporTests`EventLoopFuture._addCallback(callback=0x000000010a904360 VaporTests`partial apply forwarder for closure #1 @Sendable () -> NIOCore.CallbackList in NIOCore.EventLoopFuture.whenSuccess((A) -> ()) -> () at <compiler-generated>) at EventLoopFuture.swift:791:16
frame #13301: 0x000000010a8f8cf8 VaporTests`EventLoopFuture._whenCompleteIsolated(callback=0x000000010a904360 VaporTests`partial apply forwarder for closure #1 @Sendable () -> NIOCore.CallbackList in NIOCore.EventLoopFuture.whenSuccess((A) -> ()) -> () at <compiler-generated>) at EventLoopFuture.swift:818:14
frame #13302: 0x000000010a8f8d98 VaporTests`closure #1 in EventLoopFuture._internalWhenComplete(, callback=0x000000010a904360 VaporTests`partial apply forwarder for closure #1 @Sendable () -> NIOCore.CallbackList in NIOCore.EventLoopFuture.whenSuccess((A) -> ()) -> () at <compiler-generated>) at EventLoopFuture.swift:809:22
frame #13303: 0x000000010aa2692c VaporTests`closure #1 in SelectableEventLoop.run(task=(function = 0x000000010a904500 VaporTests`partial apply forwarder for closure #1 @Sendable () -> () in NIOCore.EventLoopFuture._internalWhenComplete(@Sendable () -> NIOCore.CallbackList) -> () at <compiler-generated>)) at SelectableEventLoop.swift:590:17
frame #13305: 0x000000010aa2095c VaporTests`closure #1 in withAutoReleasePool<()>(execute=0x000000010aa2d458 VaporTests`partial apply forwarder for closure #1 () -> () in NIOPosix.SelectableEventLoop.run(NIOPosix.UnderlyingTask) -> () at <compiler-generated>) at SelectableEventLoop.swift:43:13
frame #13308: 0x000000010aa208d0 VaporTests`withAutoReleasePool<()>(execute=0x000000010aa2d458 VaporTests`partial apply forwarder for closure #1 () -> () in NIOPosix.SelectableEventLoop.run(NIOPosix.UnderlyingTask) -> () at <compiler-generated>) at SelectableEventLoop.swift:42:16
frame #13309: 0x000000010aa26824 VaporTests`SelectableEventLoop.run(task=(function = 0x000000010a904500 VaporTests`partial apply forwarder for closure #1 @Sendable () -> () in NIOCore.EventLoopFuture._internalWhenComplete(@Sendable () -> NIOCore.CallbackList) -> () at <compiler-generated>)) at SelectableEventLoop.swift:587:9
frame #13310: 0x000000010aa27e60 VaporTests`SelectableEventLoop.runLoop(selfIdentifier=ObjectIdentifier(0x0000600003438000 -> 0x000000010bc22c20 type metadata for NIOPosix.SelectableEventLoop)) at SelectableEventLoop.swift:764:22
frame #13311: 0x000000010aa28790 VaporTests`SelectableEventLoop.run() at SelectableEventLoop.swift:877:33
frame #13312: 0x000000010a9d257c VaporTests`static MultiThreadedEventLoopGroup.runTheLoop(thread=0x0000600000f217a0, parentGroup=0x00006000022308c0, canEventLoopBeShutdownIndividually=false, selectorFactory=0x000000010a9d31f4 VaporTests`implicit closure #1 @Sendable () throws -> NIOPosix.Selector<NIOPosix.NIORegistration> in static NIOPosix.MultiThreadedEventLoopGroup._makePerpetualGroup(threadNamePrefix: Swift.String, numberOfThreads: Swift.Int) -> NIOPosix.MultiThreadedEventLoopGroup at MultiThreadedEventLoopGroup.swift, initializer=0x000000010a9dac8c VaporTests`partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@in_guaranteed NIOPosix.NIOThread) -> (@out ()) to @escaping @callee_guaranteed (@guaranteed NIOPosix.NIOThread) -> () at <compiler-generated>, metricsDelegate=nil, callback=0x000000010a9dacdc VaporTests`partial apply forwarder for closure #1 (NIOPosix.SelectableEventLoop) -> () in closure #1 (NIOPosix.NIOThread) -> () in static NIOPosix.MultiThreadedEventLoopGroup.setupThreadAndEventLoop(name: Swift.String, parentGroup: NIOPosix.MultiThreadedEventLoopGroup, selectorFactory: () throws -> NIOPosix.Selector<NIOPosix.NIORegistration>, initializer: (NIOPosix.NIOThread) -> (), metricsDelegate: Swift.Optional<NIOPosix.NIOEventLoopMetricsDelegate>) -> NIOPosix.SelectableEventLoop at <compiler-generated>) at MultiThreadedEventLoopGroup.swift:105:22
frame #13313: 0x000000010a9d2bf0 VaporTests`closure #1 in static MultiThreadedEventLoopGroup.setupThreadAndEventLoop(t=0x0000600000f217a0, parentGroup=0x00006000022308c0, selectorFactory=0x000000010a9d31f4 VaporTests`implicit closure #1 @Sendable () throws -> NIOPosix.Selector<NIOPosix.NIORegistration> in static NIOPosix.MultiThreadedEventLoopGroup._makePerpetualGroup(threadNamePrefix: Swift.String, numberOfThreads: Swift.Int) -> NIOPosix.MultiThreadedEventLoopGroup at MultiThreadedEventLoopGroup.swift, initializer=0x000000010a9dac8c VaporTests`partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@in_guaranteed NIOPosix.NIOThread) -> (@out ()) to @escaping @callee_guaranteed (@guaranteed NIOPosix.NIOThread) -> () at <compiler-generated>, metricsDelegate=nil, lock=0x0000600000f21ad0, _loop=0x0000600003438000) at MultiThreadedEventLoopGroup.swift:126:41
frame #13316: 0x000000010aa5fddc VaporTests`closure #1 in closure #1 in static ThreadOpsPosix.run($0=0x600000f21b90) at ThreadPosix.swift:153:21
frame #13318: 0x000000019d301c0c libsystem_pthread.dylib`_pthread_start + 136
Can you expand on this a little @0xTim? This crashing stack doesn't have any evidence of NIOLockedValueBox on it.
Also I'm afraid that branch reference doesn't resolve for me.
Oops, would help if I pushed - pushed now.
Moving from NIOLockedValueBox to NIOLock solved the crash for us - I'll see if I can dig up some more info as well from when it was originally diagnosed
Awesome, I can reproduce.
Some other notes - one side effect of this was the CPU consistently climbing until the crash occurred. And on earlier versions of Swift, the backtrace was very different, much larger and pointing to NIOLockedValueBox (trying to get a stack trace now but LLDB on Linux is not playing ball)
Thanks Tim, I've reached out to some colleagues who work on the Swift compiler for a gut check.
The compiler is probably reabstracting the closure value on the way into the withLockedValue closure, and then because it's passed inout, writing back the re-reabstracted value every time withLockedValue is called. That would explain why the CPU and stack usage climbs gradually. As a workaround, you could parameterize NIOLockedValueBox on a nongeneric struct type that contains the closure as a field, instead of parameterizing it on a closure type directly.