public async Task WaitAsync_SecondEntryWithTheSameID_Is_BlockedUntilTheFirstResolvesTest() { // Arrange List <Task> tasks = new List <Task>(); string id = "test"; bool firstRunning = false; DateTime completionTimeOfFirstTask = DateTime.MinValue; DateTime completionTimeOfSecondTask = DateTime.MinValue; async Task FirstLock() { firstRunning = true; await _semaphoreOnEntity.WaitAsync(id); Thread.Sleep(LongSleepInterval); _semaphoreOnEntity.Release(id); completionTimeOfFirstTask = DateTime.UtcNow; } async Task SecondLock() { do { // Wait for first to start Thread.Sleep(ShortSleepInterval); }while (!firstRunning); await _semaphoreOnEntity.WaitAsync(id); _semaphoreOnEntity.Release(id); completionTimeOfSecondTask = DateTime.UtcNow; } // Act Task firstTask = Task.Run(FirstLock); Task secondTask = Task.Run(SecondLock); await Task.WhenAll(new Task[] { firstTask, secondTask }); // Assert completionTimeOfFirstTask.Should().BeBefore(completionTimeOfSecondTask); }
/// <inheritdoc/> public async Task CreateAsync(CreateContractRequest request) { await _semaphoreOnEntity.WaitAsync(request.ContractNumber).ConfigureAwait(false); try { _logger.LogInformation($"[{nameof(CreateAsync)}] Creating new contract [{request.ContractNumber}] version [{request.ContractVersion}] for [{request.UKPRN}]."); var existing = await _repository.GetByContractNumberAsync(request.ContractNumber); _contractValidator.ValidateForNewContract(request, existing); var newContract = _mapper.Map <Repository.DataModels.Contract>(request); newContract.LastUpdatedAt = newContract.CreatedAt = DateTime.UtcNow; // For amendment type notification the default status has to be approved // For None and Variation, it should be published to provider if (request.AmendmentType == ContractAmendmentType.Notfication) { newContract.Status = (int)ContractStatus.Approved; newContract.SignedOn = request.SignedOn.Value.Date; newContract.SignedBy = "Feed"; newContract.SignedByDisplayName = "Feed"; } else if (request.AmendmentType == ContractAmendmentType.None || request.AmendmentType == ContractAmendmentType.Variation) { newContract.Status = (int)ContractStatus.PublishedToProvider; } await _contractDocumentService.UpsertOriginalContractXmlAsync(newContract, new ContractRequest() { FileName = request.ContractData, ContractNumber = request.ContractNumber, ContractVersion = request.ContractVersion }); await _repository.CreateAsync(newContract); var updatedContractStatusResponse = new UpdatedContractStatusResponse { Id = newContract.Id, ContractNumber = newContract.ContractNumber, ContractVersion = newContract.ContractVersion, Ukprn = newContract.Ukprn, NewStatus = (ContractStatus)newContract.Status, Action = ActionType.ContractCreated, AmendmentType = request.AmendmentType }; _logger.LogInformation($"[{nameof(CreateAsync)}] Contract [{newContract.ContractNumber}] version [{newContract.ContractVersion}] has been created for [{newContract.Ukprn}]."); // Update operations to existing records can be done outside the semaphore if (request.AmendmentType == ContractAmendmentType.Variation || request.AmendmentType == ContractAmendmentType.None) { var statuses = new int[] { (int)ContractStatus.PublishedToProvider }; await ReplaceContractsWithGivenStatuses(existing, statuses); } else if (request.AmendmentType == ContractAmendmentType.Notfication) { var statuses = new int[] { (int)ContractStatus.Approved, (int)ContractStatus.ApprovedWaitingConfirmation, (int)ContractStatus.PublishedToProvider }; await ReplaceContractsWithGivenStatuses(existing, statuses); } await _mediator.Publish(updatedContractStatusResponse); } finally { _semaphoreOnEntity.Release(request.ContractNumber); } }