Skip to content

feat: adaptive retry with configurable backoff strategies#42

Merged
deepjoy merged 7 commits into
mainfrom
adaptive-retry
Mar 16, 2026
Merged

feat: adaptive retry with configurable backoff strategies#42
deepjoy merged 7 commits into
mainfrom
adaptive-retry

Conversation

@deepjoy

@deepjoy deepjoy commented Mar 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add configurable backoff strategies (Constant, Linear, Exponential, ExponentialJitter) and per-task-type RetryPolicy registration via SchedulerBuilder::executor_with_retry_policy()
  • Persist per-task max_retries in the schema (migration 008_retry_backoff.sql) so retry behavior is deterministic across restarts and policy changes
  • Compute backoff delays in the store fail path using the existing run_after column — zero new scheduling infrastructure needed
  • Support executor-signaled retry delays via TaskError::retryable("...").retry_after(Duration) for honoring upstream Retry-After headers
  • Add HistoryStatus::DeadLetter for tasks that exhaust retries (distinct from permanent Failed), with Scheduler::dead_letter_tasks() query and Scheduler::retry_dead_letter() for manual resubmission
  • Emit SchedulerEvent::DeadLettered and add retry_after field to SchedulerEvent::Failed
  • Update migration guide (docs/migrating-to-0.4.md) with all breaking changes and new APIs
  • Fix all 23 pre-existing rustdoc warnings (private intra-doc links, unresolved paths, redundant explicit targets)

Fixes #13

Breaking changes

  • SchedulerEvent::Failed gains retry_after: Option<Duration> field — exhaustive matches must be updated
  • SchedulerEvent::DeadLettered new variant — exhaustive matches must add an arm
  • HistoryStatus::DeadLetter new variant — exhaustive matches must add an arm
  • TaskError gains retry_after_ms: Option<u64> field — struct literal construction must include it (constructors unaffected)

Test plan

  • Unit tests for BackoffStrategy::delay_for() across all strategies (constant, linear, exponential, jitter) including clamping and edge cases
  • Unit tests for registry policy storage (register, lookup, missing returns None)
  • Store tests: migration applies cleanly, max_retries round-trips, NULL backward compat
  • Store tests: backoff delay sets correct run_after for Constant and Exponential strategies
  • Store tests: executor retry_after override takes precedence over strategy
  • Store tests: exhausted retries produce dead_letter status; non-retryable produces failed
  • Store tests: dead_letter_tasks() returns only dead-lettered records
  • Integration test: per-type policy overrides global default
  • Integration test: exponential backoff delays redispatch until delay elapses
  • Integration test: Failed event includes correct retry_after duration
  • Integration test: NULL max_retries falls back to global default
  • Integration test: retry_dead_letter() resubmits with reset retry count
  • All 46 integration tests pass
  • All 167 unit tests pass
  • All benchmarks pass with no regressions
  • cargo doc builds with zero warnings under RUSTDOCFLAGS="-D warnings"

deepjoy added 7 commits March 15, 2026 16:18
Introduce BackoffStrategy enum (Constant, Linear, Exponential,
ExponentialJitter) with delay_for() computation, RetryPolicy struct,
TaskError.retry_after() builder for executor-signaled delays, and
TaskError::permanent() convenience constructor. Add fastrand dependency
for jitter randomization.
…13, phase 2)

Add RetryPolicy storage to TaskTypeRegistry with register, erased-register,
and lookup methods mirroring the existing TTL pattern. Extend SchedulerBuilder
with executor_with_retry_policy() and executor_with_options() to wire policies
at build time.
Add max_retries column to tasks and task_history tables via migration
008, enabling per-task retry limits resolved at submit time from
per-type retry policies or the global default. NULL preserves backward
compatibility with pre-migration tasks.
Extend fail_inner() to compute a retry delay from the backoff strategy
or executor override and set run_after on requeued tasks. Introduces
FailBackoff struct to bundle backoff parameters and keep function
signatures under the clippy argument-count lint.
Tasks that fail with a retryable error but exhaust their max_retries now
move to a distinct `dead_letter` history status instead of `failed`.
This lets consumers differentiate "permanently broken" (failed) from
"might succeed later" (dead_letter).

- Add `HistoryStatus::DeadLetter` variant mapping to/from `"dead_letter"`
- Update `fail_inner()` to use `dead_letter` for exhausted retries
- Add `TaskStore::dead_letter_tasks()` and `Scheduler::dead_letter_tasks()`
- Add `Scheduler::retry_dead_letter(id)` to re-submit from dead-letter
- Add `SchedulerEvent::DeadLettered` variant emitted on retry exhaustion
- Add `StoreError::NotFound` and `StoreError::InvalidState` variants
- Add `TaskStore::delete_history()` for cleanup on re-submission
…e 6)

