Skip to content

Commit

Permalink
Fixed more issues with disconnect and clean disconnect.
Browse files Browse the repository at this point in the history
- Made a new abort command so disconnect events aren't fired multiple
  times for the same client. When a clean disconnect is triggered, the
  client will send an abort command and the receiving end will only raise
  disconnect for those commands, instead of for the disconnect command.

  The disconnect command signifies an unclean disconnect and therefore
  no other event needs to be raised.

  Remove connections from the list of tracked connections when an abort command
  is received. This prevents the disconnect event to fire again for that
  connection.
- Fixed a nasty issue with the longpolling transport in the javascript client
  sending requests forever when disconnect commands are received.
  • Loading branch information
davidfowl committed May 18, 2012
1 parent 08fe8c2 commit 3e64c0e
Show file tree
Hide file tree
Showing 16 changed files with 111 additions and 28 deletions.
3 changes: 2 additions & 1 deletion SignalR/CommandType.cs
Expand Up @@ -4,6 +4,7 @@ public enum CommandType
{
AddToGroup,
RemoveFromGroup,
Disconnect
Disconnect,
Abort
}
}
5 changes: 5 additions & 0 deletions SignalR/Connection.cs
Expand Up @@ -18,6 +18,7 @@ public class Connection : IConnection, ITransportConnection
private readonly HashSet<string> _groups;
private readonly ITraceManager _trace;
private bool _disconnected;
private bool _aborted;

public Connection(IMessageBus messageBus,
IJsonSerializer jsonSerializer,
Expand Down Expand Up @@ -87,6 +88,7 @@ private PersistentResponse GetResponse(MessageResult result)
MessageId = result.LastMessageId,
Messages = messageValues,
Disconnect = _disconnected,
Aborted = _aborted,
TimedOut = result.TimedOut
};

Expand Down Expand Up @@ -133,6 +135,9 @@ private void ProcessCommand(SignalCommand command)
case CommandType.Disconnect:
_disconnected = true;
break;
case CommandType.Abort:
_aborted = true;
break;
}
}

Expand Down
10 changes: 10 additions & 0 deletions SignalR/Infrastructure/ConnectionExtensions.cs
Expand Up @@ -15,6 +15,16 @@ public static Task Close(this ITransportConnection connection)
return connection.SendCommand(command);
}

public static Task Abort(this ITransportConnection connection)
{
var command = new SignalCommand
{
Type = CommandType.Abort
};

return connection.SendCommand(command);
}

