Skip to content
Browse files

Merge pull request #145 from sdether/servermanager_fix

Fixing Single server manager so that failure does not cause a reconnect spin-loop
  • Loading branch information...
2 parents 88bcc74 + 8b29440 commit 343fe42ca2c2c99f8ac83868c3c52451be45412a @Aaronontheweb Aaronontheweb committed
View
64 src/Connections/InfallibleSingleServerManager.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace FluentCassandra.Connections
+{
+ public class InfallibleSingleServerManager : IServerManager
+ {
+ private Server _server;
+
+ public InfallibleSingleServerManager(IConnectionBuilder builder)
+ {
+ _server = builder.Servers[0];
+ }
+
+ #region IServerManager Members
+
+ public bool HasNext
+ {
+ get { return true; }
+ }
+
+ public Server Next()
+ {
+ return _server;
+ }
+
+ public void ErrorOccurred(Server server, Exception exc = null)
+ {
+ Debug.WriteLineIf(exc != null, exc, "connection");
+ }
+
+ public void Add(Server server)
+ {
+ _server = server;
+ }
+
+ public void Remove(Server server)
+ {
+ throw new NotSupportedException("You cannot remove a server since SingleServerManager supports one server. Call the Add method to change the server.");
+ }
+
+ #endregion
+
+ #region IEnumerable<Server> Members
+
+ public IEnumerator<Server> GetEnumerator()
+ {
+ throw new NotImplementedException("SingleServerManager does not implement Enumerable(server)");
+ }
+
+ #endregion
+
+ #region IEnumerable Members
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+
+ }
+}
View
37 src/Connections/ServerManagerFactory.cs
@@ -1,33 +1,48 @@
+using System;
using System.Collections.Generic;
namespace FluentCassandra.Connections
{
public static class ServerManagerFactory
{
- private static readonly object Lock = new object();
- private static volatile IDictionary<string, IServerManager> Managers = new Dictionary<string, IServerManager>();
+ private static readonly object LOCK = new object();
+ private static volatile IDictionary<string, IServerManager> _managers = new Dictionary<string, IServerManager>();
+ private static volatile Func<IConnectionBuilder, IServerManager> _alternateManagerCreator;
public static IServerManager Get(IConnectionBuilder connectionBuilder)
{
- lock (Lock) {
+ lock(LOCK)
+ {
IServerManager manager;
-
- if (!Managers.TryGetValue(connectionBuilder.Uuid, out manager)) {
+
+ if(!_managers.TryGetValue(connectionBuilder.Uuid, out manager))
+ {
manager = CreateManager(connectionBuilder);
- Managers.Add(connectionBuilder.Uuid, manager);
+ _managers.Add(connectionBuilder.Uuid, manager);
}
return manager;
}
}
- private static IServerManager CreateManager(IConnectionBuilder builder)
+ public static void SetAlternateManagerCreationCallback(Func<IConnectionBuilder, IServerManager> alternateManagerCreator)
{
- if (builder.Servers.Count == 1) {
- return new SingleServerManager(builder);
- } else {
- return new RoundRobinServerManager(builder);
+ lock(LOCK)
+ _alternateManagerCreator = alternateManagerCreator;
+ }
+
+ private static IServerManager CreateManager(IConnectionBuilder builder)
+ {
+ if(_alternateManagerCreator != null)
+ {
+ var manager = _alternateManagerCreator(builder);
+ if(manager != null)
+ return manager;
}
+
+ return builder.Servers.Count == 1
+ ? (IServerManager)new SingleServerManager(builder)
+ : new RoundRobinServerManager(builder);
}
}
}
View
50 src/Connections/SingleServerManager.cs
@@ -1,39 +1,81 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Threading;
namespace FluentCassandra.Connections
{
public class SingleServerManager : IServerManager
{
private readonly object _lock = new object();
+ private readonly Timer _recoveryTimer;
+ private readonly long _recoveryTimerInterval;
private Server _server;
+ private bool _failed;
+
public SingleServerManager(IConnectionBuilder builder)
{
- _server = builder.Servers[0];
+ _server = builder.Servers[0];
+ _recoveryTimerInterval = (long)builder.ServerPollingInterval.TotalMilliseconds;
+ _recoveryTimer = new Timer(ServerRecover);
+ }
+
+ private void ServerRecover(object unused)
+ {
+ lock(_lock)
+ {
+ if(!_failed)
+ return;
+
+ var connection = new Connection(_server, ConnectionType.Simple, 1024);
+
+ try
+ {
+ connection.Open();
+ _failed = false;
+ }
+ catch { }
+ finally
+ {
+ connection.Close();
+ }
+ }
}
#region IServerManager Members
public bool HasNext
{
- get { return true; }
+ get { return !_failed; }
}
public Server Next()
{
- return _server;
+ return _failed ? null : _server;
}
public void ErrorOccurred(Server server, Exception exc = null)
{
Debug.WriteLineIf(exc != null, exc, "connection");
+ lock(_lock)
+ {
+ if(_failed)
+ return;
+
+ _failed = true;
+ _recoveryTimer.Change(_recoveryTimerInterval, Timeout.Infinite);
+ }
}
public void Add(Server server)
{
- _server = server;
+ lock(_lock)
+ {
+ _server = server;
+ _failed = false;
+ _recoveryTimer.Change(Timeout.Infinite,Timeout.Infinite);
+ }
}
public void Remove(Server server)
View
1 src/FluentCassandra.csproj
@@ -108,6 +108,7 @@
<Compile Include="CassandraTokenRange.cs" />
<Compile Include="Connections\CassandraConnectionException.cs" />
<Compile Include="Connections\LockTimeoutException.cs" />
+ <Compile Include="Connections\InfallibleSingleServerManager.cs" />
<Compile Include="Connections\TSocketWithConnectTimeout.cs" />
<Compile Include="Connections\Connection.cs" />
<Compile Include="Connections\ConnectionBuilder.cs" />
View
27 test/FluentCassandra.Tests/Connections/InfallibleSingleServerManagerTests.cs
@@ -0,0 +1,27 @@
+using System;
+using Xunit;
+
+namespace FluentCassandra.Connections
+{
+ public class InfallibleSingleServerManagerTests
+ {
+ [Fact]
+ public void CanGetServerAfterError()
+ {
+ var target = new InfallibleSingleServerManager(new ConnectionBuilder("Server=unit-test-1"));
+
+ var original = target.Next();
+
+ for (int i = 0; i < 10; i++)
+ {
+ Assert.True(target.HasNext, "InfallibleSingleServerManager should always have another server available.");
+
+ Server next = target.Next();
+ Assert.True(original.ToString().Equals(next.ToString(), StringComparison.OrdinalIgnoreCase), "InfallibleSingleServerManager always returns the same server.");
+
+ //mark the server as failing to set up the next test iteration.
+ target.ErrorOccurred(next);
+ }
+ }
+ }
+}
View
92 test/FluentCassandra.Tests/Connections/ServerManagerFactoryTests.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Xunit;
+
+namespace FluentCassandra.Connections
+{
+ public class ServerManagerFactoryTests
+ {
+
+ // Make sure these tests never run in parallel (in case someone uses a parallel test runner),
+ // since they both manipulate global state of a singleton
+ private static readonly object _lock = new object();
+
+ [Fact]
+ public void Can_insert_a_server_manager_creator()
+ {
+ lock(_lock)
+ {
+ try
+ {
+ ServerManagerFactory.SetAlternateManagerCreationCallback(b => new StubServerManager());
+ var manager = ServerManagerFactory.Get(new ConnectionBuilder("Server=unit-test-1111"));
+
+ Assert.IsType<StubServerManager>(manager);
+ }
+ finally
+ {
+ ServerManagerFactory.SetAlternateManagerCreationCallback(null);
+ }
+ }
+ }
+
+ [Fact]
+ public void Manager_creator_can_be_optional()
+ {
+ lock(_lock)
+ {
+ try
+ {
+ ServerManagerFactory.SetAlternateManagerCreationCallback(
+ b => b.Servers[0].Host == "unit-test-2222b" ? new StubServerManager() : null
+ );
+ var manager1 = ServerManagerFactory.Get(new ConnectionBuilder("Server=unit-test-2222a"));
+ var manager2 = ServerManagerFactory.Get(new ConnectionBuilder("Server=unit-test-2222b"));
+
+ Assert.IsType<SingleServerManager>(manager1);
+ Assert.IsType<StubServerManager>(manager2);
+ }
+ finally
+ {
+ ServerManagerFactory.SetAlternateManagerCreationCallback(null);
+ }
+ }
+ }
+
+ public class StubServerManager : IServerManager
+ {
+ public IEnumerator<Server> GetEnumerator()
+ {
+ throw new NotImplementedException();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public bool HasNext { get; private set; }
+
+ public Server Next()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ErrorOccurred(Server server, Exception exc = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Add(Server server)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Remove(Server server)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
View
26 test/FluentCassandra.Tests/Connections/SingleServerManagerTests.cs
@@ -1,27 +1,23 @@
using System;
using Xunit;
-namespace FluentCassandra.Connections
+namespace FluentCassandra.Connections
{
+
public class SingleServerManagerTests
{
+
[Fact]
- public void CanGetServerAfterError()
+ public void HasNextIsFalseAfterServerFailure()
{
- SingleServerManager target = new SingleServerManager(new ConnectionBuilder("Server=unit-test-1"));
-
- Server original = target.Next();
- for (int i = 0; i < 10; i++)
- {
- Assert.True(target.HasNext, "SingleServerManager should always have another server available.");
-
- Server next = target.Next();
- Assert.True(original.ToString().Equals(next.ToString(), StringComparison.OrdinalIgnoreCase), "SingleServerManager always returns the same server.");
+ var manager = new SingleServerManager(new ConnectionBuilder("Server=unit-test-1"));
+
+ Assert.True(manager.HasNext, "SingleServerManager was not initialized with a server");
+ var server = manager.Next();
+ manager.ErrorOccurred(server,new Exception());
- //mark the server as failing to set up the next test iteration.
- target.ErrorOccurred(next);
- }
+ Assert.False(manager.HasNext, "SingleServerManager still has a server after its failure");
}
}
-}
+}
View
4 test/FluentCassandra.Tests/FluentCassandra.Tests.csproj
@@ -49,8 +49,10 @@
<Compile Include="BigDecimalTest.cs" />
<Compile Include="Bugs\Issue28GuidGeneratorInParallelContext.cs" />
<Compile Include="Bugs\Issue65ServerTimeoutLost.cs" />
- <Compile Include="Connections\SingleServerManagerTests.cs" />
+ <Compile Include="Connections\InfallibleSingleServerManagerTests.cs" />
<Compile Include="Connections\RoundRobinServerManagerTests.cs" />
+ <Compile Include="Connections\ServerManagerFactoryTests.cs" />
+ <Compile Include="Connections\SingleServerManagerTests.cs" />
<Compile Include="CqlHelperTest.cs" />
<Compile Include="Helper.cs" />
<Compile Include="Operations\CassandraIndexClauseTest.cs" />

0 comments on commit 343fe42

Please sign in to comment.
Something went wrong with that request. Please try again.