Skip to content

feat(java): per-task retry backoff via core RetryPolicy#311

Merged
pratyush618 merged 2 commits into
masterfrom
feat/java-retry-backoff
Jun 27, 2026
Merged

feat(java): per-task retry backoff via core RetryPolicy#311
pratyush618 merged 2 commits into
masterfrom
feat/java-retry-backoff

Conversation

@pratyush618

@pratyush618 pratyush618 commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

What

Per-task retry-backoff config for the Java SDK: Task.retryPolicy(RetryPolicy) with exponential(base, max), fixed(delay), and explicit delays(...).

Why this is translation, not emulation

The core scheduler already owns a full retry engine — RetryPolicy { base_delay_ms, max_delay_ms, custom_delays_ms } + next_retry_at() in taskito-core, the same one Python/Node use. The Java gap was only that the worker never fed it per-task policies.

So this surfaces the existing engine through the binding instead of reinventing it. Zero taskito-core changes. Retries stay in the core scheduler → durable, atomic, timeout-safe; no Java-side catch-and-re-enqueue, no unique-key collisions, and RETRY outcomes fire natively.

How

  • Worker.Builder collects each registered task's RetryPolicy, encodes it into the worker-start options JSON (taskConfigs).
  • crates/taskito-java (convert.rs, worker.rs): parses taskConfigs, calls Scheduler::register_task with the backoff curve before the scheduler loop starts. Unset fields keep core defaults; the retry budget still travels via per-job maxRetries.

Verification

RetryPolicyTest (live, JNI-backed): a handler that fails twice then succeeds runs exactly 3 times, the core emits 2 RETRY outcomes, and the result resolves — proving the native retry path end-to-end. Full ./gradlew build green (Spotless + Checkstyle + suite), cargo clippy clean.

Summary by CodeRabbit

  • New Features
    • Added per-task retry backoff options for Java tasks, including fixed, exponential, and custom delay sequences.
    • Workers now carry task-specific retry settings into startup configuration.
  • Bug Fixes
    • Retry behavior now follows the configured backoff policy for each task instead of using only default scheduling behavior.
  • Tests
    • Added coverage confirming tasks retry the expected number of times and eventually succeed with the configured policy.

Surface the core scheduler's existing RetryPolicy (base/max delay, custom per-attempt delays) through the worker-start binding. Retries stay in the core engine — durable, atomic, timeout-safe — so no Java re-enqueue emulation and no core changes.
@github-actions github-actions Bot added the rust label Jun 27, 2026
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pratyush618, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 5 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68ed3b27-ef8e-435c-96bb-531fe0fa5d05

📥 Commits

Reviewing files that changed from the base of the PR and between 597f7f9 and c3ec4f6.

📒 Files selected for processing (2)
  • crates/taskito-java/src/worker.rs
  • sdks/java/src/main/java/org/byteveda/taskito/task/RetryPolicy.java
📝 Walkthrough

Walkthrough

Java task descriptors can now carry retry backoff policies, worker startup options serialize those policies, and the Rust worker converts them into scheduler registrations. A new Java test exercises retries until a task succeeds.

Changes

Retry policy wiring

Layer / File(s) Summary
RetryPolicy model and task field
sdks/java/src/main/java/org/byteveda/taskito/task/RetryPolicy.java, sdks/java/src/main/java/org/byteveda/taskito/task/Task.java
RetryPolicy adds exponential, fixed, and explicit-delay factories with validation and getters; Task<T> stores an optional retryPolicy, preserves it across copies, and exposes a fluent setter and accessor.
Worker builder encoding
sdks/java/src/main/java/org/byteveda/taskito/worker/Worker.java
Worker.Builder captures per-task retry policies during handler registration and serializes them into taskConfigs with name, baseDelayMs, maxDelayMs, and customDelaysMs.
JNI options and scheduler registration
crates/taskito-java/src/convert.rs, crates/taskito-java/src/worker.rs
WorkerOptions now carries task_configs as TaskRetryConfig, and the Rust worker registers each configured task policy with Scheduler through register_task_policies before sharing it with runtime tasks.
Retry policy integration test
sdks/java/src/test/java/org/byteveda/taskito/RetryPolicyTest.java
A new test runs a failing task with a retry policy and asserts the task succeeds after two retries, three handler invocations, two RETRY outcomes, and a persisted result of "42".

