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

Fix: configurable SO_REUSEPORT socket setting (disabled by default) #4046

Merged
merged 1 commit into from Feb 20, 2017

Conversation

ysbaddaden
Copy link
Contributor

@ysbaddaden ysbaddaden commented Feb 17, 2017

This led to confusing behavior when starting different TCP services that default to the same default ports. They didn't fail to start, but were failing to receive some requests.

I propose here to allow the setting to be easily configured and to disable it by default. You may enable it by setting the reuse_port named argument to true. For example:

TCPServer.open(9292, reuse_port: true) { }
server = HTTP::Server.new(9292) { |ctx| }
server.listen(reuse_port: true)

Maybe #listen could accept the reuse_port but I think #bind is a better place. Same for #new which would require to memoize the choice until we call #bind. I favored explicitness. Both HTTP::Server#bind and HTTP::Server#listen now accept the reuse_port method.

fixes #4019

@ysbaddaden
Copy link
Contributor Author

I also chose to not allow to configure SO_REUSEADDR which will (almost) never be set to false, as I explained in #4019 (comment). That would only cruft the API.

self.reuse_port = true
rescue Errno
end
self.reuse_port = true if reuse_port
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonus: now we can raise if the OS doesn't support SO_REUSEPORT, since it's now explicitly enabled.

@@ -138,8 +138,8 @@ class HTTP::Server
end
end

def bind
@server ||= TCPServer.new(@host, @port)
def bind(reuse_port = false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to have either all of host, port and reuse_port on #bind, or all in either .new or #listen. I appreciate it makes the API a bit more unwieldy but it seems to me that reuse_port option should be in the same place as port and host.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. I have no strong opinion here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the discussion of this issue we suggested to make it a property of HTTP::Server. What's wrong with that? That way it's used in both bind and listen, and if you stop the server and start it again the configuration is preserved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things:

  • host and port aren't properties, why should reuse_port be?
  • reuse_port, as host and port, only applies before binding the socket;
  • stopping and restarting HTTP::Server... will that happen? I'm not sure this is even possible (as of now).

Maybe an argument to #new would be better, just like TCPServer#new.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we have like 8 constructors, it will clutter the API.

For me, having it as a property or as named arguments in bind/listen works well too. But I'd probably add it to both listen and bind, because it's not very common to do bind + listen, just listen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allright. Do we need the property is we add the argument to both listen and bind? Just say "yes" or "no" :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no :-)

This led to confusing behavior when starting different TCP services
that default to the same default ports. They didn't fail to start,
but were failing to receive some requests.

The setting is now configurable and disabled by default. You may
enable it by setting the `reuse_port` named argument to true in the
following methods:

    TCPServer.new
    TCPServer.open
    HTTP::Server#bind
    HTTP::Server#listen
@ysbaddaden
Copy link
Contributor Author

Updated so HTTP::Server#listen now also takes a reuse_port argument.

@asterite asterite added this to the 0.21.0 milestone Feb 20, 2017
@asterite asterite merged commit b29a846 into crystal-lang:master Feb 20, 2017
@ysbaddaden ysbaddaden deleted the fix-tcp-server-reuse-port branch February 20, 2017 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Binding TCPServer to the same port from different processes
3 participants