diff --git a/src/EventStore.ClientAPI.NetCore/Common/Log/DebugLogger.cs b/src/EventStore.ClientAPI.NetCore/Common/Log/DebugLogger.cs
index 91c03dd..d3a803e 100644
--- a/src/EventStore.ClientAPI.NetCore/Common/Log/DebugLogger.cs
+++ b/src/EventStore.ClientAPI.NetCore/Common/Log/DebugLogger.cs
@@ -6,32 +6,32 @@ class DebugLogger : ILogger
{
public void Error(string format, params object[] args)
{
- System.Diagnostics.Debug.WriteLine(Log("ERROR", format, args));
+ System.Diagnostics.Trace.WriteLine(Log("ERROR", format, args));
}
public void Error(Exception ex, string format, params object[] args)
{
- System.Diagnostics.Debug.WriteLine(Log("ERROR", ex, format, args));
+ System.Diagnostics.Trace.WriteLine(Log("ERROR", ex, format, args));
}
public void Debug(string format, params object[] args)
{
- System.Diagnostics.Debug.WriteLine(Log("DEBUG", format, args));
+ System.Diagnostics.Trace.WriteLine(Log("DEBUG", format, args));
}
public void Debug(Exception ex, string format, params object[] args)
{
- System.Diagnostics.Debug.WriteLine(Log("DEBUG", ex, format, args));
+ System.Diagnostics.Trace.WriteLine(Log("DEBUG", ex, format, args));
}
public void Info(string format, params object[] args)
{
- System.Diagnostics.Debug.WriteLine(Log("INFO", format, args));
+ System.Diagnostics.Trace.WriteLine(Log("INFO", format, args));
}
public void Info(Exception ex, string format, params object[] args)
{
- System.Diagnostics.Debug.WriteLine(Log("INFO", ex, format, args));
+ System.Diagnostics.Trace.WriteLine(Log("INFO", ex, format, args));
}
diff --git a/src/EventStore.ClientAPI.NetCore/ConnectionSettings.cs b/src/EventStore.ClientAPI.NetCore/ConnectionSettings.cs
index 5803a8e..5397411 100644
--- a/src/EventStore.ClientAPI.NetCore/ConnectionSettings.cs
+++ b/src/EventStore.ClientAPI.NetCore/ConnectionSettings.cs
@@ -61,6 +61,10 @@ public static ConnectionSettingsBuilder Create()
///
public readonly TimeSpan ReconnectionDelay;
///
+ /// The amount of time a request for an operation is permitted to be queued awaiting transmission to the server.
+ ///
+ public readonly TimeSpan QueueTimeout;
+ ///
/// The amount of time before an operation is considered to have timed out.
///
public readonly TimeSpan OperationTimeout;
@@ -139,6 +143,7 @@ internal ConnectionSettings(ILogger log,
int maxReconnections,
bool requireMaster,
TimeSpan reconnectionDelay,
+ TimeSpan queueTimeout,
TimeSpan operationTimeout,
TimeSpan operationTimeoutCheckPeriod,
UserCredentials defaultUserCredentials,
@@ -173,6 +178,7 @@ internal ConnectionSettings(ILogger log,
MaxReconnections = maxReconnections;
RequireMaster = requireMaster;
ReconnectionDelay = reconnectionDelay;
+ QueueTimeout = queueTimeout;
OperationTimeout = operationTimeout;
OperationTimeoutCheckPeriod = operationTimeoutCheckPeriod;
ClientConnectionTimeout = clientConnectionTimeout;
diff --git a/src/EventStore.ClientAPI.NetCore/ConnectionSettingsBuilder.cs b/src/EventStore.ClientAPI.NetCore/ConnectionSettingsBuilder.cs
index b95ea42..ed8ef6f 100644
--- a/src/EventStore.ClientAPI.NetCore/ConnectionSettingsBuilder.cs
+++ b/src/EventStore.ClientAPI.NetCore/ConnectionSettingsBuilder.cs
@@ -23,6 +23,7 @@ public class ConnectionSettingsBuilder
private bool _requireMaster = Consts.DefaultRequireMaster;
private TimeSpan _reconnectionDelay = Consts.DefaultReconnectionDelay;
+ private TimeSpan _queueTimeout = Consts.DefaultQueueTimeout;
private TimeSpan _operationTimeout = Consts.DefaultOperationTimeout;
private TimeSpan _operationTimeoutCheckPeriod = Consts.DefaultOperationTimeoutCheckPeriod;
@@ -214,6 +215,17 @@ public ConnectionSettingsBuilder SetReconnectionDelayTo(TimeSpan reconnectionDel
return this;
}
+ ///
+ /// Sets the maximum permitted time a request may be queued awaiting transmission; if exceeded an is thrown.
+ ///
+ ///
+ ///
+ public ConnectionSettingsBuilder SetQueueTimeoutTo(TimeSpan queueTimeout)
+ {
+ _queueTimeout = queueTimeout;
+ return this;
+ }
+
///
/// Sets the operation timeout duration.
///
@@ -449,6 +461,7 @@ public ConnectionSettings Build()
_maxReconnections,
_requireMaster,
_reconnectionDelay,
+ _queueTimeout,
_operationTimeout,
_operationTimeoutCheckPeriod,
_defaultUserCredentials,
diff --git a/src/EventStore.ClientAPI.NetCore/DnsClusterSettingsBuilder.cs b/src/EventStore.ClientAPI.NetCore/DnsClusterSettingsBuilder.cs
index 837967d..9c0c2a1 100644
--- a/src/EventStore.ClientAPI.NetCore/DnsClusterSettingsBuilder.cs
+++ b/src/EventStore.ClientAPI.NetCore/DnsClusterSettingsBuilder.cs
@@ -54,6 +54,16 @@ public DnsClusterSettingsBuilder SetGossipTimeout(TimeSpan timeout)
return this;
}
+ ///
+ /// Allows infinite nodes discovery attempts.
+ ///
+ /// A for further configuration.
+ public DnsClusterSettingsBuilder KeepDiscovering()
+ {
+ _maxDiscoverAttempts = Int32.MaxValue;
+ return this;
+ }
+
///
/// Whether to randomly choose a node that's alive from the known nodes.
///
diff --git a/src/EventStore.ClientAPI.NetCore/EventStoreCatchUpSubscription.cs b/src/EventStore.ClientAPI.NetCore/EventStoreCatchUpSubscription.cs
index d5fb49d..98fac26 100644
--- a/src/EventStore.ClientAPI.NetCore/EventStoreCatchUpSubscription.cs
+++ b/src/EventStore.ClientAPI.NetCore/EventStoreCatchUpSubscription.cs
@@ -455,7 +455,7 @@ private async Task ProcessEventsAsync(long? lastCommitPosition, AllEventsS
: slice.NextPosition >= new Position(lastCommitPosition.Value, lastCommitPosition.Value);
if (!done && slice.IsEndOfStream)
- Thread.Sleep(1); // we are waiting for server to flush its data
+ await Task.Delay(1).ConfigureAwait(false); // we are waiting for server to flush its data
return done;
}
@@ -595,7 +595,7 @@ private async Task ProcessEventsAsync(long? lastEventNumber, StreamEventsS
}
if (!done && slice.IsEndOfStream)
- Thread.Sleep(1); // we are waiting for server to flush its data
+ await Task.Delay(1).ConfigureAwait(false); // we are waiting for server to flush its data
return done;
}
diff --git a/src/EventStore.ClientAPI.NetCore/EventStoreConnection.cs b/src/EventStore.ClientAPI.NetCore/EventStoreConnection.cs
index 85a3855..342e3d9 100644
--- a/src/EventStore.ClientAPI.NetCore/EventStoreConnection.cs
+++ b/src/EventStore.ClientAPI.NetCore/EventStoreConnection.cs
@@ -4,7 +4,6 @@
using EventStore.ClientAPI.Common.Utils;
using EventStore.ClientAPI.Internal;
using System;
-using EventStore.ClientApi.Core.Internal;
using EventStore.ClientAPI.SystemData;
namespace EventStore.ClientAPI
@@ -93,7 +92,7 @@ public static IEventStoreConnection Create(ConnectionSettings connectionSettings
connectionSettings.MaxQueueSize, connectionSettings.MaxConcurrentItems,
connectionSettings.MaxRetries, connectionSettings.MaxReconnections,
connectionSettings.RequireMaster, connectionSettings.ReconnectionDelay,
- connectionSettings.OperationTimeout,
+ connectionSettings.QueueTimeout, connectionSettings.OperationTimeout,
connectionSettings.OperationTimeoutCheckPeriod, credential, connectionSettings.UseSslConnection,
connectionSettings.TargetHost,
connectionSettings.ValidateServer, connectionSettings.FailOnNoServerResponse,
@@ -214,5 +213,19 @@ public static IEventStoreConnection Create(ConnectionSettings connectionSettings
return new EventStoreNodeConnection(connectionSettings, clusterSettings, endPointDiscoverer, connectionName);
}
+
+ ///
+ /// Creates a new using specific and a custom-defined
+ ///
+ /// The to apply to the new connection
+ /// The custom-defined to use for node discovery
+ /// Optional name of connection (will be generated automatically, if not provided)
+ /// a new
+ public static IEventStoreConnection Create(ConnectionSettings connectionSettings, IEndPointDiscoverer endPointDiscoverer, string connectionName = null)
+ {
+ Ensure.NotNull(connectionSettings, "settings");
+ Ensure.NotNull(endPointDiscoverer, "endPointDiscoverer");
+ return new EventStoreNodeConnection(connectionSettings, null, endPointDiscoverer, connectionName);
+ }
}
}
diff --git a/src/EventStore.ClientAPI.NetCore/EventStorePersistentSubscriptionBase.cs b/src/EventStore.ClientAPI.NetCore/EventStorePersistentSubscriptionBase.cs
index b97640c..0a37376 100644
--- a/src/EventStore.ClientAPI.NetCore/EventStorePersistentSubscriptionBase.cs
+++ b/src/EventStore.ClientAPI.NetCore/EventStorePersistentSubscriptionBase.cs
@@ -197,7 +197,7 @@ private async void ProcessQueue()
{
if (_subscription == null)
{
- Thread.Sleep(1);
+ await Task.Delay(1).ConfigureAwait(false);
}
else
{
diff --git a/src/EventStore.ClientAPI.NetCore/Exceptions/OperationExpiredException.cs b/src/EventStore.ClientAPI.NetCore/Exceptions/OperationExpiredException.cs
new file mode 100644
index 0000000..47abe9f
--- /dev/null
+++ b/src/EventStore.ClientAPI.NetCore/Exceptions/OperationExpiredException.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EventStore.ClientAPI.Exceptions
+{
+ ///
+ /// Exception thrown if an operation expires before it can be scheduled.
+ ///
+ public class OperationExpiredException : EventStoreConnectionException
+ {
+ ///
+ /// Constructs a new .
+ ///
+ public OperationExpiredException()
+ {
+ }
+
+ ///
+ /// Constructs a new .
+ ///
+ ///
+ public OperationExpiredException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Constructs a new .
+ ///
+ public OperationExpiredException(string message, Exception innerException):base(message, innerException)
+ {
+
+ }
+ }
+}
diff --git a/src/EventStore.ClientAPI.NetCore/GossipSeedClusterSettingsBuilder.cs b/src/EventStore.ClientAPI.NetCore/GossipSeedClusterSettingsBuilder.cs
index 105864a..32f96d2 100644
--- a/src/EventStore.ClientAPI.NetCore/GossipSeedClusterSettingsBuilder.cs
+++ b/src/EventStore.ClientAPI.NetCore/GossipSeedClusterSettingsBuilder.cs
@@ -50,6 +50,16 @@ public GossipSeedClusterSettingsBuilder SetGossipSeedEndPoints(params GossipSeed
_gossipSeeds = gossipSeeds;
return this;
}
+
+ ///
+ /// Allows infinite nodes discovery attempts.
+ ///
+ /// A for further configuration.
+ public GossipSeedClusterSettingsBuilder KeepDiscovering()
+ {
+ _maxDiscoverAttempts = Int32.MaxValue;
+ return this;
+ }
///
/// Sets the maximum number of attempts for discovery.
diff --git a/src/EventStore.ClientAPI.NetCore/IEndPointDiscoverer.cs b/src/EventStore.ClientAPI.NetCore/IEndPointDiscoverer.cs
new file mode 100644
index 0000000..e44ac5f
--- /dev/null
+++ b/src/EventStore.ClientAPI.NetCore/IEndPointDiscoverer.cs
@@ -0,0 +1,18 @@
+using System.Net;
+using System.Threading.Tasks;
+
+namespace EventStore.ClientAPI
+{
+ ///
+ /// Represents a way of discovering cluster endpoints. This could be through gossip, consul, text files, etc
+ ///
+ public interface IEndPointDiscoverer
+ {
+ ///
+ /// Called to discover a new
+ ///
+ /// The The recently failed endpoint
+ ///
+ Task DiscoverAsync(IPEndPoint failedTcpEndPoint);
+ }
+}
\ No newline at end of file
diff --git a/src/EventStore.ClientAPI.NetCore/Internal/ClusterDnsEndPointDiscoverer.cs b/src/EventStore.ClientAPI.NetCore/Internal/ClusterDnsEndPointDiscoverer.cs
index d76af0c..f227ffb 100644
--- a/src/EventStore.ClientAPI.NetCore/Internal/ClusterDnsEndPointDiscoverer.cs
+++ b/src/EventStore.ClientAPI.NetCore/Internal/ClusterDnsEndPointDiscoverer.cs
@@ -47,6 +47,9 @@ public ClusterDnsEndPointDiscoverer(ILogger log,
public async Task DiscoverAsync(IPEndPoint failedTcpEndPoint )
{
+ var maxDiscoverAttemptsStr = "";
+ if(_maxDiscoverAttempts != Int32.MaxValue)
+ maxDiscoverAttemptsStr = "/" + _maxDiscoverAttempts;
for (int attempt = 1; attempt <= _maxDiscoverAttempts; ++attempt)
{
//_log.Info("Discovering cluster. Attempt {0}/{1}...", attempt, _maxDiscoverAttempts);
@@ -55,18 +58,19 @@ public async Task DiscoverAsync(IPEndPoint failedTcpEndPoint )
var endPoints = await DiscoverEndPoint(failedTcpEndPoint).ConfigureAwait(false);
if (endPoints != null)
{
- _log.Info("Discovering attempt {0}/{1} successful: best candidate is {2}.", attempt, _maxDiscoverAttempts, endPoints);
+ _log.Info("Discovering attempt {0}{1} successful: best candidate is {2}.", attempt, maxDiscoverAttemptsStr, endPoints);
return endPoints.Value;
}
_log.Info("Discovering attempt {0}/{1} failed: no candidate found.", attempt, _maxDiscoverAttempts);
+
}
catch (Exception exc)
{
- _log.Info("Discovering attempt {0}/{1} failed with error: {2}.", attempt, _maxDiscoverAttempts, exc);
+ _log.Info("Discovering attempt {0}{1} failed with error: {2}.", attempt, maxDiscoverAttemptsStr, exc);
}
- Thread.Sleep(500);
+ await Task.Delay(500).ConfigureAwait(false);
}
throw new ClusterException(string.Format("Failed to discover candidate in {0} attempts.", _maxDiscoverAttempts));
}
@@ -230,6 +234,7 @@ private ClusterMessages.ClusterInfoDto TryGetGossipFrom(GossipSeed endPoint)
break;
case NodePreference.Slave:
nodes = nodes.OrderBy(nodeEntry => nodeEntry.State != ClusterMessages.VNodeState.Slave).ToArray(); // OrderBy is a stable sort and only affects order of matching entries
+ RandomShuffle(nodes, 0, nodes.Count(nodeEntry => nodeEntry.State == ClusterMessages.VNodeState.Slave) - 1);
break;
}
diff --git a/src/EventStore.ClientAPI.NetCore/Internal/Consts.cs b/src/EventStore.ClientAPI.NetCore/Internal/Consts.cs
index e2566e2..23bc70a 100644
--- a/src/EventStore.ClientAPI.NetCore/Internal/Consts.cs
+++ b/src/EventStore.ClientAPI.NetCore/Internal/Consts.cs
@@ -12,6 +12,7 @@ internal static class Consts
public const bool DefaultRequireMaster = true;
public static readonly TimeSpan DefaultReconnectionDelay = TimeSpan.FromMilliseconds(100);
+ public static readonly TimeSpan DefaultQueueTimeout = TimeSpan.Zero; //unlimited
public static readonly TimeSpan DefaultOperationTimeout = TimeSpan.FromSeconds(7);
public static readonly TimeSpan DefaultOperationTimeoutCheckPeriod = TimeSpan.FromSeconds(1);
diff --git a/src/EventStore.ClientAPI.NetCore/Internal/EventStoreNodeConnection.cs b/src/EventStore.ClientAPI.NetCore/Internal/EventStoreNodeConnection.cs
index dafa923..a9058f6 100644
--- a/src/EventStore.ClientAPI.NetCore/Internal/EventStoreNodeConnection.cs
+++ b/src/EventStore.ClientAPI.NetCore/Internal/EventStoreNodeConnection.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Threading;
using System.Threading.Tasks;
using EventStore.ClientAPI.ClientOperations;
using EventStore.ClientAPI.Common;
@@ -80,14 +79,15 @@ public Task DeleteStreamAsync(string stream, long expectedVersion,
return DeleteStreamAsync(stream, expectedVersion, false, userCredentials);
}
- public Task DeleteStreamAsync(string stream, long expectedVersion, bool hardDelete, UserCredentials userCredentials = null)
+ public async Task DeleteStreamAsync(string stream, long expectedVersion, bool hardDelete, UserCredentials userCredentials = null)
{
Ensure.NotNullOrEmpty(stream, "stream");
var source = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- EnqueueOperation(new DeleteStreamOperation(Settings.Log, source, Settings.RequireMaster,
- stream, expectedVersion, hardDelete, userCredentials));
- return source.Task;
+ var operation = new DeleteStreamOperation(Settings.Log, source, Settings.RequireMaster,
+ stream, expectedVersion, hardDelete, userCredentials);
+ await EnqueueOperation(operation).ConfigureAwait(false);
+ return await source.Task.ConfigureAwait(false);
}
public Task AppendToStreamAsync(string stream, long expectedVersion, params EventData[] events)
@@ -106,20 +106,21 @@ public Task AppendToStreamAsync(string stream, long expectedVersion
// ReSharper restore RedundantCast
}
- public Task AppendToStreamAsync(string stream, long expectedVersion, IEnumerable events, UserCredentials userCredentials = null)
+ public async Task AppendToStreamAsync(string stream, long expectedVersion, IEnumerable events, UserCredentials userCredentials = null)
{
// ReSharper disable PossibleMultipleEnumeration
Ensure.NotNullOrEmpty(stream, "stream");
Ensure.NotNull(events, "events");
var source = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- EnqueueOperation(new AppendToStreamOperation(Settings.Log, source, Settings.RequireMaster,
- stream, expectedVersion, events, userCredentials));
- return source.Task;
+ var operation = new AppendToStreamOperation(Settings.Log, source, Settings.RequireMaster,
+ stream, expectedVersion, events, userCredentials);
+ await EnqueueOperation(operation).ConfigureAwait(false);
+ return await source.Task.ConfigureAwait(false);
// ReSharper restore PossibleMultipleEnumeration
}
- public Task ConditionalAppendToStreamAsync(string stream, long expectedVersion, IEnumerable events,
+ public async Task ConditionalAppendToStreamAsync(string stream, long expectedVersion, IEnumerable events,
UserCredentials userCredentials = null)
{
// ReSharper disable PossibleMultipleEnumeration
@@ -127,20 +128,22 @@ public Task ConditionalAppendToStreamAsync(string stream
Ensure.NotNull(events, "events");
var source = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- EnqueueOperation(new ConditionalAppendToStreamOperation(Settings.Log, source, Settings.RequireMaster,
- stream, expectedVersion, events, userCredentials));
- return source.Task;
+ var operation = new ConditionalAppendToStreamOperation(Settings.Log, source, Settings.RequireMaster,
+ stream, expectedVersion, events, userCredentials);
+ await EnqueueOperation(operation).ConfigureAwait(false);
+ return await source.Task.ConfigureAwait(false);
// ReSharper restore PossibleMultipleEnumeration
}
- public Task StartTransactionAsync(string stream, long expectedVersion, UserCredentials userCredentials = null)
+ public async Task StartTransactionAsync(string stream, long expectedVersion, UserCredentials userCredentials = null)
{
Ensure.NotNullOrEmpty(stream, "stream");
var source = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- EnqueueOperation(new StartTransactionOperation(Settings.Log, source, Settings.RequireMaster,
- stream, expectedVersion, this, userCredentials));
- return source.Task;
+ var operation = new StartTransactionOperation(Settings.Log, source, Settings.RequireMaster,
+ stream, expectedVersion, this, userCredentials);
+ await EnqueueOperation(operation).ConfigureAwait(false);
+ return await source.Task.ConfigureAwait(false);
}
public EventStoreTransaction ContinueTransaction(long transactionId, UserCredentials userCredentials = null)
@@ -149,42 +152,44 @@ public EventStoreTransaction ContinueTransaction(long transactionId, UserCredent
return new EventStoreTransaction(transactionId, userCredentials, this);
}
- Task IEventStoreTransactionConnection.TransactionalWriteAsync(EventStoreTransaction transaction, IEnumerable events, UserCredentials userCredentials)
+ async Task IEventStoreTransactionConnection.TransactionalWriteAsync(EventStoreTransaction transaction, IEnumerable events, UserCredentials userCredentials)
{
// ReSharper disable PossibleMultipleEnumeration
Ensure.NotNull(transaction, "transaction");
Ensure.NotNull(events, "events");
var source = new TaskCompletionSource