Sequence Diagram(s)

sequenceDiagram
  participant TaskT as Task<T>
  participant WorkerBuilder as Worker.Builder
  participant QueueBackend
  participant StartWorker as start_worker
  participant Scheduler

  WorkerBuilder->>TaskT: capturePolicy(task) reads retryPolicy()
  WorkerBuilder->>QueueBackend: encodeOptions() adds taskConfigs
  QueueBackend->>StartWorker: WorkerOptions.task_configs
  StartWorker->>Scheduler: register_task_policies(options.task_configs)
  Scheduler->>Scheduler: register_task(name, TaskConfig)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A carrot-bright policy hopped in a line,
From Java to Rust, the delays align.
Two retries later, the task took a bow,
This bunny says “boing” and “success” now. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: per-task retry backoff support in the Java SDK via core RetryPolicy.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/java-retry-backoff

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/taskito-java/src/worker.rs`:
- Around line 137-139: The custom retry delay handling in worker.rs still
inherits the default RetryPolicy jitter, which causes custom delays to drift and
can make retries after the custom list run immediately. Update the retry setup
around the `RetryPolicy::default()` initialization and the `custom_delays_ms`
assignment so that custom delays remain exact, either by disabling jitter only
when a custom delay is chosen or by moving custom-delay curve handling into the
core retry policy logic.

In `@sdks/java/src/main/java/org/byteveda/taskito/task/RetryPolicy.java`:
- Around line 36-40: The RetryPolicy.fixed() path is misleading because it still
goes through the retry jitter logic, so a requested constant delay is actually
randomized. Update RetryPolicy.fixed() and the retry scheduling code it feeds so
this method truly produces a fixed no-jitter delay, or rename/document it to
reflect the jittered behavior; use the RetryPolicy.fixed(Duration) factory and
the retry delay computation in the same class to locate the fix.
- Around line 42-55: `RetryPolicy.delays(...)` is not being honored as exact
per-attempt delays because the retry core falls back to exponential/default
delay handling after the list is exhausted and also applies default base/max
jitter behavior. Update the `delays(...)` factory and the retry delay selection
logic so explicit delays from `RetryPolicy` repeat the last value for all later
attempts and bypass any exponential backoff/default jitter path. Use the
existing `RetryPolicy.delays(...)`, `baseDelay`, `maxDelay`, and
delay-computation code paths to make the custom list fully authoritative.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9691c79c-f3bd-4d10-8f8a-c06b3e085dc0

📥 Commits

Reviewing files that changed from the base of the PR and between 7551eaa and 597f7f9.

📒 Files selected for processing (6)
  • crates/taskito-java/src/convert.rs
  • crates/taskito-java/src/worker.rs
  • sdks/java/src/main/java/org/byteveda/taskito/task/RetryPolicy.java
  • sdks/java/src/main/java/org/byteveda/taskito/task/Task.java
  • sdks/java/src/main/java/org/byteveda/taskito/worker/Worker.java
  • sdks/java/src/test/java/org/byteveda/taskito/RetryPolicyTest.java

Comment thread crates/taskito-java/src/worker.rs Outdated
Comment thread sdks/java/src/main/java/org/byteveda/taskito/task/RetryPolicy.java Outdated
Comment thread sdks/java/src/main/java/org/byteveda/taskito/task/RetryPolicy.java
The core derives jitter and post-exhaustion fallback from base_delay_ms, so custom delays inherited the 1s default jitter. Zero base/max for the custom-delay path → listed delays are exact. Drop the misleading fixed() factory (the core always jitters exponential delays) and document the jitter on exponential().
@pratyush618 pratyush618 merged commit cbb3aac into master Jun 27, 2026
29 checks passed
@pratyush618 pratyush618 deleted the feat/java-retry-backoff branch June 27, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant