Skip to content

Commit

Permalink
Fix a crash when replying to a closed socket
Browse files Browse the repository at this point in the history
  • Loading branch information
Azolo committed Feb 9, 2018
1 parent da9a661 commit 9e200ee
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/websockex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ defmodule WebSockex do
{:reply, frame, new_state} ->
# A `with` that includes `else` clause isn't tail recursive (elixir-lang/elixir#6251)
res = with {:ok, binary_frame} <- WebSockex.Frame.encode_frame(frame),
do: :ok = WebSockex.Conn.socket_send(state.conn, binary_frame)
do: WebSockex.Conn.socket_send(state.conn, binary_frame)
case res do
:ok ->
debug = Utils.sys_debug(debug, {:reply, function, frame}, state)
Expand Down
89 changes: 89 additions & 0 deletions test/websockex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ defmodule WebSockexTest do
def handle_ping({:ping, "Bad Reply"}, _), do: :lemon_pie
def handle_ping({:ping, "Error"}, _), do: raise "Ping Error"
def handle_ping({:ping, "Exit"}, _), do: exit "Ping Exit"
def handle_ping({:ping, "Please Reply"}, state), do: {:reply, {:pong, "No"}, state}
def handle_ping(frame, state), do: super(frame, state)

# Implicitly test default implementation defined with using through super
Expand All @@ -146,6 +147,7 @@ defmodule WebSockexTest do
def handle_pong({:pong, "Bad Reply"}, _), do: :lemon_pie
def handle_pong({:pong, "Error"}, _), do: raise "Pong Error"
def handle_pong({:pong, "Exit"}, _), do: exit "Pong Exit"
def handle_pong({:pong, "Please Reply"}, state), do: {:reply, {:text, "No"}, state}

def handle_frame({:binary, msg}, %{catch_binary: pid} = state) do
send(pid, {:caught_binary, msg})
Expand All @@ -155,6 +157,7 @@ defmodule WebSockexTest do
send(pid, {:caught_text, msg})
{:ok, state}
end
def handle_frame({:text, "Please Reply"}, state), do: {:reply, {:text, "No"}, state}
def handle_frame({:text, "Bad Reply"}, _), do: :lemon_pie
def handle_frame({:text, "Error"}, _), do: raise "Frame Error"
def handle_frame({:text, "Exit"}, _), do: exit "Frame Exit"
Expand Down Expand Up @@ -642,6 +645,22 @@ defmodule WebSockexTest do
assert_receive {1011, ""}
assert_receive {:EXIT, _, %WebSockex.InvalidFrameError{}}
end

test "handles dead connections when replying", context do
Process.flag(:trap_exit, true)
%{socket: socket} = TestClient.get_conn(context.pid)
TestClient.catch_attr(context.pid, :disconnect, self())

:sys.suspend(context.pid)

WebSockex.cast(context.pid, {:send, {:text, "It's Closed"}})
:gen_tcp.shutdown(socket, :write)

:sys.resume(context.pid)

assert_receive {:EXIT, _, %WebSockex.ConnError{original: :closed}}
assert_received :caught_disconnect
end
end

describe "handle_connect callback" do
Expand Down Expand Up @@ -677,6 +696,24 @@ defmodule WebSockexTest do

assert_receive {:caught_text, ^text}
end

test "handles dead connections when replying", context do
Process.flag(:trap_exit, true)
conn = TestClient.get_conn(context.pid)
TestClient.catch_attr(context.pid, :disconnect, self())

:sys.suspend(context.pid)

frame = <<1::1, 0::3, 1::4, 0::1, 12::7, "Please Reply"::utf8>>
send(context.pid, {conn.transport, conn.socket, frame})

:gen_tcp.shutdown(conn.socket, :write)

:sys.resume(context.pid)

assert_receive {:EXIT, _, %WebSockex.ConnError{original: :closed}}
assert_received :caught_disconnect
end
end

describe "handle_info callback" do
Expand Down Expand Up @@ -706,6 +743,22 @@ defmodule WebSockexTest do
assert_receive {:EXIT, _, {:local, 4012, "Test Close"}}
assert_receive {4012, "Test Close"}
end

test "handles dead connections when replying", context do
Process.flag(:trap_exit, true)
%{socket: socket} = TestClient.get_conn(context.pid)
TestClient.catch_attr(context.pid, :disconnect, self())

:sys.suspend(context.pid)

send(context.pid, {:send, {:text, "It's Closed"}})
:gen_tcp.shutdown(socket, :write)

:sys.resume(context.pid)

assert_receive {:EXIT, _, %WebSockex.ConnError{original: :closed}}
assert_received :caught_disconnect
end
end

describe "terminate callback" do
Expand Down Expand Up @@ -973,6 +1026,24 @@ defmodule WebSockexTest do

assert_receive :received_payload_pong
end

test "handles dead connections when replying", context do
Process.flag(:trap_exit, true)
conn = TestClient.get_conn(context.pid)
TestClient.catch_attr(context.pid, :disconnect, self())

:sys.suspend(context.pid)

frame = <<1::1, 0::3, 9::4, 0::1, 12::7, "Please Reply"::utf8>>
send(context.pid, {conn.transport, conn.socket, frame})

:gen_tcp.shutdown(conn.socket, :write)

:sys.resume(context.pid)

assert_receive {:EXIT, _, %WebSockex.ConnError{original: :closed}}
assert_received :caught_disconnect
end
end

describe "handle_pong callback" do
Expand All @@ -989,6 +1060,24 @@ defmodule WebSockexTest do

assert_receive {:caught_payload_pong, "bananas"}
end

test "handles dead connections when replying", context do
Process.flag(:trap_exit, true)
conn = TestClient.get_conn(context.pid)
TestClient.catch_attr(context.pid, :disconnect, self())

:sys.suspend(context.pid)

frame = <<1::1, 0::3, 10::4, 0::1, 12::7, "Please Reply"::utf8>>
send(context.pid, {conn.transport, conn.socket, frame})

:gen_tcp.shutdown(conn.socket, :write)

:sys.resume(context.pid)

assert_receive {:EXIT, _, %WebSockex.ConnError{original: :closed}}
assert_received :caught_disconnect
end
end

describe "disconnects and handle_disconnect callback" do
Expand Down

0 comments on commit 9e200ee

Please sign in to comment.