Skip to content

feat: persist per-subscription delivery settings#415

Merged
pratyush618 merged 2 commits into
masterfrom
feat/pubsub-delivery-settings
Jul 12, 2026
Merged

feat: persist per-subscription delivery settings#415
pratyush618 merged 2 commits into
masterfrom
feat/pubsub-delivery-settings

Conversation

@pratyush618

@pratyush618 pratyush618 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Persists each subscriber's own delivery settings (priority, max retries, timeout) on the subscription record, so a producer that never loaded the subscriber's task still applies them
  • Closes the cross-process gap: previously these settings only applied when the publishing process had the subscriber task registered
  • Resolution order per field: explicit publish override > subscription-row setting > queue default

Changes

  • Core: three nullable columns (priority, max_retries, timeout_ms) on topic_subscriptions, populated at registration; publish_to_topic resolves delivery settings from the row. Drops the in-process task_defaults map — the row is a strict superset (works cross-process and in-process)
  • Backward compatible: nullable columns backfilled via ALTER; pre-existing subscriptions resolve to queue defaults exactly as before
  • Bindings: Python/Node forward the subscriber task's options into registration; Java threads them through the JNI + SPI layers (nullable via a MIN sentinel)

Test plan

  • Rust: subscription upsert refreshes settings while preserving paused state; publish resolves override > row > queue default (contract suite, all backends)
  • Python: cross-process regression — a second Queue that never registered the task publishes and the delivery carries the subscriber's persisted settings
  • Node + Java: equivalent cross-process tests; full suites green

Summary by CodeRabbit

  • New Features

    • Subscription-specific priority, retry, and timeout settings can now be configured across Java, Node.js, and Python integrations.
    • These settings persist with subscriptions and apply when publishing from a separate process or queue instance.
    • Publish-time overrides take precedence over subscription settings, followed by queue defaults.
  • Bug Fixes

    • Updated subscription registration to reliably save and update delivery settings across supported storage backends.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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 reviews.

How do review 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 refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f1dd641-b2c1-47aa-9dad-fe09522001de

📥 Commits

Reviewing files that changed from the base of the PR and between 1da1e47 and 25598f4.

📒 Files selected for processing (30)
  • crates/taskito-core/src/pubsub.rs
  • crates/taskito-core/src/storage/diesel_common/migrations.rs
  • crates/taskito-core/src/storage/models.rs
  • crates/taskito-core/src/storage/postgres/pubsub.rs
  • crates/taskito-core/src/storage/redis_backend/pubsub.rs
  • crates/taskito-core/src/storage/schema.rs
  • crates/taskito-core/src/storage/sqlite/pubsub.rs
  • crates/taskito-core/src/storage/sqlite/tests.rs
  • crates/taskito-core/tests/rust/storage_tests.rs
  • crates/taskito-java/src/convert.rs
  • crates/taskito-java/src/queue/pubsub.rs
  • crates/taskito-java/src/worker.rs
  • crates/taskito-node/src/config.rs
  • crates/taskito-node/src/queue/pubsub.rs
  • crates/taskito-python/src/py_queue/pubsub.rs
  • sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/NativeQueue.java
  • sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/worker/Worker.java
  • sdks/java/src/test/java/org/byteveda/taskito/core/PubSubTest.java
  • sdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java
  • sdks/java/test-support/src/test/java/org/byteveda/taskito/test/InMemoryPubSubTest.java
  • sdks/node/src/native.ts
  • sdks/node/src/queue.ts
  • sdks/node/src/types.ts
  • sdks/node/test/core/pubsub.test.ts
  • sdks/python/taskito/_taskito.pyi
  • sdks/python/taskito/mixins/pubsub.py
  • sdks/python/tests/core/test_pubsub.py
📝 Walkthrough

Walkthrough

Changes

The pub/sub system now persists delivery settings on subscription rows. Publish-time task defaults are removed, and fan-out resolves each field from explicit publish overrides, persisted subscription settings, or queue defaults. Java, Node, and Python bindings expose the updated registration APIs.

Subscription delivery settings

