Skip to content

Commit

Permalink
Implement Socket reuse_port (=reuse_address) on Windows (#13326)
Browse files Browse the repository at this point in the history
  • Loading branch information
stakach authored Apr 18, 2023
1 parent d907d50 commit 949efb7
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 16 deletions.
2 changes: 1 addition & 1 deletion spec/std/http/server/server_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe HTTP::Server do
ch.receive.end?.should be_true
end

pending_win32 "reuses the TCP port (SO_REUSEPORT)" do
it "reuses the TCP port (SO_REUSEPORT)" do
s1 = HTTP::Server.new { |ctx| }
address = s1.bind_unused_port(reuse_port: true)

Expand Down
9 changes: 4 additions & 5 deletions spec/std/socket/tcp_server_spec.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{% skip_file if flag?(:wasm32) %}

require "./spec_helper"
require "../../support/win32"

describe TCPServer, tags: "network" do
describe ".new" do
Expand Down Expand Up @@ -52,31 +51,31 @@ describe TCPServer, tags: "network" do
end

describe "reuse_port" do
pending_win32 "raises when port is in use" do
it "raises when port is in use" do
TCPServer.open(address, 0) do |server|
expect_raises(Socket::BindError, "Could not bind to '#{address}:#{server.local_address.port}': ") do
TCPServer.open(address, server.local_address.port) { }
end
end
end

pending_win32 "raises when not binding with reuse_port" do
it "raises when not binding with reuse_port" do
TCPServer.open(address, 0, reuse_port: true) do |server|
expect_raises(Socket::BindError) do
TCPServer.open(address, server.local_address.port) { }
end
end
end

pending_win32 "raises when port is not ready to be reused" do
it "raises when port is not ready to be reused" do
TCPServer.open(address, 0) do |server|
expect_raises(Socket::BindError) do
TCPServer.open(address, server.local_address.port, reuse_port: true) { }
end
end
end

pending_win32 "binds to used port with reuse_port = true" do
it "binds to used port with reuse_port = true" do
TCPServer.open(address, 0, reuse_port: true) do |server|
TCPServer.open(address, server.local_address.port, reuse_port: true) { }
end
Expand Down
13 changes: 10 additions & 3 deletions src/crystal/system/win32/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ module Crystal::System::Socket

private def initialize_handle(handle)
value = 1_u8
ret = LibC.setsockopt(handle, LibC::SOL_SOCKET, LibC::SO_REUSEADDR, pointerof(value), 1)
ret = LibC.setsockopt(handle, LibC::SOL_SOCKET, LibC::SO_EXCLUSIVEADDRUSE, pointerof(value), 1)
if ret == LibC::SOCKET_ERROR
raise ::Socket::Error.from_wsa_error("setsockopt")
end
Expand Down Expand Up @@ -254,11 +254,18 @@ module Crystal::System::Socket
end

private def system_reuse_port?
false
getsockopt_bool LibC::SO_REUSEADDR
end

private def system_reuse_port=(val : Bool)
raise NotImplementedError.new("Socket#reuse_port=")
if val
setsockopt_bool LibC::SO_EXCLUSIVEADDRUSE, false
setsockopt_bool LibC::SO_REUSEADDR, true
else
setsockopt_bool LibC::SO_REUSEADDR, false
setsockopt_bool LibC::SO_EXCLUSIVEADDRUSE, true
end
val
end

private def system_linger
Expand Down
29 changes: 22 additions & 7 deletions src/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -339,13 +339,28 @@ class Socket < IO
val
end

def reuse_address? : Bool
getsockopt_bool LibC::SO_REUSEADDR
end

def reuse_address=(val : Bool)
setsockopt_bool LibC::SO_REUSEADDR, val
end
# SO_REUSEADDR, as used in posix, is always assumed on windows
# the SO_REUSEADDR flag on windows is the equivalent of SO_REUSEPORT on linux
# https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse#application-strategies
{% if flag?(:win32) %}
# the address component of a binding can always be reused on windows
def reuse_address? : Bool
true
end

# there is no effect on windows
def reuse_address=(val : Bool)
val
end
{% else %}
def reuse_address? : Bool
getsockopt_bool LibC::SO_REUSEADDR
end

def reuse_address=(val : Bool)
setsockopt_bool LibC::SO_REUSEADDR, val
end
{% end %}

def reuse_port? : Bool
system_reuse_port?
Expand Down

0 comments on commit 949efb7

Please sign in to comment.