Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failing to start on Mac due to in-use port fails to return #132

Closed
drekka opened this issue Jul 20, 2022 · 12 comments
Closed

Failing to start on Mac due to in-use port fails to return #132

drekka opened this issue Jul 20, 2022 · 12 comments

Comments

@drekka
Copy link
Contributor

drekka commented Jul 20, 2022

Not sure if I'm right about this. I have a Mac command line target that's starting a Hummingbird server.

What appears to be happening is that if the port I ask it to start on is already taken, Hummingbird fail and hangs at this point:

Screen Shot 2022-07-20 at 10 35 03 pm

This doesn't seem to occur when running the same startup code on an iOS simulator, just when running via a Mac command.

Any clues how I might resolve this?

@bubudrc
Copy link

bubudrc commented Jul 20, 2022

you can kill your localhost by running:

$ lsof -n -i4TCP:8080

PID is the second field. Then, kill that process:

$ kill -9 PID

Here are more examples for different ports

@drekka
Copy link
Contributor Author

drekka commented Jul 20, 2022

That's not really an option. Firstly because I don't know what's already got the port and I might need it. Second because I need to start Hummingbird from Swift. So shell scripting it's a possibility at this point.

@drekka
Copy link
Contributor Author

drekka commented Jul 21, 2022

Been digging into this and the problem seems to be related to the interaction of several pieces of code.

In HBApplication it starts with:

    public func start() throws {
        let promise = self.eventLoopGroup.next().makePromise(of: Void.self)
        self.lifecycle.start { error in
            if let error = error {
                self.logger.error("Failed starting HummingBird: \(error)")
                promise.fail(error)
            } else {
                self.logger.info("HummingBird started successfully")
                promise.succeed(())
            }
        }
        try promise.futureResult.wait()
    }

Then there is the code in NIO's EventLoopFuture:

@inlinable
    internal func _resolve(value: Result<Value, Error>) {
        if self.futureResult.eventLoop.inEventLoop {
            self._setValue(value: value)._run()
        } else {
            self.futureResult.eventLoop.execute {
                self._setValue(value: value)._run()
            }
        }
    }

And SelectableEventLoop:

 @inlinable
    internal func execute(_ task: @escaping () -> Void) {
        // nothing we can do if we fail enqueuing here.
        try? self._schedule0(ScheduledTask(id: self.scheduledTaskCounter.add(1), task, { error in
            // do nothing
        }, .now()))
    }

and finally in NIOPosix/SelectableEventLoop:

 @inlinable
    internal func execute(_ task: @escaping () -> Void) {
        // nothing we can do if we fail enqueuing here.
        try? self._schedule0(ScheduledTask(id: self.scheduledTaskCounter.add(1), task, { error in
            // do nothing
        }, .now()))
    }

When a port is already take, the application's start() ... self.lifecycle.start { closure calls the promise's .fail(...) which eventually executes EventLoopFuture._resolve(...).

In iOS inEventLoop returns true and the future's is set directly, which resolves it and allows the wait() to finish with the error being returned to the calling code.

In OSX however it's quite different. inEventLoop returns false so the EventLoopFuture reaches out to the POSIX event loop to .execute(...). That try's to schedule (presumably) on the current thread.

At this point execution stops and the future is never resolved. Which means that Hummingbird is hung, not able to either execute or exit.

My guess (because I don't have deep enough knowledge of NIO, etc) is that NIO is unable to execute the resolve of the future because it's trying to run it on the same thread that has been stopped by the .wait() call back in HummingBird's .start() function.

So, if I'm reading this right, any error generated by Hummingbird attempting to start in a POSIX environment would cause it to hang rather than return.

How this can be fixed I don't know because I don't have the knowledge to resolve this. Is there an alternative way to "wait", or can the code somehow bypass the queuing of the error resolving, or is there some other way to handle this?

@adam-fowler
Copy link
Member

If another process has the port, you can't bind to it, so you can't start your server. What do you expect Hummingbird to do at that point?

@drekka
Copy link
Contributor Author

drekka commented Jul 21, 2022

I expect it to stop with an error. It does that on iOS. On OS X it queues the error to the future, but then cannot execute that because the thread is locked. So it just hangs. The calling code doesn't get anything. No error. Nothing.

@adam-fowler
Copy link
Member

I do remember seeing something recently about using RunLoop I'll need to do some more investigation

@drekka
Copy link
Contributor Author

drekka commented Jul 25, 2022

Yeah. That's what I did in #133 but the code I have there is probably pretty hacky as it was the first thing I could think of. Did solve the issue though :-)

PS. I'm also working through this on Linux where some of the other guys in my team are using the code I've got. So far (apart from this issue) everything is working fine.

@adam-fowler
Copy link
Member

#135 should fix this

@adam-fowler
Copy link
Member

This is now merged into main. Can you verify this works for you

@drekka
Copy link
Contributor Author

drekka commented Jul 31, 2022

Cool. Will get back to you.

@drekka
Copy link
Contributor Author

drekka commented Jul 31, 2022

Hi, I've run Hummingbird on both OSX and Linux using the main branch and it's worked exactly as expected. Thank you. I look forwards to having it in a release.

@adam-fowler
Copy link
Member

Cheers, I should hopefully get a release out this morning

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants