Expose orchestrator_lock_timeout in JsRuntimeOptions (Rust supports it, Node bindings do not)
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 72/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- javascript, node.js, rust
Hướng nghiên cứu
Tìm định nghĩa của JsRuntimeOptions và ánh xạ workerLockTimeoutMs hiện có, sau đó so sánh nó với RuntimeOptions trong src/runtime/mod.rs. Truy vết các bài kiểm thử tùy chọn hiện có hoặc các bước kiểm tra build của binding; công việc được xem là hoàn tất khi các consumer Node có thể cấu hình orchestratorLockTimeoutMs và quá trình validation của binding đạt.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
RuntimeOptions.orchestrator_lock_timeout is configurable in the Rust crate but is not surfaced through JsRuntimeOptions. Node consumers are pinned to the 5s default with no override.
The asymmetry is the tell: JsRuntimeOptions already exposes workerLockTimeoutMs → worker_lock_timeout, but its orchestrator twin was never mapped. This looks like an oversight rather than a deliberate omission.
Current state
Rust crate (duroxide 0.1.29, src/runtime/mod.rs):
/// Lock timeout for orchestrator queue items.
/// When an orchestration message is dequeued, it's locked for this duration.
/// Default: 5 seconds
pub orchestrator_lock_timeout: Duration,
Default Duration::from_secs(5), consumed in src/runtime/dispatchers/orchestration.rs, and explicitly covered by tests/lock_timeout_tests.rs (which exercises Duration::from_secs(10), from_secs(1), from_secs(60), …). The crate's own doc example even shows orchestrator_lock_timeout: Duration::from_secs(10).
Node bindings (duroxide npm 0.1.27) — JsRuntimeOptions exposes:
orchestrationConcurrency, workerConcurrency, dispatcherPollIntervalMs, workerLockTimeoutMs, logFormat, logLevel, serviceName, serviceVersion, maxSessionsPerRuntime, sessionIdleTimeoutMs, workerNodeId, workerTagFilter, workerTagFilterTags
Not mapped: orchestrator_lock_timeout, orchestrator_lock_renewal_buffer, worker_lock_renewal_buffer, dispatcher_long_poll_timeout, max_attempts, session_lock_timeout, session_lock_renewal_buffer, session_cleanup_interval, activity_cancellation_grace_period, unregistered_backoff, supported_replay_versions.
The only DUROXIDE_* environment variable in the native binary is DUROXIDE_PG_POOL_MAX, so there is no env-var escape hatch either.
Motivation: a 5-day production livelock with no available mitigation
A PilotSwarm deployment (8 worker pods × orchestrationConcurrency: 4 = 32 orchestration dispatchers against a single Postgres-backed queue) entered a self-sustaining livelock:
- Under contention,
fetch_orchestration_itembegan taking ~6.4s — longer than the 5sorchestrator_lock_timeout. - The lock token was therefore already expired by the time the turn tried to ack:
ack_orchestration_item: Invalid lock token. - Because the ack failed, retry bookkeeping never committed — including the poison-marking after
max_attempts. The runtime loggedOrchestration message exceeded max attempts, marking as poisonon every single pass, but the marking could never land. - With no backoff committed either, the row's lock simply expired after 5s and the message became visible again. The effective retry interval was pinned at exactly the lock lease.
- A permanently non-empty queue kept all 32 dispatchers in constant contention — which is precisely what made the fetch slow in step 1.
The loop is stable rather than self-correcting, because step 5 causes step 1.
Observed at the point of intervention:
| Metric | Value |
|---|---|
| Max attempts on a single instance | 78,987 (against max_attempts: 10) |
| Total accumulated attempts | 781,446 across 12 instances |
fetch_orchestration_item latency |
~6.4s, very tightly clustered |
| Effective throughput | zero |
| Duration before manual intervention | 5 days |
Latency clustered extremely tightly (6.40, 6.39, 6.39, 6.26, 6.27, 6.45s), which is the signature of queueing/serialization rather than scan cost.
Note that lock renewal cannot help here. The documented strategy renews at 0.5 × timeout when orchestrator_lock_timeout < 15s (so 2.5s at the default), but the 6.4s is spent inside fetch_orchestration_item — before any item exists to renew.
Recovery required manually deleting the affected orchestration instances out-of-band. With orchestrator_lock_timeout exposed, raising it to 10s would have let the ack — and therefore the poison-marking — commit, allowing the runtime's own poison handling to drain the queue without operator intervention.
Ask
Map orchestrator_lock_timeout into JsRuntimeOptions as orchestratorLockTimeoutMs, exactly as workerLockTimeoutMs already maps to worker_lock_timeout.
Ideally also expose orchestratorLockRenewalBufferMs and maxAttempts, which are part of the same tuning surface.
Secondary observation: default divergence
Two values differ notably between the Rust defaults and what Node consumers run, and they compound the contention that triggers this:
| Option | Rust default | Observed in Node consumers |
|---|---|---|
dispatcher_min_poll_interval |
100ms | 10ms |
orchestration_concurrency |
2 | 4 (× N pods) |
At 32 dispatchers polling every 10ms, that is up to ~3,200 fetch_orchestration_item calls/sec against one queue table.
It would be worth documenting the interaction explicitly: total dispatchers × poll frequency determines contention on the orchestrator queue, and orchestrator_lock_timeout must exceed worst-case fetch_orchestration_item latency under that load, or the system cannot make forward progress.
Related
The underlying reason a too-short lease is unrecoverable rather than merely slow — the poison-marking path depending on a lock token that has already expired — is filed separately against the Rust crate.
Environment
duroxide(npm) 0.1.27duroxide(crate) 0.1.29- Postgres-backed provider
- Ngôn ngữ chính
- JavaScript
- Star
- 36
- Fork
- 20
- Merge trung bình
- 2 ngày 11 giờ
- Pull request đã merge (30 ngày)
- 4
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của microsoft/duroxide-node
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 28/100
microsoft/duroxide-node#16 ·
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Tất cả issue của microsoft/duroxide-node
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
HarperFast/skills#96 ·
-
[Block] Latest Posts [Type] Bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
-
Daemon passes --experimental-wasm-jspi unconditionally on Node >= 24; Node 26 rejects the flag Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Automattic/studio#4908 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
sugarlabs/musicblocks#8847 ·