abpframework/abp · critical · AbpException
The following background job(s) are configured for more than
Error message
The following background job(s) are configured for more than one dedicated worker: {string.Join(", ", alreadyConfigured)}. Each job type can be handled by only one dedicated worker. What it means
Thrown during BackgroundJobWorkerManager.StartAsync when two dedicated worker configurations resolve to overlapping job names. This is a backstop: AddDedicatedWorker already rejects duplicate job *types* eagerly at registration time, but two distinct args types can share the same resolved job name (via BackgroundJobNameAttribute or JobOptions configuration). This check prevents two workers from competing for the same job at runtime.
Source
Thrown at framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorkerManager.cs:72
// for what can only be known here: two different args types that resolve to the same job name.
// Validate all configurations first, so a misconfiguration does not leave already-started workers running.
var dedicatedWorkers = new List<DedicatedWorkerDefinition>();
var allDedicatedJobNames = new List<string>();
// The default worker uses WorkerOptions.DistributedLockName, so dedicated workers must not reuse it.
var lockNames = new List<string> { WorkerOptions.DistributedLockName };
foreach (var configuration in WorkerOptions.WorkerConfigurations)
{
var jobNames = configuration.JobArgsTypes
.Select(GetJobName)
.Distinct()
.ToList();
var alreadyConfigured = jobNames.Intersect(allDedicatedJobNames).ToList();
if (alreadyConfigured.Any())
{
throw new AbpException(
$"The following background job(s) are configured for more than one dedicated worker: {string.Join(", ", alreadyConfigured)}. " +
$"Each job type can be handled by only one dedicated worker.");
}
if (lockNames.Contains(configuration.LockName))
{
throw new AbpException(
$"The distributed lock name '{configuration.LockName}' is used by more than one background job worker " +
$"(the default worker uses '{WorkerOptions.DistributedLockName}'). Each worker must have a unique lock name to run independently.");
}
lockNames.Add(configuration.LockName);
allDedicatedJobNames.AddRange(jobNames);
dedicatedWorkers.Add(new DedicatedWorkerDefinition(configuration.LockName, jobNames));
}
foreach (var dedicatedWorker in dedicatedWorkers)
{View on GitHub (pinned to 7ed43b1931)
Solutions
- Inspect the error's listed job names, find which args types share each name (check [BackgroundJobName] attributes and AddJob/JobName configuration), and ensure each job name maps to exactly one dedicated worker.
- Merge the two dedicated worker configurations into one by passing both args types to a single AddDedicatedWorker call.
- Give each args type a distinct job name by removing the conflicting [BackgroundJobName] attribute or registering unique names in AbpBackgroundJobOptions.
Example fix
// before — two types, same job name, two workers
context.Services.Configure<AbpBackgroundJobWorkerOptions>(opts =>
{
opts.AddDedicatedWorker<SharedNameJobAArgs>("lock-a");
opts.AddDedicatedWorker<SharedNameJobBArgs>("lock-b"); // both resolve to "shared-job"
});
// after — both types handled by one dedicated worker
context.Services.Configure<AbpBackgroundJobWorkerOptions>(opts =>
{
opts.AddDedicatedWorker<SharedNameJobAArgs, SharedNameJobBArgs>("lock-a");
}); Defensive patterns
Strategy: validation
Validate before calling
// Before StartAsync, verify no two dedicated workers share a resolved job name.
var jobOptions = serviceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value;
var workerOptions = serviceProvider.GetRequiredService<IOptions<AbpBackgroundJobWorkerOptions>>().Value;
var seenNames = new HashSet<string>();
foreach (var cfg in workerOptions.WorkerConfigurations)
{
foreach (var t in cfg.JobArgsTypes)
{
var name = jobOptions.GetJob(t).JobName;
if (!seenNames.Add(name))
throw new InvalidOperationException($"Job name '{name}' is assigned to more than one dedicated worker.");
}
} Try / catch
try { await backgroundJobWorkerManager.StartAsync(cancellationToken); }
catch (AbpException ex) when (ex.Message.Contains("configured for more than one dedicated worker"))
{
logger.LogError(ex, "Duplicate dedicated worker job assignment; check AddDedicatedWorker registrations.");
throw;
} Prevention
- Avoid sharing [BackgroundJobName] values across different args types that each get a dedicated worker.
- Consolidate job types that must run on the same worker into a single AddDedicatedWorker call.
- Add a startup health check that resolves job names for all WorkerConfigurations and asserts uniqueness.
When it happens
Trigger: Registering two dedicated workers where the args types are different but both resolve to the same JobName through BackgroundJobNameAttribute.GetNameOrNull or the AbpBackgroundJobOptions.GetJob mapping. For example, SharedNameJobAArgs and SharedNameJobBArgs both decorated with [BackgroundJobName("shared-job")] and assigned to separate dedicated workers.
Common situations: Two developers independently add [BackgroundJobName("email")] to different args types in separate modules, then each module registers its own dedicated worker. Refactoring a job's args type into a new type while forgetting to remove the old dedicated worker registration. Sharing a job name string across two modules via a constant.
Related errors
- The distributed lock name '{configuration.LockName}' is used
- No background job is registered for the args type '{argsType
- The background job args type '{duplicateType.FullName}' is a
- Background job execution is disabled. This method should not
- The distributed lock name '{lockName}' is already used by an
AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13).
Data as JSON: /api/errors/be3ea6fd73409526.
Report an issue: GitHub.