public static Task SendCommand(this IConnection connection, string connectionId, SignalCommand command)
{
return connection.Send(SignalCommand.AddCommandSuffix(connectionId), command);
Expand Down
7 changes: 7 additions & 0 deletions SignalR/PersistentResponse.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace SignalR
{
Expand All @@ -24,6 +25,12 @@ public class PersistentResponse
/// </summary>
public bool Disconnect { get; set; }

/// <summary>
/// True if the connection was forcibly closed.
/// </summary>
[JsonIgnore]
public bool Aborted { get; set; }

/// <summary>
/// True if the connection timed out.
/// </summary>
Expand Down
22 changes: 19 additions & 3 deletions SignalR/Scripts/jquery.signalR.js
Expand Up @@ -422,7 +422,7 @@
log("Disconnect command received from server", connection.logging);

// Disconnected by the server
connection.transport.stop(connection);
connection.stop();
return;
}

Expand Down Expand Up @@ -837,7 +837,9 @@
start: function (connection, onSuccess, onFailed) {
/// <summary>Starts the long polling connection</summary>
/// <param name="connection" type="signalR">The SignalR connection to start</param>
var that = this;
var that = this,
initialConnectFired = false;

if (connection.pollXhr) {
log("Polling xhr requests already exists, aborting.");
connection.stop();
Expand Down Expand Up @@ -865,6 +867,11 @@
var delay = 0,
timedOutReceived = false;

if (initialConnectFired == false) {
onSuccess();
initialConnectFired = true;
}

if (raiseReconnect === true) {
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
Expand All @@ -885,6 +892,10 @@
timedOutReceived = data.TimedOut;
}

if (data && data.Disconnect) {
return;
}

if (delay > 0) {
window.setTimeout(function () {
poll(instance, timedOutReceived);
Expand Down Expand Up @@ -931,7 +942,12 @@
// Now connected
// There's no good way know when the long poll has actually started so
// we assume it only takes around 150ms (max) to start the connection
window.setTimeout(onSuccess, 150);
window.setTimeout(function () {
if (initialConnectFired === false) {
onSuccess();
initialConnectFired = true;
}
}, 150);

}, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
},
Expand Down
2 changes: 1 addition & 1 deletion SignalR/Scripts/jquery.signalR.min.js

Large diffs are not rendered by default.

12 changes: 4 additions & 8 deletions SignalR/Transports/ForeverTransport.cs
Expand Up @@ -87,7 +87,7 @@ protected Task ProcessRequestCore(ITransportConnection connection)
}
else if (IsAbortRequest)
{
return Connection.Close();
return Connection.Abort();
}
else
{
Expand Down Expand Up @@ -201,10 +201,11 @@ private void ProcessMessagesImpl(TaskCompletionSource<object> taskCompletetionSo
// If the response has the Disconnect flag, just send the response and exit the loop,
// the server thinks connection is gone. Otherwse, send the response then re-enter the loop
Task sendTask = Send(response);
if (response.Disconnect || response.TimedOut)
if (response.Disconnect || response.TimedOut || response.Aborted)
{
if (response.Disconnect)
if (response.Aborted)
{
// If this was a clean disconnect raise the event.
OnDisconnect();
}
Expand Down Expand Up @@ -232,11 +233,6 @@ private void ProcessMessagesImpl(TaskCompletionSource<object> taskCompletetionSo
return;
}

if (!IsTimedOut)
{
OnDisconnect();
}

taskCompletetionSource.SetResult(null);
return;
}
Expand Down
6 changes: 6 additions & 0 deletions SignalR/Transports/ITransportHeartBeat.cs
Expand Up @@ -22,5 +22,11 @@ public interface ITransportHeartBeat
/// </summary>
/// <param name="connection">The connection to mark.</param>
void MarkConnection(ITrackingConnection connection);

/// <summary>
/// Removes a connection from the list of tracked connections.
/// </summary>
/// <param name="connection">The connection to remove.</param>
void RemoveConnection(ITrackingConnection connection);
}
}
5 changes: 3 additions & 2 deletions SignalR/Transports/LongPollingTransport.cs
Expand Up @@ -112,7 +112,7 @@ public Task ProcessRequest(ITransportConnection connection)
}
else if (IsAbortRequest)
{
return Connection.Close();
return Connection.Abort();
}
else
{
Expand Down Expand Up @@ -213,8 +213,9 @@ private Task ProcessReceiveRequest(ITransportConnection connection, Action postR

return receiveTask.Then(response =>
{
if (response.Disconnect)
if (response.Aborted)
{
// If this was a clean disconnect then raise the event
OnDisconnect();
}
Expand Down
4 changes: 4 additions & 0 deletions SignalR/Transports/TransportDisconnectBase.cs
Expand Up @@ -120,6 +120,10 @@ public Task Disconnect()

public Task OnDisconnect()
{
// When a connection is aborted (graceful disconnect) we send a command to it
// telling to to disconnect. At that moment, we raise the disconnect event and
// remove this connection from the heartbeat so we don't end up raising it for the same connection.
HeartBeat.RemoveConnection(this);
if (Interlocked.Exchange(ref _isDisconnected, 1) == 0)
{
var disconnected = Disconnected; // copy before invoking event to avoid race
Expand Down
6 changes: 5 additions & 1 deletion SignalR/Transports/TransportHeartBeat.cs
Expand Up @@ -80,7 +80,11 @@ private void RemoveConnection(string connectionId)
RemoveConnection(new ConnectionReference(connectionId));
}

private void RemoveConnection(ITrackingConnection connection)
/// <summary>
/// Removes a connection from the list of tracked connections.
/// </summary>
/// <param name="connection">The connection to remove.</param>
public void RemoveConnection(ITrackingConnection connection)
{
// Remove the connection and associated metadata
_connections.Remove(connection);
Expand Down
9 changes: 5 additions & 4 deletions samples/SignalR.Hosting.AspNet.Samples/Raw/index.htm
Expand Up @@ -46,11 +46,15 @@
.appendTo($("#messages"));
});

connection.disconnected(function () {
$("#stopStart").val("Start")
.prop("disabled", false);
});

var start = function () {
connection.start({ transport: activeTransport })
.then(function () {
$("#stopStart").prop("disabled", false);
$("#stopStart").val("Stop").prop("disabled", false);
});
};
start();
Expand Down Expand Up @@ -88,11 +92,8 @@
$el.prop("disabled", true);
if ($el.val() === "Stop") {
connection.stop();
$el.val("Start")
.prop("disabled", false);
} else {
start();
$el.val("Stop");
}
});
});
Expand Down
22 changes: 19 additions & 3 deletions samples/SignalR.Hosting.AspNet.Samples/Scripts/jquery.signalR.js
Expand Up @@ -422,7 +422,7 @@
log("Disconnect command received from server", connection.logging);

// Disconnected by the server
connection.transport.stop(connection);
connection.stop();
return;
}

Expand Down Expand Up @@ -837,7 +837,9 @@
start: function (connection, onSuccess, onFailed) {
/// <summary>Starts the long polling connection</summary>
/// <param name="connection" type="signalR">The SignalR connection to start</param>
var that = this;
var that = this,
initialConnectFired = false;

if (connection.pollXhr) {
log("Polling xhr requests already exists, aborting.");
connection.stop();
Expand Down Expand Up @@ -865,6 +867,11 @@
var delay = 0,
timedOutReceived = false;

if (initialConnectFired == false) {
onSuccess();
initialConnectFired = true;
}

if (raiseReconnect === true) {
// Fire the reconnect event if it hasn't been fired as yet
if (reconnectFired === false) {
Expand All @@ -885,6 +892,10 @@
timedOutReceived = data.TimedOut;
}

if (data && data.Disconnect) {
return;
}

if (delay > 0) {
window.setTimeout(function () {
poll(instance, timedOutReceived);
Expand Down Expand Up @@ -931,7 +942,12 @@
// Now connected
// There's no good way know when the long poll has actually started so
// we assume it only takes around 150ms (max) to start the connection
window.setTimeout(onSuccess, 150);
window.setTimeout(function () {
if (initialConnectFired === false) {
onSuccess();
initialConnectFired = true;
}
}, 150);

}, 250); // Have to delay initial poll so Chrome doesn't show loader spinner in tab
},
Expand Down

0 comments on commit 3e64c0e

Please sign in to comment.