Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixing the timeout events on transports.

We fix the timer issues having to do with erlang BIF timers conflicting
with the connection timeouts occurring after a closed socket.

Any client whose connection is closed at the point where the 'dead
connection timeout' is longer than the polling/heartbeat timer's time
left will have conflicting timers where the polling/heartbeat resets the
dead connection timeout.

Given the delays between the 'dead connection timeout' and the regular
polling/heartbeat timeout can be very close (and are set that way by
default), this conflict happens fairly often.

Because we are not exactly sure what makes it valid to cancel the dead
connection timer, this fix makes it so both timers are handled the same
way, replicating the old behaviour present before the recent fixes to
the timers when it comes to these connections.
  • Loading branch information...
commit 4d5a1235ba18c68fb99bd1b23b8f3a626663ecd7 1 parent a915b18
@ferd ferd authored
View
19 src/socketio_transport_htmlfile.erl
@@ -190,14 +190,33 @@ handle_cast(_, #state{} = State) ->
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
+%% CLient disconnected. We fire a timer (ServerTimeout)!
handle_info({'EXIT',_Port,_Reason}, #state{ close_timeout = ServerTimeout} = State) when is_port(_Port) ->
{noreply, State#state { connection_reference = {'htmlfile', none}}, ServerTimeout};
+%% This branch handles two purposes: 1. handling the close_timeout,
+%% 2. handling the heartbeat timeout that might comes first. The thing is,
+%% when the client connection dies, we need to wait for the close_timeout
+%% to be fired. That one can be cancelled at any time by God knows what for now,
+%% and that might be desirable. However, it can also be cancelled because we
+%% happen to receive the heartbeat timeout. Given the right setting, this will
+%% happen every time a disconnection happens at the point where the delay left to
+%% the current heartbeat is longer than the delay left to the total value of
+%% close_timout. Funny, eh?
+%% For this reason, the heartbeat timeout when we have no htmlfile
+%% connection reference has to be seen as the same as the close_timeout
+%% timer firing off. This is the safest way to guarantee everything will run
+%% okay.
handle_info(timeout, #state{ server_module = ServerModule,
connection_reference = {'htmlfile', none}, caller = Caller, req = Req } = State) ->
gen_server:reply(Caller, ServerModule:respond(Req, 200)),
{stop, shutdown, State};
+%% See previous clauses' comments
+handle_info({timeout, _Ref, heartbeat}, #state{ connection_reference = {'htmlfile', none} } = State) ->
+ handle_info(timeout, State);
+
+%% Regular heartbeat
handle_info({timeout, _Ref, heartbeat}, State) ->
gen_server:cast(self(), heartbeat),
{noreply, State};
View
12 src/socketio_transport_polling.erl
@@ -205,21 +205,29 @@ handle_cast(_, State) ->
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
+%% A client has disconnected. We fire a timer (CloseTimeout)!
handle_info({'EXIT',Port,_Reason}, #state{ connection_reference = {TransportType, _ }, close_timeout = CloseTimeout} = State) when is_port(Port) ->
{noreply, State#state { connection_reference = {TransportType, none}}, CloseTimeout};
-%% Connection has timed out
+%% Connections has timed out, but is technically still active. This is like a
+%% heartbeat, but for polling connections.
handle_info({timeout, _Ref, polling}, #state{ server_module = ServerModule,
connection_reference = {_TransportType, connected},
caller = Caller, req = Req, index = Index, sup = Sup } = State) ->
gen_server:reply(Caller, send_message("", Req, Index, ServerModule, Sup)),
{noreply, State};
-%% Client has timed out
+%% Client has timed out, no active connection found. (connection_reference = none)
handle_info(timeout, #state{ server_module = ServerModule, caller = Caller, req = Req } = State) ->
gen_server:reply(Caller, apply(ServerModule, respond, [Req, 200,""])),
{stop, shutdown, State};
+%% client has timed out, no active connection found, but the normal close_timeout
+%% is being interrupted by the polling timeout timer interacting in here.
+%% We defer to the preceding clause.
+handle_info({timeout, _Ref, polling}, #state{ } = State) ->
+ handle_info(timeout, State);
+
handle_info(_Info, State) ->
{noreply, State}.
View
19 src/socketio_transport_xhr_multipart.erl
@@ -206,14 +206,33 @@ handle_cast(_, #state{} = State) ->
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
+%% A client has disconnected. We fire a timer (ServerTimeout)!
handle_info({'EXIT',_Port,_Reason}, #state{ close_timeout = ServerTimeout} = State) when is_port(_Port) ->
{noreply, State#state { connection_reference = {'xhr-multipart', none}}, ServerTimeout};
+%% This branch handles two purposes: 1. handling the close_timeout,
+%% 2. handling the heartbeat timeout that might comes first. The thing is,
+%% when the client connection dies, we need to wait for the close_timeout
+%% to be fired. That one can be cancelled at any time by God knows what for now,
+%% and that might be desirable. However, it can also be cancelled because we
+%% happen to receive the heartbeat timeout. Given the right setting, this will
+%% happen every time a disconnection happens at the point where the delay left to
+%% the current heartbeat is longer than the delay left to the total value of
+%% close_timout. Funny, eh?
+%% For this reason, the heartbeat timeout when we have no xhr-multipart
+%% connection reference has to be seen as the same as the close_timeout
+%% timer firing off. This is the safest way to guarantee everything will run
+%% okay.
handle_info(timeout, #state{ server_module = ServerModule,
connection_reference = {'xhr-multipart', none}, req = Req, caller = Caller } = State) ->
gen_server:reply(Caller, ServerModule:respond(Req, 200, "")),
{stop, shutdown, State};
+%% See the previous clauses' comments, please.
+handle_info({timeout, _Ref, heartbeat}, #state{ connection_reference = {'xhr-multipart', none} } = State) ->
+ handle_info(timeout, State);
+
+%% Good old regular heartbeat. I admire your simplicity.
handle_info({timeout, _Ref, heartbeat}, State) ->
gen_server:cast(self(), heartbeat),
{noreply, State};
Please sign in to comment.
Something went wrong with that request. Please try again.