swift-nio icon indicating copy to clipboard operation
swift-nio copied to clipboard

Crash with `NIOLockedValueBox`

Open 0xTim opened this issue 11 months ago • 7 comments

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

  1. git clone https://github.com/vapor/vapor
  2. cd vapor
  3. git switch nio-lockedvaluebox-crash
  4. 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

0xTim avatar Apr 14 '25 10:04 0xTim

Can you expand on this a little @0xTim? This crashing stack doesn't have any evidence of NIOLockedValueBox on it.

Lukasa avatar Apr 14 '25 11:04 Lukasa

Also I'm afraid that branch reference doesn't resolve for me.

Lukasa avatar Apr 14 '25 11:04 Lukasa

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

0xTim avatar Apr 14 '25 11:04 0xTim

Awesome, I can reproduce.

Lukasa avatar Apr 14 '25 12:04 Lukasa

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)

0xTim avatar Apr 14 '25 12:04 0xTim

Thanks Tim, I've reached out to some colleagues who work on the Swift compiler for a gut check.

Lukasa avatar Apr 14 '25 18:04 Lukasa

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.

jckarter avatar Apr 15 '25 17:04 jckarter