Skip to content

Cancellation semantics unclear/inconsistent in callback streaming: late onMessage / terminal callbacks after cancelProcess() / close() + unstable cancellation signaling #448

@ishizuki-tech

Description

@ishizuki-tech

I’m seeing inconsistent cancellation behavior when using callback streaming via:

  • Conversation.sendMessageAsync(text, callback: MessageCallback)

Two problems show up:

  1. cancellation is difficult to classify robustly, and
  2. 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(...) after cancelProcess() and/or close()
    • 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 after cancelProcess() and/or close()
    • 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 (usually onError with a well-defined cancellation type/code), and then:

    • no further callbacks (onMessage, onDone, onError) are delivered for that request.

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

  1. Document cancellation semantics clearly:

    • request-level vs engine-level cancellation
    • best-effort vs guaranteed terminal behavior
    • recommended ordering between cancelProcess() and close()
  2. Stabilize cancellation signaling:

    • stable cancellation type/code/exception (vs message strings)
    • or a dedicated cancellation callback / isCancellation flag
  3. 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

  1. Are callbacks allowed to arrive after cancelProcess() / close()? If yes, what ordering guarantees exist?
  2. Is cancellation expected to be reflected as onError(...)? Is onDone() ever valid after cancellation?
  3. 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.
  • 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)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions