Best Practices for Handling EventLoopPromise to Avoid Leaking Promises

I'm working with EventLoopPromise in SwiftNIO and have encountered an issue where a fatal error (Fatal error: leaking promise created ) arises during debugging if a promise is leaked due to an exception on an execution path that was missed. This typically occurs when the promise is passed back out and awaited by the caller:

func call1() async {
    let (bootstrap, promise1) = try call1()
    try await bootstrap.connect().get()
    await promise1.futureResult.get()
}

func call2() throws -> (ClientBootstrap, EventLoopPromise<Void>) {
    let promise1 = group.any().makePromise(of: Void.self)
    // More code where callbacks and execution paths fulfils promise1
}

I'm considering fulfilling the promise as successful by default, and then updating it if a failure occurs later. However, I'm unsure if this approach could lead to issues, such as overwriting errors or causing memory leaks, especially in release builds where this doesn't seem to occur:

let promise1 = group.any().makePromise(of: Void.self)
promise1.succeed() // avoid promise leaks
// promise1 will be fulfilled again somewhere below or in callbacks

Also, for reference, the code in question is part of the NATS Swift client, which can be found here: NATS Swift client source code

Can anyone advise if this practice could lead to potential problems, or if there's a more recommended approach to handling such scenarios?

Edit just realised it's a little stupid to fulfil straight away which defeats the point of waiting for it later on, nevertheless, I would appreciate if you have any insight to add.

It’s not possible. You can not fulfill a promise twice. After you have succeeded the promise all other attempts to fulfill the promise will be silently ignored. So in your case, even if an error has happened and you fail the promise, it wouldn‘t do anything because you have already succeeded the promise.

You need to keep track on your own to succeed or fail a promise. If you do everything in your function, you can add a defer that succeeds the promise when the function returns.

2 Likes

thanks for the answer. I just have to test/inspect the code I guess. defer is a good idea actually but in my case because it's passed to a callback as well I don't think it'd work.

1 Like

Note that there are some helpers that might be useful, including promise.completeWith and promise.completeWithTask. These can be used to encapsulate the result of a computation and ensure that you don't leak anything.

2 Likes

thank you. I didn't know these. not sure if / how I can use them here but they are very handy indeed. will keep them in mind.