diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be9f81..8a6ac23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Fix `pong` frame not being a correct return type in spec - Fix bare `ping` and `pong` frames in spec - When neither grame doesn't have a payload +- Fix handling SSL socket closings during the `close_loop` ## 0.4.1 ### Enhancements diff --git a/lib/websockex.ex b/lib/websockex.ex index 25fc78a..71aff57 100644 --- a/lib/websockex.ex +++ b/lib/websockex.ex @@ -678,7 +678,7 @@ defmodule WebSockex do :gen.reply(from, {:error, %WebSockex.NotConnectedError{connection_state: :closing}}) close_loop(reason, parent, debug, state) - {:tcp_closed, ^socket} -> + {close_mod, ^socket} when close_mod in [:tcp_closed, :ssl_closed] -> new_conn = %{conn | socket: nil} debug = Utils.sys_debug(debug, :closed, state) purge_timer(timer_ref, :websockex_close_timeout) diff --git a/test/websockex_test.exs b/test/websockex_test.exs index 6548d46..de45bbf 100644 --- a/test/websockex_test.exs +++ b/test/websockex_test.exs @@ -8,6 +8,7 @@ defmodule WebSockexTest do end @basic_server_frame <<1::1, 0::3, 1::4, 0::1, 5::7, "Hello"::utf8>> + @close_frame_with_error <<1::1, 0::3, 8::4, 0::1, 2::7, 1001::16>> defmodule TestClient do use WebSockex @@ -1144,6 +1145,33 @@ defmodule WebSockexTest do TestClient.catch_attr(context.pid, :disconnect, self()) end + test "can handle a ssl socket closing during the close loop", context do + # Close the original socket + WebSockex.cast(context.pid, :close) + assert_receive {:DOWN, _ref, :process, _, :normal} + assert_receive :normal_remote_closed + assert_receive :caught_disconnect + + # Test HTTPS + {:ok, {server_ref, url}} = WebSockex.TestServer.start_https(self()) + on_exit(fn -> WebSockex.TestServer.shutdown(server_ref) end) + + {:ok, pid} = TestClient.start_link(url, %{}) + Process.unlink(pid) + Process.monitor(pid) + TestClient.catch_attr(pid, :disconnect, self()) + + conn = TestClient.get_conn(pid) + + # Ranch/Cowboy closes the tcp connection instead sending an :ssl close + # So we're going to fake closing just the `:ssl` connection + send(pid, {conn.transport, conn.socket, @close_frame_with_error}) + send(pid, {:ssl_closed, conn.socket}) + + assert_receive {:DOWN, _ref, :process, ^pid, {:remote, 1001, ""}} + assert_receive {:caught_disconnect, 1001, ""} + end + test "is not invoked when there is an exception during runtime", context do TestClient.set_attr(context.pid, :reconnect, true) TestClient.catch_attr(context.pid, :terminate, self())