-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
I’m seeing inconsistent cancellation behavior when using callback streaming via:
Conversation.sendMessageAsync(text, callback: MessageCallback)
Two problems show up:
- cancellation is difficult to classify robustly, and
- callbacks can still arrive after cancel/close, which can corrupt client state across teardown or subsequent runs.
Summary
When I attempt to stop an in-flight streaming request (or clean up aggressively due to lifecycle / watchdog recovery):
-
Cancellation may surface through
onError(throwable)but message strings vary (casing/wording), making it hard to machine-detect cancellation reliably. -
I sometimes receive callbacks after I believe the request is canceled/closed:
onMessage(...)aftercancelProcess()and/orclose()- terminal callbacks that arrive late and race with teardown or the next run
Late events can violate common client invariants (e.g., “no UI/state updates after teardown”, “exactly one terminal outcome per run”, “next run must not be affected by previous callbacks”).
What I observe (intermittent)
-
Unstable cancellation signaling: cancellation appears via
onError(Throwable), but the message strings vary (casing/wording), making cancellation hard to machine-detect without parsing strings. -
Late callbacks after cancel/close:
onMessage(...)arrives aftercancelProcess()and/orclose()- terminal callbacks can arrive late and race with teardown or the next run
-
Cross-run interference risk: callbacks from a previous run can arrive after a new run begins, which can corrupt state machines or UI.
Expected behavior (contract)
Either of these would be workable if clearly documented:
Option A (strong contract)
-
cancelProcess()results in a deterministic terminal callback (usuallyonErrorwith a well-defined cancellation type/code), and then:- no further callbacks (
onMessage,onDone,onError) are delivered for that request.
- no further callbacks (
Option B (explicit weak contract)
-
Late callbacks may occur, but the API documents:
- ordering/ack semantics (“cancel requested” vs “cancel acknowledged”), and
- a reliable per-request/run mechanism for clients to ignore old callbacks.
In either case, cancellation should be reliably machine-detectable (not dependent on message-string parsing).
High-level correlations
- Cancel/close while
onMessage(...)is actively streaming - Owner teardown is aggressive (lifecycle stop/destroy)
- A watchdog/timeout cancels a run and a new run starts soon after
Requests to maintainers
-
Document cancellation semantics clearly:
- request-level vs engine-level cancellation
- best-effort vs guaranteed terminal behavior
- recommended ordering between
cancelProcess()andclose()
-
Stabilize cancellation signaling:
- stable cancellation type/code/exception (vs message strings)
- or a dedicated cancellation callback /
isCancellationflag
-
Provide a robust disambiguation mechanism:
- per-request/run identifier in callbacks, or
- cancellation acknowledgement boundary, or
- a guarantee that no callbacks occur after “cancel acknowledged”
Questions
- Are callbacks allowed to arrive after
cancelProcess()/close()? If yes, what ordering guarantees exist? - Is cancellation expected to be reflected as
onError(...)? IsonDone()ever valid after cancellation? - Is there a recommended pattern for “cancel then close” (or vice-versa) to minimize late callbacks?
Temporary client-side mitigation
Until the contract is clarified/fixed, I’m forced to implement defensive logic:
- Track per-run
runId+ finalized/canceled flag and ignore callbacks after finalization - String-parsing workaround to detect cancellation
- Watchdog timeouts to avoid stuck state during lifecycle transitions
Environment
-
LiteRT‑LM:
com.google.ai.edge.litertlm:litertlm-android:0.9.0-alpha04- Version correlation: observed more frequently with
com.google.ai.edge.litertlm:litertlm-android:0.8.0.
- Version correlation: observed more frequently with
-
Android / device: Google Pixel 9a (API 36)
-
ABI:
arm64-v8a -
Model / backend / runtime config: same as Issue 1
-
Build environment: AGP 9.0.0, Kotlin 2.3.10 (Compose BOM 2026.02.00)