Пример #1
0
		/// <summary>
		/// Performs the basic update logic for a lease. This call should always be wrapped in a call that
		/// performs validation.
		/// </summary>
		/// <param name="lease">The lease to update.</param>
		private async Task UpdateLeaseInternalAsync(Lease lease)
		{
			DiskLease realLease = lease is DiskLease x ? x : new DiskLease(lease) { LeaseDuration = LeaseDuration, };
			string data = JsonConvert.SerializeObject(realLease);
			string partitionFile = GetPartitionFile(realLease);
			await WaitForLockWrite(partitionFile, data).ConfigureAwait(false);
		}
Пример #2
0
		/// <summary>
		/// Acquires the provided lease, the easy way or the hard way.
		/// </summary>
		/// <remarks>
		/// Acquiring is only used to steal leases from other owners. This applies exclusively to leases that haven't expired
		/// yet. Please be aware that "" counts as another owner, so released leases need to be stolen. If the acquire fails,
		/// the lease will not be available to anyone during the current acquire loop in the PartitionManager.
		/// </remarks>
		/// <param name="lease">The lease to acquire.</param>
		/// <returns>True if the lease was acquired, false if not.</returns>
		public async Task<bool> AcquireLeaseAsync(Lease lease)
		{
			// BUG: Not safe when used across multiple processes or machines.

			await Task.Delay(1).ConfigureAwait(false);

			DiskLease staleLease = lease is DiskLease x ? x : new DiskLease(lease) { LeaseDuration = LeaseDuration, };
			string key = GetLockKey(staleLease);
			bool hasLock = false;

			try
			{
				Monitor.Enter(key, ref hasLock);

				// NOTE: We can't check the token or expiry, because acquiring is used to steal leases.

				// Synchronous Task execution because we need to remain in the same thread for the lock we're holding.
				staleLease.Acquire(_owner, LeaseDuration);
				UpdateLeaseInternalAsync(staleLease).Wait();

				return true;
			}
			finally
			{
				if (hasLock)
				{
					Monitor.Exit(key);
				}
			}
		}
Пример #3
0
		/// <summary>
		/// Gets the lease for the provided partition from the lease store.
		/// </summary>
		/// <remarks>
		/// This call is used during the acquire loop of the PartitionManager to either set up a new partition to listen to or
		/// during the stealing of leases. It is used to refresh all lease data and replaces the original lease when used. That
		/// means the returned lease must be complete and up to date to avoid runtime issues.
		/// </remarks>
		/// <param name="partitionId">The partition to get the lease for.</param>
		/// <returns>The lease of the provided partition.</returns>
		public async Task<Lease> GetLeaseAsync(string partitionId)
		{
			await Task.Delay(1).ConfigureAwait(false);

			string partitionFile = GetPartitionFile(partitionId);
			string data = await WaitForLockRead(partitionFile).ConfigureAwait(false);
			DiskLease result = JsonConvert.DeserializeObject<DiskLease>(data);

			return result;
		}
Пример #4
0
		/// <summary>
		/// Creates a new lease if it doesn't exist in the store yet.
		/// </summary>
		/// <param name="partitionId">The partition id.</param>
		/// <returns>The new lease or the existing one.</returns>
		public async Task<Lease> CreateLeaseIfNotExistsAsync(string partitionId)
		{
			string partitionFile = GetPartitionFile(partitionId);

			// NOTE: Not particularly threadsafe, but this call is only executed on first startup of a consumer ever.
			if (File.Exists(partitionFile))
			{
				return await GetLeaseAsync(partitionId).ConfigureAwait(false);
			}

			DiskLease result = new DiskLease(partitionId) { LeaseDuration = LeaseDuration, };

			string data = JsonConvert.SerializeObject(result);
			await WaitForLockWrite(partitionFile, data).ConfigureAwait(false);

			return result;
		}
