Skip to main content

Queue activation

Queue activation records that a queue may contain work ready to process. It lets a queue job start after enqueueing without a cron schedule or a separate start request. Repeated requests are coalesced per queue, so a burst of messages does not create a separate job run for every enqueue.

Choosing how a queue runs​

Processing modelWhen processing startsConcurrency
Queue job with no scheduleWhen messages become readyOne active job run across instances, with optional processing threads
Queue job with a cron scheduleOn the saved schedule, or an explicit manual/extra runOne active job run across instances
Distributed queue processorWhen work is ready, with periodic reconciliationA worker pool on every instance

An ordinary queue job needs no DefaultSchedule:

public class OrderExportJob : IQueueJob<OrderExportMessage>
{
public async Task<ProcessResults> ProcessMessageAsync(
OrderExportMessage message, CancellationToken cancellationToken)
{
await ExportOrderAsync(message, cancellationToken);
return ProcessResults.Processed();
}
}

// Enqueueing is enough; there is no separate enqueue-and-start method.
await enqueuer.EnqueueAsync(new OrderExportMessage { OrderId = "123" });

Add a cron DefaultSchedule when the queue should run only at those times. Schedules are saved in the database when the job is first registered. To change an existing job to readiness-driven processing, clear its saved schedule through the Admin UI/API or IJobMetaDataRepository.UpdateJobScheduleAsync(jobName, null). Removing the property from the class alone does not clear that saved schedule.

An empty schedule means readiness-driven processing for queue jobs. For an ordinary IScheduledJob, an empty schedule still means it runs only when explicitly started.

What happens after enqueueing?​

For a ready message, the normal enqueuer requests activation after the queue operation completes. With the Entity Framework integration, this happens after the context's transaction commits. Rolled-back operations do not activate the queue, and in-memory additions finish before consumers are notified.

Nexus records activation state through IQueueActivationStore and sends a best-effort instance event to wake processing quickly. Losing that event does not lose a persisted activation. A pending activation can wait while the queue job is disabled or paused on error; processing resumes when it is eligible again.

The activation is a signal to inspect the queue, not a count or snapshot of messages. It may result in no work if another consumer already handled the messages. Messages arriving during processing retain readiness for more work; they do not start a concurrent run of the same job.

Activation is separate from message leasing. Leases still protect messages while they are being processed, and expired leases can cause redelivery. Processing handlers must tolerate the retry behavior of their storage provider.

Activation delay versus processing delay​

Use ActivationDelay to collect a short burst before starting a readiness-driven queue:

[Queue("order_exports", ActivationDelay = "250ms")]
public class OrderExportMessage : IQueueMessage
{
public string OrderId { get; set; } = "";
}

The first request starts a fixed collection window. Additional messages do not keep extending it. It does not delay each message and does not replace a cron schedule.

ProcessingDelay, EnqueueContext.ProcessAfter, and ProcessAtUtc instead determine when an individual message becomes eligible. Queue maintenance promotes due messages and requests readiness; a scheduled-only job still waits for its schedule.

Named/generated queue definitions also support ActivationDelay. A saved override can be changed through IQueueMetaDataRepository.SetActivationDelayAsync() or POST /api/queues/{queueName}/activation-delay with an activationDelay string; a null value restores the code default.

Activation storage​

Activation storage and message storage are separate choices. SQL Server, PostgreSQL, and SQLite provider registration supplies database activation storage automatically. No additional activation registration is needed for the usual database-backed application.

Redis can hold activation state instead:

builder.Services.AddNexus().AddRedisQueueActivations(options =>
{
options.ConnectionString = redisConnectionString;
options.KeyPrefix = "my-nexus-app";
});

This is independent of Redis external pending storage and Redis instance events. Configure the same shared activation store and namespace on producers and processing hosts for the same queues. Redis persistence and availability also determine how well its stored activation survives infrastructure failures.

For a single-instance application, AddMemoryQueueActivations() opts into process-local activation state. It does not survive restart or coordinate separate producer/worker processes. It is distinct from QueueStorageMode.InMemory, which selects where messages are processed. Only one explicitly selected activation provider can be registered.

Recovery and external producers​

Queue writes and activation requests are distinct steps; even database-backed activation does not imply an atomic enqueue-plus-activation transaction. The queue-job reconciler checks readiness-driven queues on startup and periodically to recover missed activation. Configure its interval with JobsOptions.QueueActivationReconciliationInterval (default ten minutes). Unhandled queue-job failure/cancellation defers activation using QueueActivationFailureDelay (default five seconds). Individual message retries still use their own retry dates.

Distributed processors perform their own reconciliation through QueueProcessorOptions.ReconciliationInterval. Delayed-message and lease maintenance has separate timing; see queue maintenance.

External pending providers have durable readiness bridges. A direct broker producer must use that provider's current topology/envelope; writing only a processing copy is not enough for prompt readiness-driven processing. Database migration does not transfer messages from old broker resources.

For custom infrastructure operations or explicit database transactions, IQueueReadinessNotifier.NotifyAsync(queueName) records readiness. Call it after a successful commit when the normal integration does not own the commit. It does not enqueue a message or wait for processing to finish. Normal application enqueueing should use the enqueuer's built-in notification.

IJobStartRequester is a separate best-effort job trigger. It does not provide the persistent queue-readiness contract. See starting a job programmatically for durable extra runs.