Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connection pool capabilities #73

Merged
merged 10 commits into from
Oct 2, 2013
35 changes: 34 additions & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,49 @@

* Add support for Query.HasFields to check if a single record has non-null field values, or to filter a query based upon records having non-null fields. [PR #139](https://github.com/mfenniak/rethinkdb-net/pull/139)

* Added two new forms of connection pooling; one which reduces the need to disconnect and reconnect all the time by using persistent connections, and another which monitors for network disconnects and can retry queries against a new network connection. [PR #73](https://github.com/mfenniak/rethinkdb-net/issues/73)

* Extended configuration to support the new connection pooling modes, as part of [PR #73](https://github.com/mfenniak/rethinkdb-net/issues/73):

```xml
<cluster name="testCluster">
<defaultLogger enabled="true" category="Warning"/>
<connectionPool enabled="true"/>
<networkErrorHandling enabled="true" />
<endpoints>
<endpoint address="127.0.0.1" port="55558"/>
</endpoints>
</cluster>
```

### API Changes

* Changed how connection factories are created from configuration; previously a connection factory called ConfigConnectionFactory existed, but now a static class called the RethinkDb.Configuration.ConfigurationAssembler will create a connection factory for a specified cluster from the current configuration file. Refactoring done as part of [pull request #73](https://github.com/mfenniak/rethinkdb-net/issues/73) for connection pooling. Example code changes:

Previously:

connection = RethinkDb.Configuration.ConfigConnectionFactory.Instance.Get("testCluster");

Now:

IConnectionFactory connectionFactory = ConfigurationAssembler.CreateConnectionFactory("testCluster");
connection = connectionFactory.Get();

* Added IDispoable interface to the IConnection interface. Connections retrieves from a connection factory should be Disposed now, especially is using connection pooling, to return the connections to the pool. Part of [pull request #73](https://github.com/mfenniak/rethinkdb-net/issues/73).

* Moved methods related to the establishment of connections out of IConnection and into a derived interface, IConnectableConnection. This reflects the fact that the connection factories will typically return connected connections. Refactoring done as part of [pull request #73](https://github.com/mfenniak/rethinkdb-net/issues/73) for connection pooling.

* Moved the entire synchronous API, and some redundant default-value style methods, out of IConnection, IConnectableConnection, and IConnectionFactory, and into static extension methods of those interfaces. This makes it easier to create new implementations of these interfaces with less duplicated code. Refactoring done as part of [pull request #73](https://github.com/mfenniak/rethinkdb-net/issues/73) for connection pooling.

* Created a base-interface, IScalarQuery&lt;T&gt;, for IWriteQuery and ISingleObjectQuery, allowing the removal of duplicate methods on IConnection & Connection. Refactoring done as part of [pull request #73](https://github.com/mfenniak/rethinkdb-net/issues/73) for connection pooling.

* Create new namespaces RethinkDb.DatumConverters (for all datum converter) and RethinkDb.Logging (for logging requirements). This cleans up the RethinkDb namespace and simplifies the API for library users. [Issue #141](https://github.com/mfenniak/rethinkdb-net/issues/141)

### Internals

* Better unit test coverage of builtin datum converters. [Issue #60](https://github.com/mfenniak/rethinkdb-net/issues/60), [PR #140](https://github.com/mfenniak/rethinkdb-net/pull/140)



## 0.5.0.0

### Features
Expand Down
4 changes: 4 additions & 0 deletions rethinkdb-net-test/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<rethinkdb>
<clusters>
<cluster name="testCluster">
<!-- <defaultLogger enabled="true" category="Debug"/> -->
<defaultLogger enabled="true" category="Warning"/>
<connectionPool enabled="true"/>
<networkErrorHandling enabled="true" />
<endpoints>
<endpoint address="127.0.0.1" port="55558"/>
</endpoints>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using NSubstitute;
using RethinkDb.ConnectionFactories;

namespace RethinkDb.Test.ConnectionFactories
{
[TestFixture]
public class ConnectionPoolingConnectionFactoryTests
{
private IConnectionFactory rootConnectionFactory;

[SetUp]
public void SetUp()
{
var realConnection1 = Substitute.For<IConnection>();
realConnection1.RunAsync(Arg.Any<IDatumConverterFactory>(), (ISingleObjectQuery<int>)null).Returns(
y => { var x = new TaskCompletionSource<int>(); x.SetResult(1); return x.Task; }
);

var realConnection2 = Substitute.For<IConnection>();
realConnection2.RunAsync(Arg.Any<IDatumConverterFactory>(), (ISingleObjectQuery<int>)null).Returns(
y => { var x = new TaskCompletionSource<int>(); x.SetResult(2); return x.Task; }
);

rootConnectionFactory = Substitute.For<IConnectionFactory>();
rootConnectionFactory.GetAsync().Returns<Task<IConnection>>(
y => { var x = new TaskCompletionSource<IConnection>(); x.SetResult(realConnection1); return x.Task; },
y => { var x = new TaskCompletionSource<IConnection>(); x.SetResult(realConnection2); return x.Task; }
);
}

private void AssertRealConnection1(IConnection conn)
{
Assert.That(conn, Is.Not.Null);
Assert.That(conn.Run((ISingleObjectQuery<int>)null), Is.EqualTo(1));
}

private void AssertRealConnection2(IConnection conn)
{
Assert.That(conn, Is.Not.Null);
Assert.That(conn.Run((ISingleObjectQuery<int>)null), Is.EqualTo(2));
}

[Test]
public void RetrieveSameConnectionOverAndOver()
{
var cf = new ConnectionPoolingConnectionFactory(rootConnectionFactory);

var conn1 = cf.Get();
AssertRealConnection1(conn1);
conn1.Dispose();

conn1 = cf.Get();
AssertRealConnection1(conn1);
conn1.Dispose();

conn1 = cf.Get();
AssertRealConnection1(conn1);
conn1.Dispose();

conn1 = cf.Get();
AssertRealConnection1(conn1);
conn1.Dispose();
}

[Test]
public void SecondConnectionEstablishedIfFirstInUse()
{
var cf = new ConnectionPoolingConnectionFactory(rootConnectionFactory);
var conn1 = cf.Get();
var conn2 = cf.Get();
AssertRealConnection1(conn1);
AssertRealConnection2(conn2);
conn1.Dispose();
conn2.Dispose();
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using NSubstitute;
using RethinkDb.ConnectionFactories;

namespace RethinkDb.Test.ConnectionFactories
{
[TestFixture]
public class ReliableConnectionFactoryTests
{
[SetUp]
public void SetUp()
{
}

private IConnectionFactory CreateRootConnectionFactory(IConnection connection)
{
var rootConnectionFactory = Substitute.For<IConnectionFactory>();
rootConnectionFactory.GetAsync().Returns<Task<IConnection>>(
y => { var x = new TaskCompletionSource<IConnection>(); x.SetResult(connection); return x.Task; }
);
return rootConnectionFactory;
}

private IConnectionFactory CreateRootConnectionFactory(IConnection conn1, IConnection conn2)
{
var rootConnectionFactory = Substitute.For<IConnectionFactory>();
rootConnectionFactory.GetAsync().Returns<Task<IConnection>>(
y => { var x = new TaskCompletionSource<IConnection>(); x.SetResult(conn1); return x.Task; },
y => { var x = new TaskCompletionSource<IConnection>(); x.SetResult(conn2); return x.Task; }
);
return rootConnectionFactory;
}

[Test]
public void GetCallsUnderlyingFactory()
{
var rootConnectionFactory = Substitute.For<IConnectionFactory>();
var cf = new ReliableConnectionFactory(rootConnectionFactory);

cf.Get();

rootConnectionFactory.Received().GetAsync();
}

[Test]
public void DelegateRunISingleObjectQuery()
{
var mockConnection = Substitute.For<IConnection>();
mockConnection.RunAsync(Arg.Any<IDatumConverterFactory>(), (ISingleObjectQuery<int>)null).Returns(
y => { var x = new TaskCompletionSource<int>(); x.SetResult(1); return x.Task; }
);

var rootConnectionFactory = CreateRootConnectionFactory(mockConnection);
var cf = new ReliableConnectionFactory(rootConnectionFactory);

var conn = cf.Get();
Assert.That(conn, Is.Not.Null);
Assert.That(conn.Run((ISingleObjectQuery<int>)null), Is.EqualTo(1));
conn.Dispose();
}

[Test]
public void RetryRunISingleObjectQuery()
{
var errorConnection = Substitute.For<IConnection>();
errorConnection
.RunAsync(Arg.Any<IDatumConverterFactory>(), (ISingleObjectQuery<int>)null)
.Returns(x => { throw new RethinkDbNetworkException("!"); });

var successConnection = Substitute.For<IConnection>();
successConnection
.RunAsync(Arg.Any<IDatumConverterFactory>(), (ISingleObjectQuery<int>)null)
.Returns(y => { var x = new TaskCompletionSource<int>(); x.SetResult(1); return x.Task; });

var rootConnectionFactory = CreateRootConnectionFactory(errorConnection, successConnection);
var cf = new ReliableConnectionFactory(rootConnectionFactory);

var conn = cf.Get();
Assert.That(conn, Is.Not.Null);
Assert.That(conn.Run((ISingleObjectQuery<int>)null), Is.EqualTo(1));
conn.Dispose();

// Error connection was attempted...
errorConnection.Received().RunAsync(Arg.Any<IDatumConverterFactory>(), (ISingleObjectQuery<int>)null);
// Then another connection was attempted after the error failed.
successConnection.Received().RunAsync(Arg.Any<IDatumConverterFactory>(), (ISingleObjectQuery<int>)null);
}
}
}

10 changes: 5 additions & 5 deletions rethinkdb-net-test/Integration/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace RethinkDb.Test.Integration
{
public class TestBase
{
private static IConnectionFactory connectionFactory = ConfigurationAssembler.CreateConnectionFactory("testCluster");

protected IConnection connection;

[TestFixtureSetUp]
Expand All @@ -29,10 +31,7 @@ public virtual void TestFixtureSetUp()

private async Task DoTestFixtureSetUp()
{
connection = ConfigConnectionFactory.Instance.Get("testCluster");
connection.Logger = new DefaultLogger(LoggingCategory.Warning, Console.Out);

await connection.ConnectAsync();
connection = await connectionFactory.GetAsync();

try
{
Expand All @@ -48,7 +47,8 @@ private async Task DoTestFixtureSetUp()
[TestFixtureTearDown]
public virtual void TestFixtureTearDown()
{
connection.Dispose();
connection = null;
}
}
}

3 changes: 3 additions & 0 deletions rethinkdb-net-test/rethinkdb-net-test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
<Compile Include="DatumConverters\AggregateDatumConverterTests.cs" />
<Compile Include="DatumConverters\AnonymousTypeDatumConverterTests.cs" />
<Compile Include="DatumHelpers.cs" />
<Compile Include="ConnectionFactories\ConnectionPoolingConnectionFactoryTests.cs" />
<Compile Include="ConnectionFactories\ReliableConnectionFactoryTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
Expand All @@ -129,5 +131,6 @@
<Folder Include="Integration\" />
<Folder Include="QueryTests\" />
<Folder Include="Integration\Documentation\" />
<Folder Include="ConnectionFactories\" />
</ItemGroup>
</Project>
27 changes: 27 additions & 0 deletions rethinkdb-net/Configuration/ClusterElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,32 @@ public EndPointElementCollection EndPoints
return this["endpoints"] as EndPointElementCollection;
}
}

[ConfigurationProperty("connectionPool")]
public ConnectionPoolElement ConnectionPool
{
get
{
return this["connectionPool"] as ConnectionPoolElement;
}
}

[ConfigurationProperty("networkErrorHandling")]
public NetworkErrorHandlingElement NetworkErrorHandling
{
get
{
return this["networkErrorHandling"] as NetworkErrorHandlingElement;
}
}

[ConfigurationProperty("defaultLogger")]
public DefaultLoggerElement DefaultLogger
{
get
{
return this["defaultLogger"] as DefaultLoggerElement;
}
}
}
}
48 changes: 0 additions & 48 deletions rethinkdb-net/Configuration/ConfigConnectionFactory.cs

This file was deleted.

Loading