Skip to content

Commit 877292c

Browse files
authored
Merge pull request #77 from getong/add_SO_REUSEPORT
Not sure why GitHub is not updating, but the tests are green on Travis, so merging this! 😊 https://travis-ci.org/github/PSPDFKit-labs/bypass/builds/669226501
2 parents c77fa5b + eb29aed commit 877292c

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

lib/bypass/instance.ex

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ defmodule Bypass.Instance do
2222

2323
def init([opts]) do
2424
# Get a free port from the OS
25-
case :ranch_tcp.listen(ip: listen_ip(), port: Keyword.get(opts, :port, 0)) do
25+
case :ranch_tcp.listen(so_reuseport() ++ [ip: listen_ip(), port: Keyword.get(opts, :port, 0)]) do
2626
{:ok, socket} ->
2727
{:ok, port} = :inet.port(socket)
2828
:erlang.port_close(socket)
@@ -305,7 +305,7 @@ defmodule Bypass.Instance do
305305

306306
defp do_up(port, ref) do
307307
plug_opts = [self()]
308-
{:ok, socket} = :ranch_tcp.listen(ip: listen_ip(), port: port)
308+
{:ok, socket} = :ranch_tcp.listen(so_reuseport() ++ [ip: listen_ip(), port: port])
309309
cowboy_opts = cowboy_opts(port, ref, socket)
310310
{:ok, _pid} = Plug.Cowboy.http(Bypass.Plug, plug_opts, cowboy_opts)
311311
socket
@@ -376,6 +376,37 @@ defmodule Bypass.Instance do
376376
end
377377
end
378378

379+
# Use raw socket options to set SO_REUSEPORT so we fix {:error, :eaddrinuse} - where the OS errors
380+
# when we attempt to listen on the same port as before, since it's still considered in use.
381+
#
382+
# See https://lwn.net/Articles/542629/ for details on SO_REUSEPORT.
383+
#
384+
# See https://github.com/aetrion/erl-dns/blob/0c8d768/src/erldns_server_sup.erl#L81 for an
385+
# Erlang library using this approach.
386+
#
387+
# We want to do this:
388+
#
389+
# int optval = 1;
390+
# setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
391+
#
392+
# Use the following C program to find the values on each OS:
393+
#
394+
# #include <stdio.h>
395+
# #include <sys/socket.h>
396+
#
397+
# int main() {
398+
# printf("SOL_SOCKET: %d\n", SOL_SOCKET);
399+
# printf("SO_REUSEPORT: %d\n", SO_REUSEPORT);
400+
# return 0;
401+
# }
402+
defp so_reuseport() do
403+
case :os.type() do
404+
{:unix, :linux} -> [{:raw, 1, 15, <<1::32-native>>}]
405+
{:unix, :darwin} -> [{:raw, 65535, 512, <<1::32-native>>}]
406+
_ -> []
407+
end
408+
end
409+
379410
# This is used to override the default behaviour of ranch_tcp
380411
# and limit the range of interfaces it will listen on to just
381412
# the configured interface. Loopback is a default interface.

test/bypass_test.exs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ defmodule BypassTest do
1515
end
1616
end
1717

18+
test "show ISSUE #51" do
19+
Enum.each(1..1000,
20+
fn (_) ->
21+
bypass = %Bypass{} = Bypass.open(port: 8000)
22+
23+
Bypass.down(bypass)
24+
end
25+
)
26+
end
27+
1828
test "Bypass.open can specify a port to operate on with expect" do
1929
1234 |> specify_port(:expect)
2030
end
@@ -36,7 +46,8 @@ defmodule BypassTest do
3646
])
3747

3848
assert {:ok, 200, ""} = request(port)
39-
assert {:error, :eaddrinuse} == Bypass.open(port: port)
49+
bypass2 = Bypass.open(port: port)
50+
assert(is_map(bypass2) and bypass2.__struct__ == Bypass)
4051
end
4152

4253
test "Bypass.down takes down the socket with expect" do

0 commit comments

Comments
 (0)