swift-server-community / APNSwift

📱HTTP/2 Apple Push Notification Service built with swift - send push notifications to iOS, iPadOS, tvOS, macOS, watchOS, visionOS, and Safari!
Apache License 2.0
695 stars 105 forks source link

Promise leak crash #79

Closed hjuraev closed 4 years ago

hjuraev commented 4 years ago

Crashing with promise leak message

Fatal error: leaking promise created at (file: "/.build/checkouts/apnswift/Sources/APNSwift/APNSwiftConnection.swift", line: 197): file /.build/checkouts/apnswift/Sources/APNSwift/APNSwiftConnection.swift, line 197

Platform:

Current stack trace: 0 libswiftCore.so 0x00007f625a8f7ec0 swift_reportError + 50 1 libswiftCore.so 0x00007f625a968f60 _swift_stdlib_reportFatalErrorInFile + 115 2 libswiftCore.so 0x00007f625a88cf0e + 3514126 3 libswiftCore.so 0x00007f625a88d087 + 3514503 4 libswiftCore.so 0x00007f625a68282d + 1374253 5 libswiftCore.so 0x00007f625a863e68 + 3346024 6 libswiftCore.so 0x00007f625a681bc9 + 1371081 7 Run 0x000056280b0632d0 + 6386384 8 Run 0x000056280b118efe + 7130878 9 Run 0x000056280b118e78 + 7130744 10 Run 0x000056280b062cc6 + 6384838 11 Run 0x000056280b063507 + 6386951 12 libswiftCore.so 0x00007f625a8f99ab + 3959211 13 Run 0x000056280ab7e52f + 1254703 14 libswiftCore.so 0x00007f625a8f99ab + 3959211 15 Run 0x000056280ab7e645 + 1254981 16 libswiftCore.so 0x00007f625a8f99ab + 3959211 17 Run 0x000056280b069c6a + 6413418 18 libswiftCore.so 0x00007f625a8f99ab + 3959211 19 Run 0x000056280b05ebbb + 6368187 20 Run 0x000056280b060f58 + 6377304 21 Run 0x000056280b061214 + 6378004 22 Run 0x000056280b2d285d + 8939613 23 Run 0x000056280b0673fe + 6403070 24 Run 0x000056280b0643cb + 6390731 25 Run 0x000056280b064525 + 6391077 26 Run 0x000056280b06716a + 6402410 27 Run 0x000056280b2d1c2f + 8936495 28 Run 0x000056280b2df478 + 8991864 29 Run 0x000056280b0522ac + 6316716 30 Run 0x000056280b05de61 + 6364769 31 Run 0x000056280b0522cc + 6316748 32 Run 0x000056280b052317 + 6316823 33 Run 0x000056280add491f + 3705119 34 Run 0x000056280b059be4 + 6347748 35 Run 0x000056280b04d412 + 6296594 36 Run 0x000056280b050efb + 6311675 37 Run 0x000056280b055515 + 6329621 38 Run 0x000056280b05d92a + 6363434 39 Run 0x000056280b055b4f + 6331215 40 Run 0x000056280b117231 + 7123505 41 Run 0x000056280b1176c1 + 7124673 42 Run 0x000056280b117789 + 7124873 43 libpthread.so.0 0x00007f625a31b6db + 30427 44 libc.so.6 0x00007f6257f08850 clone + 63 Illegal instruction (core dumped)

kylebrowning commented 4 years ago

Can you give a bit more information on what you ran, and steps to reproduce? The stack trace is not symbolicated so it's hard to tell what's happening.

weissi commented 4 years ago

@kylebrowning it's a leak of this promise. In debug mode, NIO will print the file/line where the leaking promise was allocated:

Fatal error: leaking promise created at (file: "/.build/checkouts/apnswift/Sources/APNSwift/APNSwiftConnection.swift", line: 197): file /.build/checkouts/apnswift/Sources/APNSwift/APNSwiftConnection.swift, line 197

I can see a very straightforward leak there. If stream is already closed by the time you call writeAndFlush, it will fail (because the channel is already deallocated) so the promise will be leaked.

In case the writeAndFlush fails, you'll need to also fail responsePromise with the error you get.

kylebrowning commented 4 years ago

@weissi is there a way I can make it leak?

            return stream.writeAndFlush(context).recover { error in
                responsePromise.fail(error)
            }
weissi commented 4 years ago

@kylebrowning Couple of options:

