Browse Source

Fix UpdateScheduleAsync restart behavior and revert TickerQ IsRegistered

- Fix Hangfire/Quartz UpdateScheduleAsync to operate directly on persistent
  scheduler state without checking the in-memory registry, consistent with
  the RemoveAsync fix; this allows updating schedules after restart
- Revert TickerQ IsRegistered to return false instead of throwing;
  a boolean query method should not throw exceptions
- Update docs: note that UpdateScheduleAsync works after restart for
  persistent providers
pull/25059/head
maliming 6 months ago
parent
commit
a04e20c407
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      docs/en/framework/infrastructure/background-workers/index.md
  2. 8
      framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs
  3. 10
      framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs
  4. 5
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQDynamicBackgroundWorkerManager.cs

2
docs/en/framework/infrastructure/background-workers/index.md

@ -176,7 +176,7 @@ var updated = await dynamicWorkerManager.UpdateScheduleAsync(
* **`CronExpression` is only supported by scheduler-backed providers ([Hangfire](./hangfire.md), [Quartz](./quartz.md)).** The default in-memory provider requires `Period` and does not support `CronExpression` alone.
* **[TickerQ](./tickerq.md) does not support dynamic background workers** because it uses `FrozenDictionary` for function registration, which requires all functions to be registered before the application starts.
* `RemoveAsync` stops and removes a dynamic worker. Returns `true` if the worker was found and removed.
* `UpdateScheduleAsync` changes the schedule of an existing dynamic worker. Returns `true` if the worker was found and updated. The handler itself is not changed.
* `UpdateScheduleAsync` changes the schedule of an existing dynamic worker. Returns `true` if the worker was found and updated. The handler itself is not changed. For persistent providers (Hangfire, Quartz), this also works correctly after an application restart — the persistent scheduling record is updated even if the handler is no longer registered in memory.
> **Important:** Dynamic worker handlers are stored **in memory only** and are not persisted across application restarts. When using a persistent scheduler provider (Hangfire or Quartz), the recurring job entries remain in the database after a restart, but the handlers will no longer be registered. Until the handler is re-registered, each scheduled execution will be **skipped with a warning log**. To ensure handlers are always available, register them in `OnApplicationInitializationAsync` so they are re-registered on every startup.

8
framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs

@ -86,11 +86,6 @@ public class HangfireDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMa
schedule.Validate();
if (!HandlerRegistry.IsRegistered(workerName))
{
return Task.FromResult(false);
}
var cronExpression = schedule.CronExpression;
if (cronExpression.IsNullOrWhiteSpace())
{
@ -98,6 +93,9 @@ public class HangfireDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMa
cronExpression = GetCron(period);
}
// Always update the persistent recurring job regardless of in-memory registry state.
// This ensures UpdateScheduleAsync works correctly after an application restart,
// when the registry is empty but the Hangfire recurring job may still exist in the database.
ScheduleRecurringJob(workerName, cronExpression, cancellationToken);
return Task.FromResult(true);

10
framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs

@ -92,11 +92,6 @@ public class QuartzDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMana
schedule.Validate();
if (!HandlerRegistry.IsRegistered(workerName))
{
return false;
}
var triggerKey = new TriggerKey($"DynamicWorker:{workerName}");
var jobKey = new JobKey($"DynamicWorker:{workerName}");
var jobDetail = JobBuilder.Create<QuartzDynamicBackgroundWorkerAdapter>()
@ -105,6 +100,11 @@ public class QuartzDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMana
.Build();
var trigger = BuildTrigger(schedule, jobDetail, triggerKey);
// Always attempt to reschedule the persistent job regardless of in-memory registry state.
// This ensures UpdateScheduleAsync works correctly after an application restart,
// when the registry is empty but the Quartz job may still exist in the scheduler store.
// RescheduleJob returns null if the trigger was not found, indicating the job did not exist.
var result = await Scheduler.RescheduleJob(triggerKey, trigger, cancellationToken);
return result != null;
}

5
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQDynamicBackgroundWorkerManager.cs

@ -38,9 +38,8 @@ public class TickerQDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMan
public virtual bool IsRegistered(string workerName)
{
throw new AbpException(
"TickerQ does not support dynamic background worker registration at runtime. " +
"Please use Hangfire or Quartz provider for dynamic background workers.");
// TickerQ does not support runtime registration, so there are never any registered workers.
return false;
}
public virtual Task StopAllAsync(CancellationToken cancellationToken = default)

Loading…
Cancel
Save