Пример #5
0
		/// <summary>
		/// Updates the provided lease, persisting its data to the lease store.
		/// </summary>
		/// <remarks>
		/// This call is not used by the API and is only useful for internal calls like UpdateCheckpointAsync.
		/// </remarks>
		/// <param name="lease">The lease to update.</param>
		/// <returns>True if the lease was updated, false if not.</returns>
		public async Task<bool> UpdateLeaseAsync(Lease lease)
		{
			// BUG: Not safe when used across multiple processes or machines.

			await Task.Delay(1).ConfigureAwait(false);

			DiskLease staleLease = lease is DiskLease x ? x : new DiskLease(lease) { LeaseDuration = LeaseDuration, };
			DiskLease freshLease = (DiskLease)await GetLeaseAsync(lease.PartitionId).ConfigureAwait(false);
			string key = GetLockKey(staleLease);
			bool hasLock = false;

			try
			{
				Monitor.Enter(key, ref hasLock);

				// We can't modify the lease, it's no longer ours to use. Expiration doesn't matter. If the lease has been
				// stolen by someone else, but it has already expired, it's still not ours to modify without first acquiring.
				if (freshLease.Token != staleLease.Token)
				{
					return false;
				}

				// NOTE: Disabled because there's temporarily only 1 running.

				//// We can't modify a lease that has already expired. It must first be acquired again.
				//// Synchronous Task execution because we need to remain in the same thread for the lock we're holding.
				//if (freshLease.IsExpired().Result)
				//{
				//	return false;
				//}

				// Synchronous Task execution because we need to remain in the same thread for the lock we're holding.
				UpdateLeaseInternalAsync(staleLease).Wait();

				return true;
			}
			finally
			{
				if (hasLock)
				{
					Monitor.Exit(key);
				}
			}
		}
Пример #6
0
		/// <summary>
		/// Updates the provided checkpoint for the provided lease, persisting it to the checkpoint store.
		/// </summary>
		/// <remarks>
		/// Checkpoints are protected against out of sync and old data by the PartitionContext, so there is no need to
		/// perform checks on whether or not it's going backwards.
		/// </remarks>
		/// <param name="lease">The lease to associate the checkpoint with.</param>
		/// <param name="checkpoint">The checkpoint to persist.</param>
		public async Task UpdateCheckpointAsync(Lease lease, Checkpoint checkpoint)
		{
			await Task.Delay(1).ConfigureAwait(false);

			DiskLease realLease = lease is DiskLease x ? x : new DiskLease(lease) { LeaseDuration = LeaseDuration, };

			// TODO: Also renew the lease to ensure it doesn't expire right after checkpointing. Basically anything touching the lease should renew it, creating a sliding expiration.
			realLease.Offset = checkpoint.Offset;
			realLease.SequenceNumber = checkpoint.SequenceNumber;

			bool result = await UpdateLeaseAsync(realLease).ConfigureAwait(false);

			// Because we don't have a return value, we have no other choice than to throw an exception if the lease failed
			// to update. If we don't, we would have a checkpoint that was never persisted.
			if (!result)
			{
				throw new
					InvalidOperationException($"The checkpoint for partition '{checkpoint.PartitionId}' could not be updated, because the lease is not owned.");
			}
		}
Пример #7
0
		/// <summary>
		/// Releases the provided lease, resetting ownership information so that another owner may acquire the lease.
		/// </summary>
		/// <remarks>
		/// Leases are only ever released if a PartitionPump closes gracefully by shutdown. Stealing of leases or crashes
		/// of a consumer result in 'dirty' leases of which must be stolen before they can be used by another owner. The
		/// return value is not used, so failure to release a lease in any way will cause the application to continue anyway.
		/// However, exceptions during releasing will be propagated to the unhandled exception handler in EventProcessorOptions.
		/// </remarks>
		/// <param name="lease">The lease to release.</param>
		/// <returns>True if the lease was released, false if not.</returns>
		public async Task<bool> ReleaseLeaseAsync(Lease lease)
		{
			// BUG: Not safe when used across multiple processes or machines.

			await Task.Delay(1).ConfigureAwait(false);

			DiskLease staleLease = lease is DiskLease x ? x : new DiskLease(lease) { LeaseDuration = LeaseDuration, };
			DiskLease freshLease = (DiskLease)await GetLeaseAsync(lease.PartitionId).ConfigureAwait(false);
			string key = GetLockKey(staleLease);
			bool hasLock = false;

			try
			{
				Monitor.Enter(key, ref hasLock);

				// We can't release the lease, it's no longer ours to use. Expiration doesn't matter. If the lease has been
				// stolen by someone else, but it has already expired, it's still not ours to release without first acquiring.
				if (freshLease.Token != staleLease.Token)
				{
					return false;
				}

				// Normally we shouldn't release a lease that has expired, but since we already confirmed we were the last owners,
				// and the caller doesn't use the return value, we have no choice but to release it anyway.

				// Synchronous Task execution because we need to remain in the same thread for the lock we're holding.
				staleLease.Release();
				UpdateLeaseInternalAsync(staleLease).Wait();

				return true;
			}
			finally
			{
				if (hasLock)
				{
					Monitor.Exit(key);
				}
			}
		}