Wire per-type RetryPolicy lookup into the dispatch error handler so that
backoff strategies and per-type max_retries are applied at task failure
time. Add retry_after field to SchedulerEvent::Failed for observability.

Key changes:
- Add registry to SpawnContext for per-type policy resolution
- Resolve effective max_retries: per-task column → per-type policy → global
- Pass resolved backoff strategy to fail_with_record()
- Emit retry_after duration in Failed events
- Fix subsecond backoff precision (use millisecond-resolution run_after)
- Update peek/pop queries to use strftime for subsecond comparison
- Integration tests for per-type override, backoff delays, event fields,
  and backward compat with NULL max_retries
…nings (#13, phase 7)

Phase 7 of adaptive retry: update migrating-to-0.4.md with breaking
changes (Failed.retry_after, DeadLettered variant, HistoryStatus::DeadLetter,
TaskError.retry_after_ms, new builder methods, dead-letter APIs), update
progress-and-events.md, and expand lib.rs crate docs with backoff/dead-letter
coverage.

Fix all 23 rustdoc warnings: qualify public cross-module links with full
paths (Scheduler::subscribe, TaskStore::tasks_by_tags, etc.), use plain
backticks for private items (IoTracker, ActiveTaskMap, fail_inner, etc.),
and remove redundant explicit link targets.
@deepjoy deepjoy enabled auto-merge (squash) March 16, 2026 04:42
@deepjoy deepjoy merged commit 91c464d into main Mar 16, 2026
1 check passed
@github-actions github-actions Bot mentioned this pull request Mar 15, 2026
deepjoy pushed a commit that referenced this pull request Mar 18, 2026
## 🤖 New release

* `taskmill`: 0.3.1 -> 0.4.0 (⚠ API breaking changes)

### ⚠ `taskmill` breaking changes

```text
--- failure constructible_struct_adds_field: externally-constructible struct adds field ---

Description:
A pub struct constructible with a struct literal has a new pub field. Existing struct literals must be updated to include the new field.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field TaskHistoryRecord.expected_io in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:290
  field TaskHistoryRecord.actual_io in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:292
  field TaskHistoryRecord.ttl_seconds in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:306
  field TaskHistoryRecord.ttl_from in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:308
  field TaskHistoryRecord.expires_at in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:310
  field TaskHistoryRecord.run_after in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:312
  field TaskHistoryRecord.tags in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:314
  field TaskHistoryRecord.max_retries in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:316
  field TaskHistoryRecord.expected_io in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:290
  field TaskHistoryRecord.actual_io in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:292
  field TaskHistoryRecord.ttl_seconds in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:306
  field TaskHistoryRecord.ttl_from in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:308
  field TaskHistoryRecord.expires_at in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:310
  field TaskHistoryRecord.run_after in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:312
  field TaskHistoryRecord.tags in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:314
  field TaskHistoryRecord.max_retries in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:316
  field TaskError.cancelled in /tmp/.tmp9SeJRW/taskmill/src/task/error.rs:28
  field TaskError.retry_after_ms in /tmp/.tmp9SeJRW/taskmill/src/task/error.rs:33
  field TaskError.cancelled in /tmp/.tmp9SeJRW/taskmill/src/task/error.rs:28
  field TaskError.retry_after_ms in /tmp/.tmp9SeJRW/taskmill/src/task/error.rs:33
  field EstimatedProgress.header in /tmp/.tmp9SeJRW/taskmill/src/scheduler/progress.rs:299
  field EstimatedProgress.header in /tmp/.tmp9SeJRW/taskmill/src/scheduler/progress.rs:299
  field EstimatedProgress.header in /tmp/.tmp9SeJRW/taskmill/src/scheduler/progress.rs:299
  field SchedulerSnapshot.byte_progress in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:45
  field SchedulerSnapshot.recurring_schedules in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:49
  field SchedulerSnapshot.blocked_count in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:51
  field SchedulerSnapshot.byte_progress in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:45
  field SchedulerSnapshot.recurring_schedules in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:49
  field SchedulerSnapshot.blocked_count in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:51
  field TaskSubmission.expected_io in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:382
  field TaskSubmission.on_duplicate in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:399
  field TaskSubmission.ttl in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:403
  field TaskSubmission.ttl_from in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:406
  field TaskSubmission.run_after in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:413
  field TaskSubmission.recurring in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:416
  field TaskSubmission.dependencies in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:419
  field TaskSubmission.on_dependency_failure in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:422
  field TaskSubmission.tags in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:427
  field TaskSubmission.max_retries in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:432
  field TaskSubmission.expected_io in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:382
  field TaskSubmission.on_duplicate in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:399
  field TaskSubmission.ttl in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:403
  field TaskSubmission.ttl_from in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:406
  field TaskSubmission.run_after in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:413
  field TaskSubmission.recurring in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:416
  field TaskSubmission.dependencies in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:419
  field TaskSubmission.on_dependency_failure in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:422
  field TaskSubmission.tags in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:427
  field TaskSubmission.max_retries in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:432
  field SchedulerConfig.progress_interval in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:252
  field SchedulerConfig.cancel_hook_timeout in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:256
  field SchedulerConfig.default_ttl in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:259
  field SchedulerConfig.expiry_sweep_interval in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:262
  field SchedulerConfig.progress_interval in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:252
  field SchedulerConfig.cancel_hook_timeout in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:256
  field SchedulerConfig.default_ttl in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:259
  field SchedulerConfig.expiry_sweep_interval in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:262
  field TaskRecord.expected_io in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:202
  field TaskRecord.ttl_seconds in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:219
  field TaskRecord.ttl_from in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:221
  field TaskRecord.expires_at in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:223
  field TaskRecord.run_after in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:226
  field TaskRecord.recurring_interval_secs in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:228
  field TaskRecord.recurring_max_executions in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:230
  field TaskRecord.recurring_execution_count in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:232
  field TaskRecord.recurring_paused in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:234
  field TaskRecord.dependencies in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:237
  field TaskRecord.on_dependency_failure in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:239
  field TaskRecord.tags in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:241
  field TaskRecord.max_retries in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:245
  field TaskRecord.expected_io in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:202
  field TaskRecord.ttl_seconds in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:219
  field TaskRecord.ttl_from in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:221
  field TaskRecord.expires_at in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:223
  field TaskRecord.run_after in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:226
  field TaskRecord.recurring_interval_secs in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:228
  field TaskRecord.recurring_max_executions in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:230
  field TaskRecord.recurring_execution_count in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:232
  field TaskRecord.recurring_paused in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:234
  field TaskRecord.dependencies in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:237
  field TaskRecord.on_dependency_failure in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:239
  field TaskRecord.tags in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:241
  field TaskRecord.max_retries in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:245

--- failure constructible_struct_adds_private_field: struct no longer constructible due to new private field ---

Description:
A struct constructible with a struct literal has a new non-public field. It can no longer be constructed using a struct literal outside of its crate.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/constructible_struct_adds_private_field.ron

Failed in:
  field TaskSubmission.payload_error in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:410
  field TaskSubmission.payload_error in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:410

--- failure enum_struct_variant_changed_kind: An enum struct variant changed kind ---

Description:
A pub enum's struct variant with at least one pub field has changed to a different kind of enum variant, breaking access to its pub fields.
        ref: https://doc.rust-lang.org/reference/items/enumerations.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_struct_variant_changed_kind.ron

Failed in:
  variant SchedulerEvent::Dispatched in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:85
  variant SchedulerEvent::Completed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:87
  variant SchedulerEvent::Preempted in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:98
  variant SchedulerEvent::Cancelled in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:100
  variant SchedulerEvent::Dispatched in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:85
  variant SchedulerEvent::Completed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:87
  variant SchedulerEvent::Preempted in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:98
  variant SchedulerEvent::Cancelled in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:100

--- failure enum_struct_variant_field_added: pub enum struct variant field added ---

Description:
An enum's exhaustive struct variant has a new field, which has to be included when constructing or matching on this variant.
        ref: https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_struct_variant_field_added.ron

Failed in:
  field header of variant SchedulerEvent::Failed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:90
  field retry_after of variant SchedulerEvent::Failed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:95
  field header of variant SchedulerEvent::Progress in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:110
  field header of variant SchedulerEvent::Failed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:90
  field retry_after of variant SchedulerEvent::Failed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:95
  field header of variant SchedulerEvent::Progress in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:110

--- failure enum_struct_variant_field_missing: pub enum struct variant's field removed or renamed ---

Description:
A publicly-visible enum has a struct variant whose field is no longer available under its prior name. It may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_struct_variant_field_missing.ron

Failed in:
  field task_id of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:63
  field task_type of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:64
  field key of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:65
  field label of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:66
  field task_id of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:86
  field task_type of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:87
  field key of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:88
  field label of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:89
  field task_id of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:63
  field task_type of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:64
  field key of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:65
  field label of variant SchedulerEvent::Failed, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:66
  field task_id of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:86
  field task_type of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:87
  field key of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:88
  field label of variant SchedulerEvent::Progress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/event.rs:89

--- failure enum_variant_added: enum variant added on exhaustive enum ---

Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_variant_added.ron

Failed in:
  variant StoreError:InvalidDependency in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:54
  variant StoreError:DependencyFailed in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:56
  variant StoreError:CyclicDependency in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:58
  variant StoreError:InvalidTag in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:60
  variant StoreError:NotFound in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:62
  variant StoreError:InvalidState in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:64
  variant StoreError:InvalidDependency in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:54
  variant StoreError:DependencyFailed in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:56
  variant StoreError:CyclicDependency in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:58
  variant StoreError:InvalidTag in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:60
  variant StoreError:NotFound in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:62
  variant StoreError:InvalidState in /tmp/.tmp9SeJRW/taskmill/src/store/mod.rs:64
  variant SubmitOutcome:Superseded in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:113
  variant SubmitOutcome:Rejected in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:120
  variant SubmitOutcome:Superseded in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:113
  variant SubmitOutcome:Rejected in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:120
  variant SchedulerEvent:Superseded in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:102
  variant SchedulerEvent:BatchSubmitted in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:120
  variant SchedulerEvent:TaskExpired in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:127
  variant SchedulerEvent:RecurringSkipped in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:134
  variant SchedulerEvent:RecurringCompleted in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:140
  variant SchedulerEvent:TaskUnblocked in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:147
  variant SchedulerEvent:DeadLettered in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:153
  variant SchedulerEvent:DependencyFailed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:159
  variant SchedulerEvent:Superseded in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:102
  variant SchedulerEvent:BatchSubmitted in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:120
  variant SchedulerEvent:TaskExpired in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:127
  variant SchedulerEvent:RecurringSkipped in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:134
  variant SchedulerEvent:RecurringCompleted in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:140
  variant SchedulerEvent:TaskUnblocked in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:147
  variant SchedulerEvent:DeadLettered in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:153
  variant SchedulerEvent:DependencyFailed in /tmp/.tmp9SeJRW/taskmill/src/scheduler/event.rs:159
  variant TaskStatus:Blocked in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:106
  variant TaskStatus:Blocked in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:106
  variant HistoryStatus:Cancelled in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:142
  variant HistoryStatus:Superseded in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:143
  variant HistoryStatus:Expired in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:144
  variant HistoryStatus:DependencyFailed in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:147
  variant HistoryStatus:DeadLetter in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:154
  variant HistoryStatus:Cancelled in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:142
  variant HistoryStatus:Superseded in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:143
  variant HistoryStatus:Expired in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:144
  variant HistoryStatus:DependencyFailed in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:147
  variant HistoryStatus:DeadLetter in /tmp/.tmp9SeJRW/taskmill/src/task/mod.rs:154

--- failure inherent_method_missing: pub method removed or renamed ---

Description:
A publicly-visible method or associated fn is no longer available under its prior name. It may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/inherent_method_missing.ron

Failed in:
  TaskSubmission::expected_net_io, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:186
  TaskSubmission::with_payload, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:231
  TaskSubmission::expected_net_io, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:186
  TaskSubmission::with_payload, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:231
  TaskContext::submit, previously in file /tmp/.tmptg1HQQ/taskmill/src/registry/context.rs:140
  TaskContext::submit_typed, previously in file /tmp/.tmptg1HQQ/taskmill/src/registry/context.rs:151
  TaskContext::submit_typed_at, previously in file /tmp/.tmptg1HQQ/taskmill/src/registry/context.rs:160
  TaskContext::submit, previously in file /tmp/.tmptg1HQQ/taskmill/src/registry/context.rs:140
  TaskContext::submit_typed, previously in file /tmp/.tmptg1HQQ/taskmill/src/registry/context.rs:151
  TaskContext::submit_typed_at, previously in file /tmp/.tmptg1HQQ/taskmill/src/registry/context.rs:160
  SchedulerBuilder::executor, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/builder.rs:97
  SchedulerBuilder::typed_executor, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/builder.rs:108
  SchedulerBuilder::executor, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/builder.rs:97
  SchedulerBuilder::typed_executor, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/builder.rs:108

--- failure method_parameter_count_changed: pub method parameter count changed ---

Description:
A publicly-visible method now takes a different number of parameters, not counting the receiver (self) parameter.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/method_parameter_count_changed.ron

Failed in:
  taskmill::task::TaskSubmission::expected_io now takes 1 parameters instead of 2, in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:525
  taskmill::TaskSubmission::expected_io now takes 1 parameters instead of 2, in /tmp/.tmp9SeJRW/taskmill/src/task/submission.rs:525
  taskmill::store::TaskStore::fail now takes 6 parameters instead of 5, in /tmp/.tmp9SeJRW/taskmill/src/store/lifecycle/fail.rs:29
  taskmill::store::TaskStore::fail_with_record now takes 6 parameters instead of 5, in /tmp/.tmp9SeJRW/taskmill/src/store/lifecycle/fail.rs:72
  taskmill::TaskStore::fail now takes 6 parameters instead of 5, in /tmp/.tmp9SeJRW/taskmill/src/store/lifecycle/fail.rs:29
  taskmill::TaskStore::fail_with_record now takes 6 parameters instead of 5, in /tmp/.tmp9SeJRW/taskmill/src/store/lifecycle/fail.rs:72

--- failure struct_missing: pub struct removed or renamed ---

Description:
A publicly-visible struct cannot be imported by its prior path. A `pub use` may have been removed, or the struct itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/struct_missing.ron

Failed in:
  struct taskmill::task::TaskMetrics, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:192
  struct taskmill::TaskMetrics, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:192

--- failure struct_pub_field_missing: pub struct's pub field removed or renamed ---

Description:
A publicly-visible struct has at least one public field that is no longer available under its prior name. It may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/struct_pub_field_missing.ron

Failed in:
  field expected_read_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:67
  field expected_write_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:68
  field expected_net_rx_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:70
  field expected_net_tx_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:72
  field expected_read_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:67
  field expected_write_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:68
  field expected_net_rx_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:70
  field expected_net_tx_bytes of struct TaskSubmission, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/submission.rs:72
  field expected_read_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:110
  field expected_write_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:111
  field expected_net_rx_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:113
  field expected_net_tx_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:115
  field expected_read_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:110
  field expected_write_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:111
  field expected_net_rx_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:113
  field expected_net_tx_bytes of struct TaskRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:115
  field expected_read_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:158
  field expected_write_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:159
  field expected_net_rx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:161
  field expected_net_tx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:163
  field actual_read_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:164
  field actual_write_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:165
  field actual_net_rx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:167
  field actual_net_tx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:169
  field expected_read_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:158
  field expected_write_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:159
  field expected_net_rx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:161
  field expected_net_tx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:163
  field actual_read_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:164
  field actual_write_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:165
  field actual_net_rx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:167
  field actual_net_tx_bytes of struct TaskHistoryRecord, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/mod.rs:169
  field task_id of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:104
  field task_type of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:105
  field key of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:106
  field label of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:107
  field task_id of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:104
  field task_type of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:105
  field key of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:106
  field label of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:107
  field task_id of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:104
  field task_type of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:105
  field key of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:106
  field label of struct EstimatedProgress, previously in file /tmp/.tmptg1HQQ/taskmill/src/scheduler/progress.rs:107

--- failure trait_method_missing: pub trait method removed or renamed ---

Description:
A trait method is no longer callable, and may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#major-any-change-to-trait-item-signatures
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/trait_method_missing.ron

Failed in:
  method expected_read_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:38
  method expected_write_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:43
  method expected_net_rx_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:48
  method expected_net_tx_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:53
  method expected_read_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:38
  method expected_write_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:43
  method expected_net_rx_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:48
  method expected_net_tx_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:53
  method expected_read_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:38
  method expected_write_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:43
  method expected_net_rx_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:48
  method expected_net_tx_bytes of trait TypedTask, previously in file /tmp/.tmptg1HQQ/taskmill/src/task/typed.rs:53
```

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

## [0.4.0](v0.3.1...v0.4.0)
- 2026-03-18

### Added

- implement multi-module API
([#44](#44))
- adaptive retry with configurable backoff strategies
([#42](#42))
- add task metadata tags
([#40](#40))
- task dependencies with blocked status, cycle detection, and failure
cascading ([#39](#39))
- delayed and recurring task scheduling
([#38](#38))
- task TTL with automatic expiry, sweep, and child inheritance
([#33](#33))
- task superseding with atomic cancel-and-replace
([#32](#32))
- task cancellation with abort hooks
([#31](#31))
- bulk task submission with BatchOutcome, BatchSubmission builder,
intra-batch dedup, and chunking
([#30](#30))
- add byte-level progress reporting with EWMA throughput tracking
([#29](#29))

### Other

- split large store modules into focused sub-modules
([#43](#43))
- rewrite documentation to be user-facing
([#28](#28))
- [**breaking**] consolidate IO fields into IoBudget and introduce
TaskEventHeader ([#26](#26))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Adaptive retry with configurable backoff strategies

1 participant