Improve task staleness detection and fix serialization group isolation - #49
Merged
Merged
Conversation
正确性 / 兼容: - 投递侧订阅解析新增 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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QL5HkjoCMcdij3jtBPYJP9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)ctx.staleAfterMsparameter to allow hosts to override the defaultSTALE_AFTER_MS(1 hour). Tasks on retry chains are now considered stale if theirretry_aftertimestamp has also been exceeded by this threshold, indicating the outage occurred during the retry window.errorCodeparameter tohandleDeliveryFailure()to distinguish permanent failures (PUSH_SUBSCRIPTION_MISSING,PUSH_SUBSCRIPTION_STORE_UNSUPPORTED) from transient ones. Permanent failures skip the retry backoff ladder entirely.planobject pattern.user_idto 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)claimTask,getPushSubscription,upsertPushSubscription,deletePushSubscription) into a new shared module used by bothpg.jsandneon.jsadapters. This ensures SQL and concurrency semantics remain identical across Postgres deployments.Request Handling (
request.js)requireUserId()utility function to centralize X-User-Id validation (required + UUID v4 format) across all user-scoped endpoints, eliminating duplicated validation logic.errorResponse()helper for consistent error envelope formatting.Message Processing (
message-processor.js)predecryptedparameter toprocessSingleMessage()to allow callers (run-tick's pre-scan) to pass already-decrypted payloads, avoiding redundant decryption.errorCodefield for error classification.Push Subscription Handling (
push-subscription-store.js)resolvePushSubscription()withlegacyFallbackparameter to support subscriptions embedded in older task payloads while preferring user-level stored subscriptions.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 withrequireUserId()call.Infrastructure & Utilities
blob-store.js: Unified base64url encoding/decoding with shared implementation fromwebcrypto-utils.jsto ensure consistency across token and blob storage.context.js: Improved adapter initialization to cache promises (not just adapters) and automatically runinitSchema()on first adapter creation per process, with proper error handling and cache cleanup on failure.single-user-worker.js: Added error handling forcors.origincallback failures and config resolution failures in scheduled handler to prevent uncaught exceptions.validation.js: Re-exportedisValidUrlfrom shared package to maintain consistent URL validation across packages.schema.js: CentralizedUPDATABLE_COLUMNSwhitelist for all three adapters (pg, neon, d1) to ensure consistency when adding new columns.Testing
pg-neon-adapter.test.mjsto verify write skew recovery: after a grouped task claim succeeds, a follow-up query checks for conflicts and releases the lease if another task inhttps://claude.ai/code/session_01QL5HkjoCMcdij3jtBPYJP9