recover is not a great idea btw, that makes the error go away. You want

return stream.writeAndFlush(context).flatMapErrorThrowing { error in
    responsePromise.fail(error)
    throw error
}
grosch commented 4 years ago

I'm having this same issue with version 1.7.0.

I got an error on push: ioOnClosedChannel Fatal error: leaking promise created at (file: "..../APNSwift/Sources/APNSwift/APNSwiftConnection.swift", line: 197): file ..../APNSwift/APNSwiftConnection.swift, line 197 2019-12-23 09:47:21.808779-0800 Run[57121:1075060] Fatal error: leaking promise created at (file: "..../APNSwift/APNSwiftConnection.swift", line: 197): file ...APNSwift/APNSwiftConnection.swift, line 197

weissi commented 4 years ago

@grosch just to be sure, mind attaching a backtrace? Here how you can do this from Xcode or lldb: https://gist.github.com/weissi/49bcd588ecc394e29afa2badb3278c73

grosch commented 4 years ago

bt.txt

grosch commented 4 years ago

@kylebrowning This is how I'm calling it:

let payload = "{}".data(using: .utf8)!
var rawBytes = ByteBufferAllocator().buffer(capacity: payload.count)
rawBytes.writeBytes(payload)

return req.apns.send(rawBytes: rawBytes, pushType: .background, to: reg.device.pushToken, topic: reg.pass.type)
    .flatMapError {
        // Unless APNs said it was a bad device token, just ignore the error.
        guard case let APNSwiftError.ResponseError.badRequest(response) = $0, response == .badDeviceToken else {
            return req.eventLoop.future()
        }

        // Be sure the device deletes before th e registration is deleted.
        // If you let them run in parallel issues might arise depending on
        // the hooks people have set for when a registration deletes, as it
        // might try to delete the same device again.
        return reg.device.delete(on: req.db)
            .flatMapError { _ in req.eventLoop.future() }
            .flatMap { reg.delete(on: req.db) }
}
weissi commented 4 years ago

Thanks @grosch ! @kylebrowning this is the relevant bit

