public async Task Test_PeekMatchesDequeuedItem_SingleConsumer() { IQueuedTask peekTask = null; IQueuedTaskToken dequeuedTaskToken = null; PostgreSqlTaskQueueInfo taskQueueInfo = CreateTaskQueueInfo(() => mDataSource.LastPostedAt); using (PostgreSqlTaskQueueConsumer taskQueue = CreateTaskQueueConsumer(() => mDataSource.LastPostedAt)) { int expectedDequeueCount = mDataSource .NumTasksInQueue; for (int i = 0; i < expectedDequeueCount; i++) { peekTask = await taskQueueInfo.PeekAsync(); Assert.NotNull(peekTask); dequeuedTaskToken = await taskQueue.DequeueAsync(); Assert.NotNull(dequeuedTaskToken); Assert.AreEqual(peekTask.Id, dequeuedTaskToken .DequeuedTask .Id); } } }
private ITaskExecutor ResolveTaskExecutor ( IQueuedTask queuedTask ) { Type payloadType; ITaskExecutor taskExecutor = null; if ( ( payloadType = DetectTaskPayloadType( queuedTask ) ) != null ) { mLogger.DebugFormat( "Runtime payload type {0} found for task type {1}.", payloadType, queuedTask.Type ); taskExecutor = mExecutorRegistry .ResolveExecutor( payloadType ); if ( taskExecutor != null ) mLogger.DebugFormat( "Executor {0} found for task type {1}.", taskExecutor.GetType().FullName, queuedTask.Type ); else mLogger.WarnFormat( "Executor not found for task type {0}.", queuedTask.Type ); } else mLogger.WarnFormat( "Runtime payload type not found for task type {0}.", queuedTask.Type ); return taskExecutor; }
public async Task Test_EnqueueDoesNotChangePeekResult_SingleConsumer() { int futureTicks = 1; IQueuedTask peekTask = null, rePeekTask = null; string taskType = typeof(SampleTaskPayload) .FullName; PostgreSqlTaskQueueProducer taskQueueProducer = CreateTaskQueueProducer(() => mDataSource .LastPostedAt .AddTicks(1)); PostgreSqlTaskQueueInfo taskQueueInfo = CreateTaskQueueInfo(() => mDataSource .LastPostedAt .AddTicks(futureTicks++)); peekTask = await taskQueueInfo.PeekAsync(); Assert.NotNull(peekTask); await taskQueueProducer.EnqueueAsync(payload : new SampleTaskPayload(100), source : nameof(Test_EnqueueDoesNotChangePeekResult_SingleConsumer), priority : 0); rePeekTask = await taskQueueInfo.PeekAsync(); Assert.NotNull(rePeekTask); //Placing a new element in a queue occurs at its end, // so peeking must not be affected // if no other operation occurs Assert.AreEqual(peekTask.Id, rePeekTask.Id); }
public async Task Test_DequeueChangesPeekResult_SingleConsumer() { IQueuedTask peekTask = null, rePeekTask = null; IQueuedTaskToken dequeuedTaskToken = null; PostgreSqlTaskQueueInfo taskQueueInfo = CreateTaskQueueInfo(() => mDataSource.LastPostedAt); using (PostgreSqlTaskQueueConsumer taskQueueConsumer = CreateTaskQueueConsumer(() => mDataSource.LastPostedAt)) { peekTask = await taskQueueInfo.PeekAsync(); Assert.NotNull(peekTask); dequeuedTaskToken = await taskQueueConsumer.DequeueAsync(); Assert.NotNull(dequeuedTaskToken); rePeekTask = await taskQueueInfo.PeekAsync(); Assert.NotNull(rePeekTask); //Removing a new element from the queue // occurs at the beginning of the queue, // so peeking must yield a different result // than before dequeue-ing Assert.AreNotEqual(rePeekTask.Id, peekTask.Id); } }
public async Task Test_CanEnqueue_NewTask_Serial() { Faker faker = new Faker(); ManualResetEvent notificationWaitHandle = new ManualResetEvent(false); DateTimeOffset postedAt = mDataSource.LastPostedAt .AddTicks(1); PostgreSqlTaskQueueProducer taskQueueProducer = CreateTaskQueueProducer(() => postedAt); EventHandler <ClearForDequeueEventArgs> handleClearForDequeue = (s, e) => { if (e.Reason == ClearForDequeReason.NewTaskPostedNotificationReceived) { notificationWaitHandle.Set(); } }; using (PostgreSqlTaskQueueConsumer taskQueueConsumer = CreateTaskQueueConsumer(() => postedAt)) { taskQueueConsumer.ClearForDequeue += handleClearForDequeue; await taskQueueConsumer .StartReceivingNewTaskUpdatesAsync(); Assert.IsTrue(taskQueueConsumer .IsReceivingNewTaskUpdates); //Enqueue task and check result IQueuedTask queuedTask = await taskQueueProducer .EnqueueAsync(payload : new SampleTaskPayload(100), source : nameof(Test_CanEnqueue_NewTask_Serial), priority : faker.Random.Int(1, 100)); Assert.NotNull(queuedTask); await Assert_ResultAddedOrUpdatedCorrectly(queuedTask); notificationWaitHandle.WaitOne(); await taskQueueConsumer .StopReceivingNewTaskUpdatesAsync(); Assert.IsFalse(taskQueueConsumer .IsReceivingNewTaskUpdates); taskQueueConsumer.ClearForDequeue -= handleClearForDequeue; } }
private void AssertCorrectDefaultIsTaskErrorRecoverableFn(StakhanoviseSetupDefaults setupDefaults) { IQueuedTask mockTask = MockQueuedTask(); Assert.NotNull(setupDefaults.IsTaskErrorRecoverable); Assert.IsFalse(setupDefaults.IsTaskErrorRecoverable(mockTask, new NullReferenceException())); Assert.IsFalse(setupDefaults.IsTaskErrorRecoverable(mockTask, new ArgumentException())); Assert.IsTrue(setupDefaults.IsTaskErrorRecoverable(mockTask, new ApplicationException())); Assert.IsTrue(setupDefaults.IsTaskErrorRecoverable(mockTask, new Exception())); Assert.IsTrue(setupDefaults.IsTaskErrorRecoverable(mockTask, new NotSupportedException())); }
public QueuedTaskResult(IQueuedTask task) { if (task == null) { throw new ArgumentNullException(nameof(task)); } Id = task.Id; Type = task.Type; Payload = task.Payload; Status = QueuedTaskStatus.Unprocessed; Source = task.Source; Priority = task.Priority; PostedAtTs = task.PostedAtTs; ProcessingTimeMilliseconds = 0; }
public async Task Test_CanPeek(int futureTicks) { IQueuedTask actualTopOfQueue; IQueuedTask expectedTopOfQueue = ExpectedTopOfQueueTask; for (int i = 0; i <= futureTicks; i++) { PostgreSqlTaskQueueInfo taskQueue = CreateTaskQueue(() => mDataSource .LastPostedAt .AddTicks(futureTicks)); actualTopOfQueue = await taskQueue.PeekAsync(); Assert.NotNull(actualTopOfQueue); Assert.AreEqual(expectedTopOfQueue.Id, actualTopOfQueue.Id); } }
public async Task <IQueuedTask> PeekAsync() { IQueuedTask peekedTask = null; DateTimeOffset refNow = mTimestampProvider.GetNow(); CheckNotDisposedOrThrow(); //This simply returns the latest item on top of the queue, // without acquiring any lock string peekSql = $@"SELECT q.* FROM {mOptions.Mapping.QueueTableName} as q WHERE q.task_locked_until_ts < @t_now ORDER BY q.task_priority ASC, q.task_locked_until_ts ASC, q.task_lock_handle_id ASC LIMIT 1" ; using (NpgsqlConnection conn = await OpenConnectionAsync()) using (NpgsqlCommand peekCmd = new NpgsqlCommand(peekSql, conn)) { peekCmd.Parameters.AddWithValue("t_now", NpgsqlDbType.TimestampTz, refNow); await peekCmd.PrepareAsync(); using (NpgsqlDataReader taskReader = await peekCmd.ExecuteReaderAsync()) { if (await taskReader.ReadAsync()) { peekedTask = await taskReader.ReadQueuedTaskAsync(); } await taskReader.CloseAsync(); } await conn.CloseAsync(); } return(peekedTask); }
private async Task Assert_ResultAddedOrUpdatedCorrectly(IQueuedTask queuedTask) { IQueuedTaskResult queuedTaskResult = await mDataSource .GetQueuedTaskResultFromDbByIdAsync(queuedTask.Id); Assert.NotNull(queuedTaskResult); Assert.NotNull(queuedTaskResult.Payload); Assert.AreEqual(queuedTask.Id, queuedTaskResult.Id); Assert.AreEqual(queuedTask.Type, queuedTaskResult.Type); Assert.AreEqual(queuedTask.Source, queuedTaskResult.Source); Assert.AreEqual(queuedTask.Priority, queuedTaskResult.Priority); Assert.LessOrEqual(Math.Abs((queuedTask.PostedAtTs - queuedTaskResult.PostedAtTs).TotalMilliseconds), 10); Assert.AreEqual(QueuedTaskStatus.Unprocessed, queuedTaskResult.Status); }
public async Task Test_CanEnqueue_RepostExistingTask_Serial() { Faker faker = new Faker(); DateTimeOffset postedAt = mDataSource.LastPostedAt .AddSeconds(1); PostgreSqlTaskQueueProducer taskQueueProducer = CreateTaskQueueProducer(() => postedAt); foreach (IQueuedTaskToken token in mDataSource.CanBeRepostedSeededTaskTokens) { QueuedTaskInfo repostTaskInfo = new QueuedTaskInfo() { Id = token.DequeuedTask.Id, Priority = faker.Random.Int(1, 100), Payload = token.DequeuedTask.Payload, Source = nameof(Test_CanEnqueue_RepostExistingTask_Serial), Type = token.DequeuedTask.Type, LockedUntilTs = postedAt.AddMilliseconds(faker.Random.Long(1000, 10000)) }; //Remove task record from DB - only dequeued tasks get reposted await mDataSource.RemoveQueuedTaskFromDbByIdAsync(token .DequeuedTask .Id); //Enqueue task and check result IQueuedTask requeuedTask = await taskQueueProducer .EnqueueAsync(repostTaskInfo); Assert.NotNull(requeuedTask); await Assert_ResultAddedOrUpdatedCorrectly(requeuedTask); } }
public async Task Test_CanEnqueue_NewTask_ParallelProducers(int nProducers) { Faker faker = new Faker(); CountdownEvent notificationWaitHandle = new CountdownEvent(nProducers); DateTimeOffset postedAt = mDataSource.LastPostedAt .AddTicks(1); PostgreSqlTaskQueueProducer taskQueueProducer = CreateTaskQueueProducer(() => postedAt); EventHandler <ClearForDequeueEventArgs> handleClearForDequeue = (s, e) => { if (e.Reason == ClearForDequeReason.NewTaskPostedNotificationReceived) { notificationWaitHandle.Signal(); } }; Task[] producers = new Task[nProducers]; using (PostgreSqlTaskQueueConsumer taskQueueConsumer = CreateTaskQueueConsumer(() => postedAt)) { taskQueueConsumer.ClearForDequeue += handleClearForDequeue; await taskQueueConsumer .StartReceivingNewTaskUpdatesAsync(); Assert.IsTrue(taskQueueConsumer .IsReceivingNewTaskUpdates); for (int i = 0; i < nProducers; i++) { producers[i] = Task.Run(async() => { //Enqueue task and check result IQueuedTask queuedTask = await taskQueueProducer .EnqueueAsync(payload: new SampleTaskPayload(100), source: nameof(Test_CanEnqueue_NewTask_ParallelProducers), priority: faker.Random.Int(1, 100)); Assert.NotNull(queuedTask); await Assert_ResultAddedOrUpdatedCorrectly(queuedTask); }); } notificationWaitHandle.Wait(); await taskQueueConsumer .StopReceivingNewTaskUpdatesAsync(); Assert.IsFalse(taskQueueConsumer .IsReceivingNewTaskUpdates); taskQueueConsumer.ClearForDequeue -= handleClearForDequeue; } }
public void AddTask(IQueuedTask taskToAdd) { throw new NotImplementedException(); }
private async Task<TaskExecutionResult> ExecuteTaskAsync ( TaskExecutionContext executionContext ) { ITaskExecutor taskExecutor = null; DateTimeOffset retryAt = DateTimeOffset.UtcNow; IQueuedTask dequeuedTask = executionContext .TaskToken .DequeuedTask; try { //Check for cancellation before we start execution executionContext.StartTimingExecution(); executionContext.ThrowIfCancellationRequested(); //Attempt to resolve and run task executor if ( ( taskExecutor = ResolveTaskExecutor( dequeuedTask ) ) != null ) { mLogger.DebugFormat( "Beginning task execution. Task id = {0}.", dequeuedTask.Id ); //Execute task await taskExecutor.ExecuteAsync( dequeuedTask.Payload, executionContext ); mLogger.DebugFormat( "Task execution completed. Task id = {0}.", dequeuedTask.Id ); //Ensure we have a result - since no exception was thrown // and no result explicitly set, assume success. if ( !executionContext.HasResult ) executionContext.NotifyTaskCompleted(); } } catch ( OperationCanceledException ) { //User code has observed cancellation request executionContext?.NotifyCancellationObserved(); } catch ( Exception exc ) { mLogger.Error( "Error executing queued task", exception: exc ); bool isRecoverable = mOptions.IsTaskErrorRecoverable( dequeuedTask, exc ); executionContext?.NotifyTaskErrored( new QueuedTaskError( exc ), isRecoverable: isRecoverable ); } finally { executionContext.StopTimingExecution(); } //Compute the amount of time to delay task execution // if execution failed if ( executionContext.HasResult && executionContext.ExecutionFailed ) retryAt = ComputeRetryAt( executionContext.TaskToken ); return taskExecutor != null ? new TaskExecutionResult( executionContext.ResultInfo, duration: executionContext.Duration, retryAt: retryAt, faultErrorThresholdCount: mOptions.FaultErrorThresholdCount ) : null; }
private Type DetectTaskPayloadType ( IQueuedTask queuedTask ) { return queuedTask.Payload != null ? queuedTask.Payload.GetType() : mExecutorRegistry.ResolvePayloadType( queuedTask.Type ); }
public void AddTask(IQueuedTask taskToAdd) { _logger.LogInformation("Adding task to background queue. {task}", taskToAdd); _tasks.Add(taskToAdd); }