Layer / File(s) Summary
Persist subscription delivery configuration
crates/taskito-core/src/storage/...
Adds nullable priority, retry, and timeout fields to subscription schemas, models, migrations, and storage backends.
Resolve delivery settings during fan-out
crates/taskito-core/src/pubsub.rs
Removes PublishRequest.task_defaults and applies publish override → subscription row → queue default precedence.
Propagate settings through SDK bindings
crates/taskito-java/..., crates/taskito-node/..., crates/taskito-python/..., sdks/java/...
Updates subscription registration APIs and removes publish-time task-default propagation across language bindings.
Validate persisted settings across processes
sdks/*/test*, sdks/python/tests/...
Tests producer-only publishing, persisted subscriber settings, and publish override precedence.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Subscriber
  participant SubscriptionStore
  participant Producer
  participant PubSubCore
  Subscriber->>SubscriptionStore: register subscription delivery settings
  Producer->>PubSubCore: publish message with queue defaults and overrides
  PubSubCore->>SubscriptionStore: load persisted subscription row
  PubSubCore-->>Producer: create delivery job using resolved settings
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: persisting delivery settings per subscription.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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/pubsub-delivery-settings

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

Store priority/max_retries/timeout_ms on the subscription row at registration
so publish applies each subscriber's own settings even from a producer process
that never loaded the task. Resolution: publish override > row > queue default.
Drops the in-process task_defaults map — the row is a strict superset.
@pratyush618
pratyush618 force-pushed the feat/pubsub-delivery-settings branch from 1da1e47 to cbc5ba3 Compare July 12, 2026 08:54

@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: 1

🧹 Nitpick comments (3)
crates/taskito-core/src/storage/redis_backend/pubsub.rs (1)

14-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a regression test for legacy-blob deserialization.

The #[serde(default)] fallback for old blobs lacking priority/max_retries/timeout_ms is exactly the kind of backward-compat behavior that's easy to silently break in a future refactor. A small test deserializing a SubEntry JSON blob without these fields (asserting they come back None) would lock this in.

🧪 Example test
#[test]
fn sub_entry_deserializes_legacy_blob_without_delivery_settings() {
    let legacy = r#"{"topic":"orders","subscription_name":"email","task_name":"send_email","queue":"default","active":true,"durable":true,"owner_worker_id":null,"created_at":0}"#;
    let entry: SubEntry = serde_json::from_str(legacy).unwrap();
    assert_eq!(entry.priority, None);
    assert_eq!(entry.max_retries, None);
    assert_eq!(entry.timeout_ms, None);
}
🤖 Prompt for 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.

In `@crates/taskito-core/src/storage/redis_backend/pubsub.rs` around lines 14 -
31, Add a focused regression test for SubEntry deserialization using a legacy
JSON blob that omits priority, max_retries, and timeout_ms; deserialize it with
serde_json and assert all three fields are None while retaining the existing
legacy fields and values.
crates/taskito-core/tests/rust/storage_tests.rs (1)

860-876: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Extend the shared CRUD test to assert persisted delivery-setting values round-trip.

This test runs across Postgres, SQLite, and Redis (per the storage trait tests), but sub(...) always passes None for priority/max_retries/timeout_ms, so no backend is asserted to actually persist and return a Some(x) value for these new columns. A mapping bug specific to one backend's register_subscription/list mapping wouldn't be caught here.

🧪 Suggested addition
// After the existing upsert-idempotency assertions:
s.register_subscription(&NewSubscriptionRow {
    topic: "ts-orders",
    subscription_name: "priced",
    task_name: "priced_task",
    queue: "default",
    active: true,
    durable: true,
    owner_worker_id: None,
    created_at: now,
    priority: Some(5),
    max_retries: Some(1),
    timeout_ms: Some(42_000),
})
.unwrap();
let priced = s
    .list_subscriptions_for_topic("ts-orders")
    .unwrap()
    .into_iter()
    .find(|r| r.subscription_name == "priced")
    .unwrap();
assert_eq!(priced.priority, Some(5));
assert_eq!(priced.max_retries, Some(1));
assert_eq!(priced.timeout_ms, Some(42_000));
🤖 Prompt for 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.

In `@crates/taskito-core/tests/rust/storage_tests.rs` around lines 860 - 876,
Extend the shared CRUD test after the existing upsert-idempotency assertions to
register a subscription with non-None priority, max_retries, and timeout_ms
values, then list it by topic and assert those fields round-trip unchanged. Keep
the test backend-agnostic and use the existing register_subscription and
list_subscriptions_for_topic flow.
sdks/node/src/queue.ts (1)

540-554: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Add priority to the Node subscriber options TaskOptions/SubscriberOptions don’t expose it, so this path always persists undefined and Node subscriptions can only inherit the queue default. If parity with Java/Python is intended, thread priority through registerSubscription().

🤖 Prompt for 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.

In `@sdks/node/src/queue.ts` around lines 540 - 554, Expose priority on the Node
TaskOptions/SubscriberOptions types, then update the subscription registration
flow around registerSubscription to read the subscriber task’s priority and pass
it through instead of undefined. Preserve queue-default fallback when no
priority is configured.
🤖 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 `@sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java`:
- Around line 170-206: Update the 9-argument registerSubscription overload in
QueueBackend so its default implementation delegates to the existing 6-argument
registerSubscription overload, discarding the optional priority, maxRetries, and
timeoutMs values. Preserve the 6-argument overload as the compatibility path so
existing backends that override it continue handling subscriptions.

---

Nitpick comments:
In `@crates/taskito-core/src/storage/redis_backend/pubsub.rs`:
- Around line 14-31: Add a focused regression test for SubEntry deserialization
using a legacy JSON blob that omits priority, max_retries, and timeout_ms;
deserialize it with serde_json and assert all three fields are None while
retaining the existing legacy fields and values.

In `@crates/taskito-core/tests/rust/storage_tests.rs`:
- Around line 860-876: Extend the shared CRUD test after the existing
upsert-idempotency assertions to register a subscription with non-None priority,
max_retries, and timeout_ms values, then list it by topic and assert those
fields round-trip unchanged. Keep the test backend-agnostic and use the existing
register_subscription and list_subscriptions_for_topic flow.

In `@sdks/node/src/queue.ts`:
- Around line 540-554: Expose priority on the Node TaskOptions/SubscriberOptions
types, then update the subscription registration flow around
registerSubscription to read the subscriber task’s priority and pass it through
instead of undefined. Preserve queue-default fallback when no priority is
configured.
🪄 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: 6ec1b577-ab21-4f76-a8a6-60bd68721279

📥 Commits

Reviewing files that changed from the base of the PR and between 680124a and 1da1e47.

📒 Files selected for processing (30)
  • crates/taskito-core/src/pubsub.rs
  • crates/taskito-core/src/storage/diesel_common/migrations.rs
  • crates/taskito-core/src/storage/models.rs
  • crates/taskito-core/src/storage/postgres/pubsub.rs
  • crates/taskito-core/src/storage/redis_backend/pubsub.rs
  • crates/taskito-core/src/storage/schema.rs
  • crates/taskito-core/src/storage/sqlite/pubsub.rs
  • crates/taskito-core/src/storage/sqlite/tests.rs
  • crates/taskito-core/tests/rust/storage_tests.rs
  • crates/taskito-java/src/convert.rs
  • crates/taskito-java/src/queue/pubsub.rs
  • crates/taskito-java/src/worker.rs
  • crates/taskito-node/src/config.rs
  • crates/taskito-node/src/queue/pubsub.rs
  • crates/taskito-python/src/py_queue/pubsub.rs
  • sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/NativeQueue.java
  • sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/worker/Worker.java
  • sdks/java/src/test/java/org/byteveda/taskito/core/PubSubTest.java
  • sdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java
  • sdks/java/test-support/src/test/java/org/byteveda/taskito/test/InMemoryPubSubTest.java
  • sdks/node/src/native.ts
  • sdks/node/src/queue.ts
  • sdks/node/src/types.ts
  • sdks/node/test/core/pubsub.test.ts
  • sdks/python/taskito/_taskito.pyi
  • sdks/python/taskito/mixins/pubsub.py
  • sdks/python/tests/core/test_pubsub.py
💤 Files with no reviewable changes (1)
  • sdks/node/src/native.ts

Comment thread sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
An external backend that overrides only the settings-less overload broke once
subscribe() called the 9-arg form (its default threw). The 6-arg default is now
the terminal base, the 9-arg default delegates to it, and both built-in backends
override the 6-arg to route through their own 9-arg impl.
@pratyush618
pratyush618 merged commit 7e26a60 into master Jul 12, 2026
35 checks passed
@pratyush618
pratyush618 deleted the feat/pubsub-delivery-settings branch July 12, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant