Search code examples
swiftswift-nio

Swift NIO EventLoopFuture chain not completing


I have a very simple service built on a Vapor app. The service consumes data from another family of services. Obviously this is just the sort of application the map methods were made for.

All the callbacks in the chain execute, but the last EventLoopFuture in the chain never completes, causing my service to hang indefinitely.

This code makes one async call to get a session Id, then uses that data to check if a person is a member of a particular group. The calls to the other service return reasonable values - currently the second call always returns an error.

When I call this method, all the callbacks execute and behave as expected.

Note that in this code snippet I have "unchained" the stages of the sequence to try to shine light on where the problem is arising, but the overall behavior is not affected.

I have tried many different permutations of the chain, using flatMap, map, and flatMapError where appropriate, without altering the final result.

let authService = remoteApi.authServices();

// EventLoopFuture<String> with sessionId
let sessionIdFuture = authService.getSessionId(username: userName, password: password)

// EventLoopFuture<Bool> with whether the user is in a particular group
let gsFuture = sessionIdFuture.flatMap { sessionId -> EventLoopFuture<Bool> in
    let groupMemberService = remoteApi.groupMemberServices()
    return groupMemberService.personIsInGroup(sessionId: sessionId, groupId: groupId, userId: userId)
}

// EventLoopFuture<Bool> if the above has an error, just return false
let errorFuture = gsFuture.flatMapErrorThrowing { error in
    // This executes successfully!
    return false
}

// EventLoopFuture<String> with the return data 
let returnFuture = errorFuture.flatMapThrowing { isMember -> String in
    // This executes successfully!
    let response = PersonIsMemberResponse(isMember: isMember)
    if let json = self.encodeResponse(response) {
        print(json) // {"isMember": false}
        return json
    } else {
        throw Abort(.internalServerError, reason: "could not encode our own dang data type");
    }
}.always { result in
    // This executes!
    do {
        try remoteApi.shutdown()
    } catch {
        print(error)
    }
}

gsFuture.whenComplete { result in
    // This executes!
    print("gsFuture complete!")
}
errorFuture.whenComplete { result in
     // This executes!
   print("errorFuture complete!")
}
returnFuture.whenComplete { result in
    // This DOES NOT execute!
    print("returnFuture complete!")
}

I don't see how the last flatMapThrowing can be executed and return a value, then the future not complete. What am I missing?


Solution

  • As we figured out together in the comments, it looks like try remoteApi.shutdown() is blocking which prevents anything further from happening.