Skip to content

Schedule periodic tasks

You’ll run a beat process that enqueues a nightly report, a heartbeat every minute and an office-hours sync, and that stays correct if you start it twice or restart it.

  • Redis and a worker consuming the queues the tasks go to.
  • The task names registered in the worker.
import 'package:gisila_queue/gisila_queue.dart';
import 'package:gisila_queue/gisila_queue_cli.dart';
Future<void> main(List<String> argv) async {
final cli = BeatCli.parse(argv);
if (cli.help) return print(BeatCli.usage);
final app = cli.buildApp();
final beat = cli.buildBeat(app)
..schedule('nightly-report', 'reports.nightly', Schedule.dailyAt(2, minute: 30))
..schedule('heartbeat', 'health.ping', Schedule.every(const Duration(minutes: 1)))
..schedule('crm-sync', 'sync.crm', Schedule.cron('*/15 9-17 * * 1-5'),
kwargs: {'full': false}, queue: 'low');
await cli.run(beat);
}

Beat only sends task names; it doesn’t need the handlers. BeatCli wires a RedisBeatLock and RedisBeatStateStore.

With GisilaQueue.redis the lock and state store come with the app:

import 'package:gisila_queue/gisila_queue.dart';
import 'package:gisila_queue/gisila_queue_cli.dart' show installShutdownHooks;
final app = GisilaQueue.redis(namespace: 'shop');
final beat = Beat(app)
..schedule('heartbeat', 'health.ping', Schedule.every(const Duration(minutes: 1)));
installShutdownHooks(beat.stop);
await beat.run();
await app.close();
Need Schedule
Roughly every N Schedule.every(Duration) (fires on first start, then every N)
Once a day at a time Schedule.dailyAt(hour, minute: m)
Calendar rules Schedule.cron('min hour dom month dow')

Times are the beat host’s local time. Set the process TZ if it matters.

Beat sends with maxRetries: 0. If a periodic job needs retries, schedule a tiny task that enqueues the real one through its TaskRef:

final nightly = app.task<void>('reports.nightly.run', buildReport, maxRetries: 5, autoRetry: true);
app.task<void>('reports.nightly', (ctx) async {
await nightly.delay();
return null;
});
Terminal window
dart run bin/beat.dart --namespace=shop --lock-ttl=30

Starting a second copy is safe: it waits until the first stops or dies (after --lock-ttl), then takes over with the persisted last-run times.

Terminal window
redis-cli GET shop:beat:lock # the active beat's id (host:pid)
redis-cli HGETALL shop:beat:lastrun # entry name → last fire time

The worker should log task health.ping ✓ once a minute.