* thread #6, name = 'NIO-ELT-0-#4', stop reason = Fatal error: leaking promise created at (file: "/Users/scott/Library/Developer/Xcode/DerivedData/server-empyslfnoeqomsdexfdameshsaij/SourcePackages/checkouts/APNSwift/Sources/APNSwift/APNSwiftConnection.swift", line: 197)
    frame #0: 0x00007fff71cdada0 libswiftCore.dylib`_swift_runtime_on_report
    frame #1: 0x00007fff71d522a3 libswiftCore.dylib`_swift_stdlib_reportFatalErrorInFile + 211
    frame #2: 0x00007fff71c68ade libswiftCore.dylib`function signature specialization <Arg[0] = Exploded, Arg[1] = Exploded, Arg[2] = Exploded> of closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 238
    frame #3: 0x00007fff71c68c57 libswiftCore.dylib`function signature specialization <Arg[0] = Exploded, Arg[1] = Exploded, Arg[2] = Exploded> of closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 87
    frame #4: 0x00007fff71a3989c libswiftCore.dylib`function signature specialization <Arg[1] = [Closure Propagated : closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never, Argument Types : [Swift.StaticStringSwift.UnsafeBufferPointer<Swift.UInt8>Swift.UIntSwift.UInt32]> of generic specialization <()> of Swift.String.withUTF8<A>((Swift.UnsafeBufferPointer<Swift.UInt8>) throws -> A) throws -> A + 236
    frame #5: 0x00007fff71c3a3e0 libswiftCore.dylib`function signature specialization <Arg[0] = Exploded, Arg[1] = Exploded> of Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 496
    frame #6: 0x00007fff71a38e59 libswiftCore.dylib`Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 25
  * frame #7: 0x00000001001386eb Run`closure #1 in EventLoopFuture.deinit(self=0x00000001025a0990) at EventLoopFuture.swift:418:21
    frame #8: 0x00000001001ea28e Run`closure #1 in implicit closure #1 in debugOnly(body=0x000000010013f040 Run`partial apply forwarder for closure #1 () -> () in NIO.EventLoopFuture.deinit at <compiler-generated>) at Utilities.swift:24:14
    frame #9: 0x00000001001ea208 Run`debugOnly(body=0x000000010013f040 Run`partial apply forwarder for closure #1 () -> () in NIO.EventLoopFuture.deinit at <compiler-generated>) at Utilities.swift:24:5
    frame #10: 0x00000001001380e6 Run`EventLoopFuture.deinit(self=0x00000001025a0990) at EventLoopFuture.swift:414:9
    frame #11: 0x0000000100138927 Run`EventLoopFuture.__deallocating_deinit(self=0x00000001025a0990) at EventLoopFuture.swift:0
    frame #12: 0x00007fff71cdcaf0 libswiftCore.dylib`_swift_release_dealloc + 16
    frame #13: 0x0000000100968bef Run`___lldb_unnamed_symbol1628$$Run + 47
    frame #14: 0x00007fff71cdcaf0 libswiftCore.dylib`_swift_release_dealloc + 16
    frame #15: 0x0000000100968d05 Run`___lldb_unnamed_symbol1629$$Run + 21
    frame #16: 0x00007fff71cdcaf0 libswiftCore.dylib`_swift_release_dealloc + 16
    frame #17: 0x000000010013f07a Run`___lldb_unnamed_symbol428$$Run + 42
    frame #18: 0x00007fff71cdcaf0 libswiftCore.dylib`_swift_release_dealloc + 16
    frame #19: 0x000000010013402b Run`outlined consume of Swift.Optional<@escaping @callee_guaranteed () -> (@owned NIO.CallbackList)> + 27
    frame #20: 0x0000000100136395 Run`EventLoopPromise._resolve(value=failure, self=NIO.EventLoopPromise<NIO.Channel & AnyObject> @ 0x000070000b2fddd0) at EventLoopFuture.swift:227:9
    frame #21: 0x0000000100136644 Run`EventLoopPromise.fail(error=ioOnClosedChannel, self=NIO.EventLoopPromise<NIO.Channel & AnyObject> @ 0x000070000b2fde78) at EventLoopFuture.swift:180:14
    frame #22: 0x000000010078126d Run`closure #3 in HTTP2StreamChannel.configure(error=ioOnClosedChannel, self=0x000000010259f740, promise=some) at HTTP2StreamChannel.swift:178:22
    frame #23: 0x000000010013c7ff Run`closure #1 in EventLoopFuture.whenFailure(self=0x00000001025a1dc0, callback=0x00000001007812d0 Run`partial apply forwarder for closure #3 (Swift.Error) -> () in NIOHTTP2.HTTP2StreamChannel.configure(initializer: Swift.Optional<(NIO.Channel, NIOHTTP2.HTTP2StreamID) -> NIO.EventLoopFuture<()>>, userPromise: Swift.Optional<NIO.EventLoopPromise<NIO.Channel>>) -> () at <compiler-generated>) at EventLoopFuture.swift:720:17
    frame #24: 0x00000001001397e8 Run`EventLoopFuture._addCallback(callback=0x000000010013f610 Run`partial apply forwarder for closure #1 () -> NIO.CallbackList in NIO.EventLoopFuture.whenFailure((Swift.Error) -> ()) -> () at <compiler-generated>, self=0x00000001025a1dc0) at EventLoopFuture.swift:663:16
    frame #25: 0x0000000100139942 Run`EventLoopFuture._whenComplete(callback=0x000000010013f610 Run`partial apply forwarder for closure #1 () -> NIO.CallbackList in NIO.EventLoopFuture.whenFailure((Swift.Error) -> ()) -> () at <compiler-generated>, self=0x00000001025a1dc0) at EventLoopFuture.swift:670:18
    frame #26: 0x000000010013c56a Run`EventLoopFuture.whenFailure(callback=0x00000001007812d0 Run`partial apply forwarder for closure #3 (Swift.Error) -> () in NIOHTTP2.HTTP2StreamChannel.configure(initializer: Swift.Optional<(NIO.Channel, NIOHTTP2.HTTP2StreamID) -> NIO.EventLoopFuture<()>>, userPromise: Swift.Optional<NIO.EventLoopPromise<NIO.Channel>>) -> () at <compiler-generated>, self=0x00000001025a1dc0) at EventLoopFuture.swift:718:14
    frame #27: 0x000000010078067d 

So as expected, we get an .ioOnClosedChannel so the writeAndFlush failed with that and then the promise never gets fulfilled.

kylebrowning commented 4 years ago

I haven't forgotten about this, I just haven't been able to write a test for it yet. #80

kylebrowning commented 4 years ago

This is fixed in #86