public void TestEquality() { var connectionA = new ZooKeeperConnectionInfo( "cs", TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), new EquatableReadOnlyList <ZooKeeperAuthInfo>(new[] { new ZooKeeperAuthInfo("s", new EquatableReadOnlyList <byte>(new byte[] { 10 })) }) ); var connectionB = new ZooKeeperConnectionInfo( "cs", TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), new EquatableReadOnlyList <ZooKeeperAuthInfo>(new[] { new ZooKeeperAuthInfo("s", new EquatableReadOnlyList <byte>(new byte[] { 10 })) }) ); var connectionC = connectionA with { AuthInfo = new EquatableReadOnlyList <ZooKeeperAuthInfo>(new[] { new ZooKeeperAuthInfo("s", new EquatableReadOnlyList <byte>(new byte[] { 10 })), new ZooKeeperAuthInfo("s2", new EquatableReadOnlyList <byte>(new byte[] { 11 })), }) }; Assert.IsTrue(connectionA == connectionB); connectionA.GetHashCode().ShouldEqual(connectionB.GetHashCode()); Assert.IsFalse(connectionA == connectionC); Assert.AreNotEqual(connectionA.GetHashCode(), connectionC.GetHashCode()); } }
private void ReleaseEntry(ZooKeeperConnectionInfo connectionInfo, ConnectionEntry entry, bool remove) { bool shouldDispose; lock (this.PoolLock) { if (remove) { this.Connections.As <ICollection <KeyValuePair <ZooKeeperConnectionInfo, ConnectionEntry> > >() .Remove(new KeyValuePair <ZooKeeperConnectionInfo, ConnectionEntry>(connectionInfo, entry)); } shouldDispose = --entry.UserCount == 0; // this guarantee is upheld by the fact that we include the timeout watcher task as a "user" Invariant.Require( !shouldDispose || !(this.Connections.TryGetValue(connectionInfo, out var registeredEntry) && registeredEntry == entry), "If we're disposing then the entry must be removed from the pool" ); } if (shouldDispose) { // kick off connection disposal in the background to // avoid blocking/throwing and to handle the case where the task hasn't yet completed entry.ConnectionTask.ContinueWith( t => { var ignored = t.Result.DisposeAsync(); }, TaskContinuationOptions.OnlyOnRanToCompletion ); } }
private async Task <InternalConnection> InternalConnectAsync(ZooKeeperConnectionInfo connectionInfo) { var watcher = new ConnectionWatcher(connectionInfo.SessionTimeout); var zooKeeper = new ZooKeeper( connectstring: connectionInfo.ConnectionString, sessionTimeout: connectionInfo.SessionTimeout.InMilliseconds, watcher: watcher ); using var timeoutSource = new CancellationTokenSource(connectionInfo.ConnectTimeout.TimeSpan); using var timeoutRegistration = timeoutSource.Token.Register( () => watcher.TaskCompletionSource.TrySetException(new TimeoutException($"Timed out connecting to ZooKeeper after {connectionInfo.ConnectTimeout.InMilliseconds}ms")) ); foreach (var authInfo in connectionInfo.AuthInfo) { zooKeeper.addAuthInfo(authInfo.Scheme, authInfo.Auth.ToArray()); } try { await watcher.TaskCompletionSource.Task.ConfigureAwait(false); return(new InternalConnection(zooKeeper, watcher)); } catch { // on failure, clean up the instance we created try { await zooKeeper.closeAsync().ConfigureAwait(false); } finally { watcher.Dispose(); } throw; } }
public Task <ZooKeeperConnection> ConnectAsync(ZooKeeperConnectionInfo connectionInfo, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); lock (this.PoolLock) { // if we have an entry in the pool, use that if (this.Connections.TryGetValue(connectionInfo, out var entry)) { ++entry.UserCount; return(ToResultAsync(entry)); } // create a new connection var newConnectionTask = this.InternalConnectAsync(connectionInfo); var newEntry = new ConnectionEntry(newConnectionTask) { // 2 because we have both the current request and the timeout task we're about to create UserCount = 2 }; this.Connections.Add(connectionInfo, newEntry); newConnectionTask.ContinueWith(OnConnectionTaskCompleted); return(ToResultAsync(newEntry)); void OnConnectionTaskCompleted(Task <InternalConnection> internalConnectionTask) { // if we never connected, just release our hold on the task if (internalConnectionTask.Status != TaskStatus.RanToCompletion) { this.ReleaseEntry(connectionInfo, newEntry, remove: true); } // Otherwise, wait until either max age has elapsed or the connection is lost to release our hold. This // ensures both that the connection won't be removed from the pool prematurely as well as that it will be // eventually removed even if it stops being used else { Task.Delay(this._maxAge.TimeSpan, internalConnectionTask.Result.ConnectionLostToken) .ContinueWith(_ => this.ReleaseEntry(connectionInfo, newEntry, remove: true)); } } } async Task <ZooKeeperConnection> ToResultAsync(ConnectionEntry entry) { try { var internalConnection = await entry.ConnectionTask.ConfigureAwait(false); return(new ZooKeeperConnection(internalConnection, releaseToPool: () => this.ReleaseEntry(connectionInfo, entry, remove: false))); } catch { // if we fail to construct a connection, still release our hold on the entry to allow cleanup this.ReleaseEntry(connectionInfo, entry, remove: false); throw; } } }
public ZooKeeperSynchronizationHelper( ZooKeeperPath path, bool assumePathExists, string connectionString, Action <ZooKeeperDistributedSynchronizationOptionsBuilder>?optionsBuilder, bool setAcquiredMarker = false) { this.Path = path; this._assumePathExists = assumePathExists; var options = ZooKeeperDistributedSynchronizationOptionsBuilder.GetOptions(optionsBuilder); this._connectionInfo = new ZooKeeperConnectionInfo( connectionString ?? throw new ArgumentNullException(nameof(connectionString)), ConnectTimeout: options.ConnectTimeout, SessionTimeout: options.SessionTimeout, AuthInfo: options.AuthInfo ); this._acl = options.Acl; this._setAcquiredMarker = setAcquiredMarker; }