mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #2116] [Feature Request]:Make getTransferStatus a pure status query and move failover/retransmit to an explicit progress API #578
Labels
No labels
ascend
auto-closed
bug
bug
CI/Build
Common
documentation
enhancement
feature request
good first issue
help wanted
high priority
MetaX
nightly-failure
pull-request
PyTorch Backend
question
RFC
Roadmap
stale
Store
Tests
Transfer Engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/Mooncake#578
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @KMSorSMS on GitHub (May 16, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/2116
Originally assigned to: @alogfans on GitHub.
Describe your feature request
Background
While reviewing #2055, we discussed a broader API design issue around
TransferEngineImpl::getTransferStatus().Today,
getTransferStatus()is not a pure status query. In addition topolling the underlying transport status, it may also trigger failover and
resubmit transfer tasks internally:
This behavior predates #2055 and is not introduced by that PR. However, it
creates an implicit side effect in what appears to be an observation API.
Problem
From an integrator's perspective, especially for
mooncake-pg, this implicitbehavior makes it harder to reason about transfer lifecycle ownership.
When upper layers call
getTransferStatus(), they may expect it to only observecurrent task or batch state. Instead, the call can also:
FAILEDback toPENDING,freeBatch().This is surprising because a polling/status API can indirectly perform
communication work.
We have seen this become problematic in
mooncake-pgbecause PG pollsgetTransferStatus()from the upper layer. With the current behavior, every pollcan implicitly drive failover/retransmission inside the transfer engine, without
the PG layer being aware that new communication work has been submitted.
This makes it hard for PG to reason about transfer ownership and retry behavior:
the upper layer believes it is only observing status, while the lower layer may
be repeatedly issuing retransmits/resubmissions as a side effect of polling.
One related warning we have printed in PG when cleanup later becomes difficult
was:
The aggregation fix in #2055 helps with one part of the lifecycle issue by
preventing a batch from being reported as failed while other tasks are still
pending. However, the broader API boundary issue remains: polling status can
still implicitly advance failover and resubmission, potentially multiple times,
without the upper layer explicitly scheduling those retries.
Design Concern
Conceptually, communication operations should be explicit and scheduled by the
upper layer or by a clearly defined progress API.
A method named
getTransferStatus()is expected to be observational. If it canalso mutate state and resubmit work, callers need to understand hidden lifecycle
transitions that are not obvious from the API boundary.
This coupling can be especially problematic for integrations that have their
own scheduling, timeout, cancellation, or cleanup logic.
Possible Short-Term Solution
Add a configuration flag to disable automatic failover during status polling,
for example:
or:
With this option disabled/enabled appropriately,
mooncake-pgand similarintegrations could opt out of implicit resubmission from
getTransferStatus().This would be a minimal change and could provide a near-term unblock without
requiring a larger API redesign.
Expected behavior with auto failover disabled:
getTransferStatus()only returns the observed transport/task state.resubmitTransferTask()is triggered from polling.free the batch.
Possible Long-Term Solution
Split the current behavior into two explicit APIs:
Pure observation only. It should query and return current status, but should not
resubmit, fail over, or otherwise create new communication work.
Explicit progress API. This would drive failover, retry, resubmission, and other
state transitions for users who want the transfer engine to manage progress.
This separation would make ownership clearer:
getTransferStatus(),freeBatch()safety becomes less dependent on hidden side effects frompolling.
Open Questions
compatibility?
getTransferStatus()?getTransferStatus(batch_id, vector<TransferStatus>&)also be madepure?
freeBatch()afterfailure, timeout, or cancellation?
by a background progress mechanism?
Suggested Next Step
For the short term, consider adding a config flag to let integrators opt out of
implicit failover during status polling.
For the long term, discuss and design a cleaner API split where status
observation and transfer progress/failover are separate operations.
Before submitting a new issue...
@ZhenyuePan commented on GitHub (May 17, 2026):
Hi maintainers, I’d like to work on the short-term config-flag slice for this issue.
My proposed first PR would keep the existing behavior as the default for backward compatibility, and add an opt-out flag for integrations that need
getTransferStatus()to be observational only.Planned scope:
enable_auto_failover_on_poll, defaulting totrue.false,TransferEngineImpl::getTransferStatus()will not callresubmitTransferTask()while polling.FAILEDwithout touching the fallback transport;Before I start coding, I’d like to confirm two details:
enable_auto_failover_on_pollthe preferred config name, or would you preferdisable_auto_failover_on_poll?getTransferStatus()overloads, including the vector and overall batch-status variants?If this direction looks right, I can send a focused PR for the config-gated behavior first, leaving the larger explicit
progressBatch()/advanceFailover()API split as a follow-up design discussion.@KMSorSMS commented on GitHub (May 17, 2026):
Thanks for taking this on.
enable_auto_failover_on_pollsounds good to me. Since the default will betrue, this name also makes the backward-compatible behavior clear.For the scope, I think the flag should apply to all
getTransferStatus()overloads, including the vector and overall batch-status variants. These APIs are all query-like from the caller’s perspective, so it would be better to keep their behavior consistent. Please just make sure the implementation is unified and clean, rather than adding separate ad-hoc checks in each path.The focused PR for the config-gated behavior sounds good.
@ZhenyuePan commented on GitHub (May 17, 2026):
@KMSorSMS Thanks for taking a look.
#2122 This PR is intended to cover only the short-term config-gated behavior discussed in #2116:
enable_auto_failover_on_polldefaults totrueto preserve existing behavior.false, status polling no longer triggersresubmitTransferTask().getTransferStatus()paths through a shared helper.Local validation:
bash /tmp/mooncake_code_format_check.sh --check -b upstream/maincmake --build build-tent-2116-system --target tent_failover_test tent_engine_failover_e2e_testctest --test-dir build-tent-2116-system -R tent_.*failover.*test --output-on-failureI’ll keep an eye on the remaining CI jobs and follow up if anything fails.
@ZhenyuePan commented on GitHub (May 17, 2026):
@KMSorSMS I’m thinking about a follow-up PR after #2122 to address the longer-term API split in this issue.
My proposed direction is:
Make
getTransferStatus()observation-onlygetTransferStatus()may still poll/refresh observed transport state and aggregate status, but it should not create new communication work.resubmitTransferTask(), change transport selection, or turn a returnedFAILEDstatus back intoPENDING.Add an explicit progress API for engine-managed failover/retry
progressBatch(BatchID batch_id, TransferStatus& overall_status).progressBatchbecause the current scheduling unit isBatchID, but I’m happy to useprogressTransferor another name if maintainers prefer.PENDING, preserving the current engine-managed retry behavior but moving it behind an explicit call.Preserve existing internal wait/sync behavior
waitTransferCompletion()and staging-buffer synchronous transfer paths can call the new progress API instead of relying on side effects fromgetTransferStatus().Clarify cleanup semantics
getTransferStatus(), a terminal overall status means no hidden resubmission was scheduled by polling.progressBatch(), they should only callfreeBatch()once the returned overall status is terminal.PENDING.API exposure
Compatibility with #2122
enable_auto_failover_on_pollas a short-term compatibility/config escape hatch.getTransferStatus()pure unconditionally and keep the config only as a deprecated/transitional option, orprogressBatch()and keep the config behavior for one release, then flip/remove poll-time failover later.getTransferStatus()pure,progressBatch()explicit.Test coverage I plan to add/adjust:
getTransferStatus()default path no longer submits to fallback transport after observed failure.progressBatch()triggers failover/resubmission and preserves the old engine-managed behavior.waitTransferCompletion()/ sync paths still progress failed transfers through the explicit progress path.Does this API split and migration direction sound right? In particular, I’d like to confirm:
progressBatch,progressTransfer, or something else;getTransferStatus()pure in that PR, or prefer a two-step migration.If maintainers prefer a separate RFC issue for the public API split, I’m happy to open one and move the detailed design there.
@KMSorSMS commented on GitHub (May 18, 2026):
Thanks for writing this up. The overall API split direction sounds right to me.
I agree that
getTransferStatus()should eventually become observation-only, and an explicit progress API is a cleaner place for engine-managed failover/retry.progressBatch(BatchID, TransferStatus&)also sounds reasonable to me, since the current scheduling/lifetime unit is the batch.I also agree that internal helpers such as
waitTransferCompletion()and the sync/staging paths should call the new progress API. That keeps the existing “engine-managed retry until done” behavior for blocking/sync users, while removing the side effect from status-query APIs.For the first long-term PR, I would suggest keeping the scope focused on the TENT C++ API and the internal wait/sync paths first. If that lands cleanly and the semantics look good, we can then consider exposing the same operation through the C API and Python binding in a follow-up.
For migration, I prefer the two-step path: add
progressBatch()first and keep the current config-gated compatibility behavior (in #2122) for now, then decide separately when to makegetTransferStatus()pure unconditionally or deprecate/remove poll-time failover.A few semantic details sound important to make explicit:
progressBatch()should perform one progress step, not loop until completion.PENDING, preserving the existing engine-managed retry behavior.PENDING, consistent with the current aggregation behavior.So overall: the split sounds good to me, but I’d prefer starting with a focused C++ core PR and treating bindings / full migration as follow-ups.
@alogfans commented on GitHub (May 19, 2026):
My advise is to monitors whether an alternative transport is chosen using in a seperate thread. Within the thread, callback is more efficient than busy polling. That is, if a request fails, it post a message to the dedicated thread and submits again using another transports.
@KMSorSMS commented on GitHub (May 19, 2026):
Thanks for the suggestion. I agree that if we want engine-managed automatic failover, an event/callback-driven background worker is a better direction than busy polling.
One point I want to clarify is that the main issue here is not whether polling or callback is used internally, but who owns the retry/failover process. Many integrations still need to poll
getTransferStatus()to observe completion, but a status-query API should not implicitly resubmit work or switch transports. For some use cases, such as fixed transport paths, GPU-kernel-related transfers, or CUDA graph style execution, switching to another transport after a runtime failure may be incorrect. In those cases, the caller may want the transfer to fail and decide explicitly whether/when to retry.So I think these concerns are compatible if we separate the concepts:
getTransferStatus()should remain observation-only.progressBatch()can be the explicit caller-driven way to advance retry/failover state.In the engine-managed mode,
getTransferStatus()would still only read the engine’s public state. If a transport failure is observed internally and the background worker successfully resubmits on an allowed fallback transport, the public task/batch state can remainPENDING. It should only become terminalFAILEDwhen failover is disabled, retry budget is exhausted, no fallback transport is available, or resubmission fails.I also think this should ideally be controlled at the batch/request level, not only globally. Some batches may want engine-managed failover, while others may need user-managed progress or failover disabled entirely.
For the next focused PR, I still prefer starting with the C++ API split: make the status-query path observation-only and add an explicit progress API. The background worker/callback-driven engine-managed mode sounds useful, but it probably deserves a separate follow-up design because it brings in thread lifecycle, event queues,
freeBatch()synchronization, and ownership/race semantics.Does this separation sound reasonable to you? My current preference is to keep an explicit progress API for callers that need control, and treat the background worker as an optional engine-managed mode for callers that want automatic failover. I’d like to hear whether you think this split makes sense.
@ZhenyuePan commented on GitHub (May 19, 2026):
@KMSorSMS @alogfans Thanks, this makes sense to me. I agree that an event/callback-driven worker is a better direction for engine-managed automatic failover than relying on status polling.
Based on the latest discussion, I see two related pieces here:
API ownership boundary:
getTransferStatus()should be observation-only.failover/retry should be driven by an explicit progress path, for example:
Engine-managed progress mechanism:
My proposed next step is to clarify whether these two pieces should land together or in two stages.
If maintainers prefer landing them together, I can plan the next PR around:
progressBatch()API as the explicit progress/failover operation;getTransferStatus();getTransferStatus()as a status observation path;waitTransferCompletion()/ sync paths use the explicit progress or engine-managed path.The main design points I would want to make explicit before coding are:
freeBatch()synchronizes with queued failover work;FAILEDis reported when failover is disabled, retry budget is exhausted, no fallback exists, or resubmission fails;Would you prefer the next PR to include both the
progressBatch()API split and the callback/event-driven worker, or should I first land the C++ API boundary and then add the worker mechanism in a follow-up?@KMSorSMS commented on GitHub (May 20, 2026):
I think the key point before introducing the engine-managed worker mechanism is backward compatibility.
With #2122, the old behavior is still preserved behind
enable_auto_failover_on_poll, and the default remainstrue. So existing users can continue to get the current poll-time auto failover/resubmit behavior fromgetTransferStatus(), while integrations such as mooncake-pg can opt into observation-only polling by setting the flag tofalse.Given that, I would prefer the next PR to first add the C++ API boundary / explicit progress path without removing the existing compatibility mode. In other words:
getTransferStatus()behavior backward-compatible by default throughenable_auto_failover_on_poll = true;getTransferStatus()when the flag is disabled;progressBatch()as the explicit progress/failover path for users that want to move away from poll-time side effects;This way, before the engine-managed worker mechanism exists, we do not break the previous operating model. We only add another clearer mode and a migration path. Once the worker design is ready, we can decide separately whether and when to change the default or deprecate poll-time auto failover.
@ZhenyuePan commented on GitHub (May 23, 2026):
@KMSorSMS @alogfans I've opened #2160 as the focused first step we discussed.
Scope, aligned with the consensus in this thread:
Status progressBatch(BatchID, TransferStatus&)on the TENT C++ API.progressBatch()always allows internal failover/resubmit, independent ofenable_auto_failover_on_poll.PENDING, preserving engine-managed retry semantics.PENDING; terminal states are not revived.waitTransferCompletion()and the sync/staging paths now drive progress viaprogressBatch()instead of relying ongetTransferStatus()side effects.Backward compatibility:
getTransferStatus()behavior is unchanged by default; theenable_auto_failover_on_pollswitch from #2122 still gates poll-time auto failover.freeBatch()synchronization, and per-batch policy separately.Test coverage (7 new e2e cases in
engine_failover_e2e_test.cpp):ProgressBatchRetriesWhenPollAutoFailoverDisabledProgressBatchDoesNotReviveObservedFailedTaskProgressBatchHonorsMaxFailoverAttemptsZeroProgressBatchKeepsOverallPendingWithMixedOutcomesProgressBatchKeepsPendingAfterSuccessfulFailoverResubmitTransferSyncUsesProgressBatchWhenPollDisabledProgressBatchAdvancesExactlyOneStepPerCallLocal validation in the project's Ubuntu 22.04 devcontainer image: 51/51 TENT failover tests pass,
scripts/code_format.sh --check -b upstream/mainclean.Happy to iterate on naming or split semantics if you have further preferences before review.
@ZhenyuePan commented on GitHub (May 26, 2026):
@KMSorSMS @alogfans Here's my planned merge sequence for the related PRs:
#2122 —enable_auto_failover_on_pollconfig flag (already merged)progressBatch()API (need to fix code formatting first, then merge)#2159 was closed and superseded by #2208.
So the immediate next step is landing #2160. Will push the format fix shortly.
@ZhenyuePan commented on GitHub (May 27, 2026):
CC @KMSorSMS @alogfans
PR #2199 (
[TE] Add ProgressWorker skeleton) has been merged. #2160 (progressBatchAPI) was a prerequisite of #2199 and its content is already included in main — it can be closed.Done so far:
progressBatch()explicit progress-driving API (from #2160)ProgressWorkerdriven by batch-ready notificationsfreeBatch())enable_progress_workerconfig +notifyBatchMaybeReady()test hookprogress_mutex_Planned follow-up PRs:
PR 3: Transport terminal-event integration
ProgressWorkernotifyBatchMaybeReady()on completion, so the worker is automatically driven by transport eventsPR 4: lazy-free behavior change + lifecycle fix
freeBatch()deferred-release logic: whenProgressWorkeris still processing a batch,freeBatch()must wait or mark for deferred releasebatch_set_dependency, making batch lifecycle fully thread-agnosticPR 5: Pure-observation
getTransferStatus()+ documentation updateenable_auto_failover_on_polldefault tofalsegetTransferStatus()will no longer implicitly callresubmitTransferTask()to resubmit failed tasks — becomes a pure read-only operationprogressBatch()or enableProgressWorkerOpen for discussion on priority and scope of each step. Feedback welcome!
@github-actions[bot] commented on GitHub (Aug 26, 2026):
This issue has had no activity for 90 days and will be closed in 7 days if there is no further activity. Please comment or react if it should stay open.
@github-actions[bot] commented on GitHub (Sep 3, 2026):
Closing due to 3 months of inactivity. If this is still relevant, please comment and we can reopen.