/// <summary> /// Constructs a new lock based on the provided <paramref name="path"/>, <paramref name="connectionString"/>, and <paramref name="options"/>. /// /// If <paramref name="assumePathExists"/> is specified, then the node will not be created as part of acquiring nor will it be /// deleted after releasing (defaults to false). /// </summary> public ZooKeeperDistributedReaderWriterLock( ZooKeeperPath path, string connectionString, bool assumePathExists = false, Action <ZooKeeperDistributedSynchronizationOptionsBuilder>?options = null) : this(path, assumePathExists : assumePathExists, connectionString, options) { if (path == default) { throw new ArgumentNullException(nameof(path)); } if (path == ZooKeeperPath.Root) { throw new ArgumentException("Cannot be the root", nameof(path)); } }
public ZooKeeperNodeHandle(ZooKeeperConnection connection, string nodePath, bool shouldDeleteParent) { this._connection = connection; this._nodePath = new ZooKeeperPath(nodePath); this._shouldDeleteParent = shouldDeleteParent; this._handleLostState = new Lazy <HandleLostState>(() => { var handleLostSource = CancellationTokenSource.CreateLinkedTokenSource(this._connection.ConnectionLostToken); var handleLostToken = handleLostSource.Token; // grab this now before the source is disposed var disposalSource = new CancellationTokenSource(); var disposalSourceToken = disposalSource.Token; var monitoringTask = Task.Run(async() => { try { while (true) { var result = await WaitForNotExistsOrChangedAsync( this._connection, this._nodePath.ToString(), timeoutToken: disposalSource.Token ).ConfigureAwait(false); switch (result) { case false: // disposalSource triggered return; case true: // node no longer exists handleLostSource.Cancel(); return; default: // something changed break; // continue looping } } } finally { handleLostSource.Dispose(); } }); return(new HandleLostState(handleLostToken, disposalSource, monitoringTask)); }); }
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; }
/// <summary> /// Constructs a provider which uses <paramref name="connectionString"/> and <paramref name="options"/>. Lock and semaphore nodes will be created /// in <paramref name="directoryPath"/>. /// </summary> public ZooKeeperDistributedSynchronizationProvider(ZooKeeperPath directoryPath, string connectionString, Action <ZooKeeperDistributedSynchronizationOptionsBuilder>?options = null) { this._directoryPath = directoryPath != default ? directoryPath : throw new ArgumentNullException(nameof(directoryPath)); this._connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); this._options = options; }
/// <summary> /// Constructs a new semaphore based on the provided <paramref name="directoryPath"/>, <paramref name="name"/>, <paramref name="connectionString"/>, and <paramref name="options"/>. /// /// The semaphore's path will be a parent node of <paramref name="directoryPath"/>. If <paramref name="name"/> is not a valid node name, it will be transformed to ensure /// validity. /// </summary> public ZooKeeperDistributedSemaphore(ZooKeeperPath directoryPath, string name, int maxCount, string connectionString, Action <ZooKeeperDistributedSynchronizationOptionsBuilder>?options = null) : this( (directoryPath == default ? throw new ArgumentNullException(nameof(directoryPath)) : directoryPath).GetChildNodePathWithSafeName(name),
public static async Task <string> CreateEphemeralSequentialNode( this ZooKeeperConnection connection, ZooKeeperPath directory, string namePrefix, IEnumerable <ACL> aclEnumerable, bool ensureDirectoryExists) { // If we are in charge of ensuring the directory, this algorithm loops until either we EnsureDirectory fails or we hit an error other than the directory // not existing. This supports concurrent node creation and directory creation as well as node creation and directory deletion. var acl = aclEnumerable.ToList(); while (true) { var createdDirectories = ensureDirectoryExists ? await EnsureDirectoryAndGetCreatedPathsAsync().ConfigureAwait(false) : Array.Empty <ZooKeeperPath>(); try { return(await connection.ZooKeeper.createAsync($"{directory}{ZooKeeperPath.Separator}{namePrefix}", data : Array.Empty <byte>(), acl, CreateMode.EPHEMERAL_SEQUENTIAL).ConfigureAwait(false)); } catch (KeeperException.NoNodeException ex) { // If we're not ensuring the directory, rethrow a more helpful error message. Otherwise, // swallow the error and go around the loop again if (!ensureDirectoryExists) { throw new InvalidOperationException($"Node '{directory}' does not exist", ex); } } catch { // on an unhandled error, clean up any directories we created await TryCleanUpCreatedDirectoriesAsync(createdDirectories).ConfigureAwait(false); throw; } } async Task <IReadOnlyList <ZooKeeperPath> > EnsureDirectoryAndGetCreatedPathsAsync() { // This algorithm loops until either the directory exists or our creation attempt fails with something other // than NoNodeException or NodeExistsException. This supports concurrent directory creation as well as concurrent // creation and deletion via optimistic concurrency var toCreate = new Stack <ZooKeeperPath>(); toCreate.Push(directory); List <ZooKeeperPath>?created = null; do { var directoryToCreate = toCreate.Peek(); if (directoryToCreate == ZooKeeperPath.Root) { throw new InvalidOperationException($"Received {typeof(KeeperException.NoNodeException)} when creating child node of directory '{ZooKeeperPath.Root}'"); } try { await connection.ZooKeeper.createAsync(directoryToCreate.ToString(), data : Array.Empty <byte>(), acl, CreateMode.PERSISTENT).ConfigureAwait(false); toCreate.Pop(); (created ??= new List <ZooKeeperPath>()).Add(directoryToCreate); } catch (KeeperException.NodeExistsException) // someone else created it { toCreate.Pop(); } catch (KeeperException.NoNodeException) // parent needs to be created { toCreate.Push(directoryToCreate.GetDirectory() !.Value); } catch { // on an unhandled failure, attempt to clean up our work if (created != null) { await TryCleanUpCreatedDirectoriesAsync(created).ConfigureAwait(false); } throw; } }while (toCreate.Count != 0); return(created ?? (IReadOnlyList <ZooKeeperPath>)Array.Empty <ZooKeeperPath>()); } async Task TryCleanUpCreatedDirectoriesAsync(IReadOnlyList <ZooKeeperPath> createdDirectories) { try { // delete in reverse order of creation for (var i = createdDirectories.Count - 1; i >= 0; --i) { await connection.ZooKeeper.deleteAsync(createdDirectories[i].ToString()).ConfigureAwait(false); } } catch { // swallow errors, since there's a good chance this cleanup fails the same way that the creation did } } }