ConfigureLocalWorkers
Customizes the built-in PostgreSQL local worker pool. Local workers are registered automatically when UsePostgres() is configured. No explicit call is needed to enable them.
Signature
public SchedulerConfigurationBuilder ConfigureLocalWorkers(
Action<LocalWorkerOptions> configure
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
configure | Action<LocalWorkerOptions> | Yes | Callback to customize worker count, polling interval, and timeouts |
Returns
SchedulerConfigurationBuilder, for continued fluent chaining.
LocalWorkerOptions
| Property | Type | Default | Description |
|---|---|---|---|
WorkerCount | int | Environment.ProcessorCount | Number of concurrent worker tasks polling for jobs. 1 to 256 |
PollingInterval | TimeSpan | 1 second | How often idle workers poll for new jobs. Greater than zero, up to 30 days |
VisibilityTimeout | TimeSpan | 30 minutes | How long a claimed job stays invisible after its worker last refreshed its claim before another worker can reclaim it (crash recovery). A running job's claim is refreshed every third of this, so a long job is not reclaimed while it runs |
BatchSize | int | 1 | Number of jobs each worker claims per poll cycle. At least 1. Every job in the batch keeps its claim until the worker runs it, however long the jobs ahead of it take |
ShutdownTimeout | TimeSpan | 30 seconds | Grace period for in-flight jobs during shutdown. 0 to 30 days |
Examples
Default Behavior (No Call Needed)
When UsePostgres() is configured, local workers are enabled automatically with default settings. You don't need to call anything to get local execution:
services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres(connectionString)
)
.AddMediator(typeof(Program).Assembly)
.AddScheduler(scheduler => scheduler
.Schedule<IMyTrain>("my-job", new MyInput(), Every.Minutes(5))
)
);Custom Configuration
Use ConfigureLocalWorkers() to customize worker behavior:
services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres(connectionString)
)
.AddMediator(typeof(Program).Assembly)
.AddScheduler(scheduler => scheduler
.ConfigureLocalWorkers(options =>
{
options.WorkerCount = 4;
options.PollingInterval = TimeSpan.FromSeconds(2);
options.VisibilityTimeout = TimeSpan.FromMinutes(15);
options.ShutdownTimeout = TimeSpan.FromMinutes(1);
})
.Schedule<IMyTrain>("my-job", new MyInput(), Every.Minutes(5))
)
);Mixed Local and Remote Workers
Local workers are always registered alongside remote submitters. Trains not routed to a remote submitter execute locally:
services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres(connectionString)
)
.AddMediator(assemblies)
.AddScheduler(scheduler => scheduler
.ConfigureLocalWorkers(opts => opts.WorkerCount = 8)
.UseRemoteWorkers(
remote => remote.BaseUrl = "https://gpu-workers/trax/execute",
routing => routing
.ForTrain<IHeavyComputeTrain>()
.ForTrain<IAiInferenceTrain>())
.Schedule<IMyTrain>("my-job", new MyInput(), Every.Minutes(5))
.Schedule<IHeavyComputeTrain>("heavy-compute", new HeavyInput(), Every.Hours(1))
)
);In this example, IHeavyComputeTrain is dispatched to the remote HTTP endpoint, while IMyTrain executes locally with 8 worker threads.
Remarks
- Local workers are the implicit default when
UsePostgres()is configured. You only needConfigureLocalWorkers()if you want to change the defaults. - No connection string parameter is needed. Local workers use the same
IDataContextregistered byUsePostgres(). - No additional NuGet packages required. This is included in
Trax.Scheduler. - Jobs are queued to the
trax.background_jobtable and dequeued atomically using PostgreSQL'sFOR UPDATE SKIP LOCKED. - Workers delete job rows after execution (both success and failure), including a job that finishes during shutdown: the delete does not take the host's stopping token. Trax.Core's Metadata and DeadLetter tables handle the audit trail.
AddSchedulerrefuses options outside their ranges when it is built (see Value Ranges);VisibilityTimeoutmust be between one second and ten years.- While a worker holds jobs, it refreshes the
fetched_atof every job it has claimed and not yet finished, the running one and any waiting behind it in a batch, every third ofVisibilityTimeout. If a worker crashes mid-execution, the refreshes stop, the timestamp becomes stale, and the job is reclaimed afterVisibilityTimeout. - A job waiting in
trax.background_jobfor a free worker is not failed by the stale-pending reaper, however long it waits. - When
UseRemoteWorkers()orUseSqsWorkers()is also configured, local workers still run. Only the trains explicitly routed viaForTrain<T>()or[TraxRemote]are dispatched remotely.
Registered Services
The scheduler automatically registers these services when UsePostgres() is configured:
| Service | Lifetime | Description |
|---|---|---|
LocalWorkerOptions | Singleton | Configuration options |
IJobRunnerTrain → JobRunnerTrain | Scoped | The train that workers use to execute each job |
IJobSubmitter → PostgresJobSubmitter | Scoped | Enqueue implementation (INSERT into background_job) |
LocalWorkerService | Hosted Service | Background worker that polls and executes jobs |
Package
dotnet add package Trax.SchedulerSee Also
- Job Submission Architecture: detailed architecture, crash recovery, comparison with Hangfire
- UseRemoteWorkers: per-train HTTP remote dispatch
- UseSqsWorkers: per-train SQS dispatch