Skip to content

RagStream: guarantee terminal event on lifecycle/coroutine cancellation #45

@nmrenyi

Description

@nmrenyi

Problem

RagStream.cancel() cancels the coroutine job, relying on the CancellationException catch block to post {done: true, cancelled: true} to Flutter. This works for the normal user-cancel path, but there is no guarantee if the coroutine is cancelled outside that path — e.g. Android lifecycle teardown, process death, or a job cancelled before withContext is entered.

If the terminal event is never posted, the Flutter UI stays in "generating" state permanently until the next query arrives and supersedes it.

Fix

Register an invokeOnCompletion handler on the job when it is launched in generateResponse(). This fires regardless of how the coroutine ends (normal completion, cancellation, exception) and can post a terminal event unconditionally if currentJob is still this job:

currentJob = lifecycleScope.launch {
    // ... existing body ...
}.also { job ->
    job.invokeOnCompletion { cause ->
        if (cause is CancellationException || cause != null) {
            postTerminalIfCurrent(job, cancelled = cause != null)
        }
    }
}

Impact

Low probability in practice (normal cancel path works), but could leave the UI stuck if triggered by lifecycle events.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions