Skip to content

Improve task staleness detection and fix serialization group isolation - #49

Merged
Tosd0 merged 2 commits into
mainfrom
claude/code-review-conclusion-verification-akr03n
Aug 6, 2026
Merged

Improve task staleness detection and fix serialization group isolation#49
Tosd0 merged 2 commits into
mainfrom
claude/code-review-conclusion-verification-akr03n

Conversation

@Tosd0

@Tosd0 Tosd0 commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

This PR enhances the scheduled message delivery system with better staleness detection for retrying tasks and fixes a critical bug in serialization group isolation. It also refactors shared Postgres query logic and improves error handling across multiple handlers.

Key Changes

Core Delivery Logic (run-tick.js)

  • Configurable staleness threshold: Added ctx.staleAfterMs parameter to allow hosts to override the default STALE_AFTER_MS (1 hour). Tasks on retry chains are now considered stale if their retry_after timestamp has also been exceeded by this threshold, indicating the outage occurred during the retry window.
  • Improved retry failure handling: Added errorCode parameter to handleDeliveryFailure() to distinguish permanent failures (PUSH_SUBSCRIPTION_MISSING, PUSH_SUBSCRIPTION_STORE_UNSUPPORTED) from transient ones. Permanent failures skip the retry backoff ladder entirely.
  • Reduced code duplication: Consolidated nearly identical recurring/non-recurring task expiration logic into a single code path using a plan object pattern.
  • Fixed serialization group isolation bug: Group serialization keys now include user_id to prevent different users' tasks with the same group name from blocking each other. Added post-commit verification to catch write skew conflicts in READ COMMITTED isolation.

Shared Postgres Implementation (pg-shared.js)

  • Extracted common Postgres query logic (claimTask, getPushSubscription, upsertPushSubscription, deletePushSubscription) into a new shared module used by both pg.js and neon.js adapters. This ensures SQL and concurrency semantics remain identical across Postgres deployments.
  • Documented write skew handling for grouped task claims with detailed comments.

Request Handling (request.js)

  • Added requireUserId() utility function to centralize X-User-Id validation (required + UUID v4 format) across all user-scoped endpoints, eliminating duplicated validation logic.
  • Added errorResponse() helper for consistent error envelope formatting.

Message Processing (message-processor.js)

  • Added predecrypted parameter to processSingleMessage() to allow callers (run-tick's pre-scan) to pass already-decrypted payloads, avoiding redundant decryption.
  • Updated return type to include optional errorCode field for error classification.

Push Subscription Handling (push-subscription-store.js)

  • Enhanced resolvePushSubscription() with legacyFallback parameter to support subscriptions embedded in older task payloads while preferring user-level stored subscriptions.
  • Added codedError() helper to attach stable error codes for branch-based error handling instead of message matching.

Handler Consolidation

  • schedule-message.js, update-message.js, get-user-key.js, get-message.js, messages.js, cancel-message.js, client-state.js, push-subscription.js: Replaced inline X-User-Id validation with requireUserId() call.

Infrastructure & Utilities

  • blob-store.js: Unified base64url encoding/decoding with shared implementation from webcrypto-utils.js to ensure consistency across token and blob storage.
  • context.js: Improved adapter initialization to cache promises (not just adapters) and automatically run initSchema() on first adapter creation per process, with proper error handling and cache cleanup on failure.
  • single-user-worker.js: Added error handling for cors.origin callback failures and config resolution failures in scheduled handler to prevent uncaught exceptions.
  • validation.js: Re-exported isValidUrl from shared package to maintain consistent URL validation across packages.
  • schema.js: Centralized UPDATABLE_COLUMNS whitelist for all three adapters (pg, neon, d1) to ensure consistency when adding new columns.

Testing

  • Updated pg-neon-adapter.test.mjs to verify write skew recovery: after a grouped task claim succeeds, a follow-up query checks for conflicts and releases the lease if another task in

https://claude.ai/code/session_01QL5HkjoCMcdij3jtBPYJP9

claude added 2 commits August 6, 2026 17:42
正确性 / 兼容:
- 投递侧订阅解析新增 legacyFallback:用户级存储缺订阅时回退到旧任务
  payload 内嵌的 pushSubscription,升级前创建的存量任务不再必然失败
- pg/neon claimTask 串行分组守卫补写偏斜收口:占位提交后复查同组活租约,
  撞上则放掉自己的租约让路(READ COMMITTED 下 NOT EXISTS 互相不可见)
- tick 内串行分组预占用改为按 user_id 隔离,与 DB 侧 per-user HMAC 对齐
- PUT /update-message 重置 retry_count / retry_after,修复后的任务不再
  背着耗尽的重试预算
- schedule-message 订阅预检改为存在性检查:查询失败报可重试的 503
  (PUSH_SUBSCRIPTION_LOOKUP_FAILED),不再伪装成 409 未登记
- CORS origin 回调包进 try(不再逃出 fetch() 错误边界);scheduled()
  的 resolveConfig 移入守卫,配置失败按日志跳过而非未捕获异常
- 过期守卫:重试链任务在 retry_after 也被拖过阈值时同样按过期处理;
  阈值可用 ctx.staleAfterMs 覆盖
- 订阅类错误改带稳定 err.code;投递失败按错误类别短路重试阶梯
  (PUSH_SUBSCRIPTION_MISSING 不再白跑 3 次退避)
- 多租户 getOrCreateAdapter 每进程首次使用时补跑 initSchema,存量租户
  升级后自动获得新列,不再依赖手工 DDL

去重 / 一致性:
- pg/neon 的 claimTask 与 push_subscriptions 三方法收拢到 pg-shared.js
- UPDATABLE_COLUMNS 三适配器共用 schema.js 一份
- X-User-Id 门禁收拢为 lib/request.js 的 requireUserId()(8 处副本、
  两种文案 → 一处)
- isValidUrl 改为 re-export shared 实现;blob-store 的 base64url 改用
  shared 实现;shared webpush 的 JWT 解码改用 utf8Decode
- run-tick 过期跳过的循环/一次性分支收拢为单一尾部;预解密 payload 线程
  化进 processSingleMessage,投递不再解第二遍(并修正相关失实注释)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QL5HkjoCMcdij3jtBPYJP9
@Tosd0
Tosd0 merged commit b8d91d7 into main Aug 6, 2026
1 check passed
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.

2 participants