diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index bdf07a651..05bd7cfda 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -84,7 +84,7 @@ class SignalRConnector extends Component { constructor(props, context) { super(props, context); - this.signalRconnectionOptions = { transport: ['webSockets', 'longPolling'] }; + this.signalRconnectionOptions = { transport: ['webSockets', 'serverSentEvents', 'longPolling'] }; this.signalRconnection = null; this.retryInterval = 1; this.retryTimeoutId = null; diff --git a/src/Lidarr.Http/LidarrRestModuleWithSignalR.cs b/src/Lidarr.Http/LidarrRestModuleWithSignalR.cs index 6ac1a41e5..5905930d3 100644 --- a/src/Lidarr.Http/LidarrRestModuleWithSignalR.cs +++ b/src/Lidarr.Http/LidarrRestModuleWithSignalR.cs @@ -25,6 +25,8 @@ namespace Lidarr.Http public void Handle(ModelEvent message) { + if (!_signalRBroadcaster.IsConnected) return; + if (message.Action == ModelAction.Deleted || message.Action == ModelAction.Sync) { BroadcastResourceChange(message.Action); @@ -35,6 +37,8 @@ namespace Lidarr.Http protected void BroadcastResourceChange(ModelAction action, int id) { + if (!_signalRBroadcaster.IsConnected) return; + if (action == ModelAction.Deleted) { BroadcastResourceChange(action, new TResource { Id = id }); @@ -48,6 +52,8 @@ namespace Lidarr.Http protected void BroadcastResourceChange(ModelAction action, TResource resource) { + if (!_signalRBroadcaster.IsConnected) return; + if (GetType().Namespace.Contains("V1")) { var signalRMessage = new SignalRMessage @@ -64,6 +70,8 @@ namespace Lidarr.Http protected void BroadcastResourceChange(ModelAction action) { + if (!_signalRBroadcaster.IsConnected) return; + if (GetType().Namespace.Contains("V1")) { var signalRMessage = new SignalRMessage diff --git a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs b/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs index 8dd65a82c..1b3e3e2d5 100644 --- a/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs +++ b/src/NzbDrone.Host/Owin/MiddleWare/SignalRMiddleWare.cs @@ -16,9 +16,14 @@ namespace NzbDrone.Host.Owin.MiddleWare SignalRDependencyResolver.Register(container); SignalRJsonSerializer.Register(); - // Half the default time (110s) to get under nginx's default 60 proxy_read_timeout - GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(55); - GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromMinutes(3); + // Note there are some important timeouts involved here: + // nginx has a default 60 sec proxy_read_timeout, this means the connection will be terminated if the server doesn't send anything within that time. + // Previously we lowered the ConnectionTimeout from 110s to 55s to remedy that, however all we should've done is set an appropriate KeepAlive. + // By default KeepAlive is 1/3rd of the DisconnectTimeout, which we set incredibly high 5 years ago, resulting in KeepAlive being 1 minute. + // So when adjusting these values in the future, please keep that all in mind. + GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110); + GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(180); + GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(30); } public void Attach(IAppBuilder appBuilder) diff --git a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs index ff1a2dca8..ef9ed0332 100644 --- a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs +++ b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Infrastructure; @@ -12,6 +12,7 @@ namespace NzbDrone.SignalR { public interface IBroadcastSignalRMessage { + bool IsConnected { get; } void BroadcastMessage(SignalRMessage message); } @@ -20,7 +21,8 @@ namespace NzbDrone.SignalR private IPersistentConnectionContext Context => ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); private static string API_KEY; - private readonly Dictionary _messageHistory; + private readonly Dictionary _messageHistory; + private HashSet _connections = new HashSet(); public NzbDronePersistentConnection(IConfigFileProvider configFileProvider) { @@ -28,6 +30,16 @@ namespace NzbDrone.SignalR _messageHistory = new Dictionary(); } + public bool IsConnected + { + get + { + lock (_connections) + { + return _connections.Count != 0; + } + } + } public void BroadcastMessage(SignalRMessage message) { @@ -59,14 +71,34 @@ namespace NzbDrone.SignalR protected override Task OnConnected(IRequest request, string connectionId) { + lock (_connections) + { + _connections.Add(connectionId); + } + return SendVersion(connectionId); } protected override Task OnReconnected(IRequest request, string connectionId) { + lock (_connections) + { + _connections.Add(connectionId); + } + return SendVersion(connectionId); } + protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) + { + lock (_connections) + { + _connections.Remove(connectionId); + } + + return base.OnDisconnected(request, connectionId, stopCalled); + } + private Task SendVersion(string connectionId) { return Context.Connection.Send(connectionId, new SignalRMessage @@ -79,4 +111,4 @@ namespace NzbDrone.SignalR }); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.SignalR/SignalRJsonSerializer.cs b/src/NzbDrone.SignalR/SignalRJsonSerializer.cs index 031a03f89..c6603ebc7 100644 --- a/src/NzbDrone.SignalR/SignalRJsonSerializer.cs +++ b/src/NzbDrone.SignalR/SignalRJsonSerializer.cs @@ -13,6 +13,7 @@ namespace NzbDrone.SignalR { _serializerSettings = Json.GetSerializerSettings(); _serializerSettings.ContractResolver = new SignalRContractResolver(); + _serializerSettings.Formatting = Formatting.None; // ServerSentEvents doesn't like newlines _serializer = JsonSerializer.Create(_serializerSettings);