Skip to content

Reminder implementation

Reminders are durable schedules with volatile local execution. A reminder row is stored in an IReminderTable; the silo responsible for the grain’s consistent-ring range loads that row into LocalReminderService and runs the schedule locally. The table is the source of truth for registration, while the local ReminderData state is a cache and timer.

ReminderRegistry validates names, due times, periods, and the configured minimum period before forwarding the operation to the grain service for the grain’s current owner. LocalReminderService performs a responsibility check, upserts the row, and reconciles its local entry using the returned ETag. Conditional removal succeeds for the matching ETag and preserves newer registrations.

The reminder service is a grain service attached to the consistent ring. Ownership changes when membership changes; a new owner reconstructs responsibility from the durable row during its next table read.

After the reminder table starts successfully, the service refreshes the ring range periodically. Reads are staggered and retried during initialization. Each refresh reads the range, compares the table sequence with local mutations, starts or updates entries which are now owned, and stops entries which disappeared or moved elsewhere. A local sequence prevents a concurrent registration or removal from being overwritten by an older refresh result.

Reminder ownership uses periodic reconciliation. A brief ownership overlap during membership convergence is possible; ETags, responsibility checks, and the service’s delivery state converge the overlap toward the current owner.

Each local reminder reports one of three states: stopped, running, or tombstone. A stopped reminder keeps its run task inactive. A running reminder computes the next due time from the stored start time and period, waits using the reminder TimeProvider, and invokes the target grain through the normal messaging path. A tombstone records a stop reason while refresh reconciliation observes the scheduling decision. The service counts active deliveries, closes tick admission after shutdown begins, and waits for active deliveries to quiesce.

The provider durably stores the schedule, while owners generate individual ticks in memory. After owner or process failure, the next owner reconstructs the schedule and calculates the next delivery from current time. Applications reconcile timing gaps from durable business state, and idempotent callbacks tolerate duplicate execution.

The in-memory table is suitable for development only. Production tables provide the durability and concurrency behavior required for ownership changes, but their consistency and availability characteristics remain provider-specific. Table startup is part of silo lifecycle initialization; failure to open the configured table prevents normal reminder service startup.

The timers and reminders guide covers registration usage. Reminder persistence, ring movement, refresh intervals, and callback failures determine observed scheduling behavior.

Source: LocalReminderService, ReminderRegistry, and IReminderTable.