Check "exited" against the process record before believing it
A session adopted at a backend start keeps the transcript's last status, so one whose process had been reported gone and was then found again read as `exited` while its CLI was running. `exited` is the word that draws the phone's Start button and lets `start_session` build a driver, so Start was accepted every time it was pressed -- and since starting replaces the driver without retiring the old one, each press left another reader on the same process. Every line the CLI wrote was then translated once per reader: three presses put three interleaved copies of one reply on screen, which is what it was reported as. So `exited` is now checked against `session::process`, the one authority on whether a process exists, in `launch` and again in `start_session`. A record that is not known to be dead makes it false, and what replaces it is `unknown` -- there is a process, and nothing here has heard from it, which is the answer `status_of_unlaunched` already gave to the same question. The correction goes out through the sink rather than into the manager's view alone, or the list and the session screen would disagree about it in the way this same button did a commit ago. A driver that `start_session` replaces now gets `Driver::detach`, which already existed for the backend going away and is the whole of what a driver whose process has exited is owed. On the phone the process button is disabled while its own request is in flight, so a second press cannot be decided against a status the first has not changed yet. That is a courtesy rather than the fix; the server refuses it either way, because a phone that has lost the stream cannot be relied on to know. Verified against a stand-in CLI, with the state forced by hand: before, three Starts returned 204 and left four readers on one process and the status still `exited`; after, the session reports `unknown` on both surfaces and all three are refused. Then driven on the emulator -- Stop, Start, Stop, Start alternated correctly with one process at a time, and the list, the transcript and the record all agree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
558095520d
commit
50f9b956d9
4 files changed
+210
-10
No files matched your search
@@ -555,6 +555,12 @@ fun SessionScreen(
|
||||
var compactingFor by remember { mutableStateOf<Long?>(null) }
|
||||
var streamError by remember { mutableStateOf<String?>(null) }
|
||||
var actionError by remember { mutableStateOf<String?>(null) }
|
||||
// Whether the composer's process button has a request out. What it does next is decided from
|
||||
// the session's status, and the status only changes once the server has answered and the
|
||||
// stream has carried it back -- so two presses in that gap are two requests, both decided
|
||||
// against the state before either of them. The server refuses the second one, but a control
|
||||
// that can be pressed while its own last press is still in flight is asking to be.
|
||||
var processInFlight by remember { mutableStateOf(false) }
|
||||
val context = LocalContext.current
|
||||
// Seeded from what was left in the box last time and written back on every keystroke, so
|
||||
// leaving the screen -- or the system reclaiming the app -- does not throw away a half-typed
|
||||
@@ -997,7 +1003,7 @@ fun SessionScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun act(onFailure: () -> Unit = {}, action: () -> Unit) {
|
||||
fun act(onFailure: () -> Unit = {}, onDone: () -> Unit = {}, action: () -> Unit) {
|
||||
scope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) { action() }
|
||||
@@ -1005,6 +1011,11 @@ fun SessionScreen(
|
||||
} catch (e: ApiException) {
|
||||
actionError = e.message
|
||||
onFailure()
|
||||
} finally {
|
||||
// Whatever happened, including the failure above: a caller that re-enables a
|
||||
// control here must get it back on the path where the request was refused too,
|
||||
// or the refusal is what disables the control permanently.
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1503,7 +1514,13 @@ fun SessionScreen(
|
||||
else -> ProcessAction.Stop
|
||||
}
|
||||
Button(
|
||||
onClick = { act { process.perform(settings, summary.id) } },
|
||||
onClick = {
|
||||
processInFlight = true
|
||||
act(onDone = { processInFlight = false }) {
|
||||
process.perform(settings, summary.id)
|
||||
}
|
||||
},
|
||||
enabled = !processInFlight,
|
||||
colors = actionButtonColors(process.colour()),
|
||||
) {
|
||||
Glyph(
|
||||
|
||||
Reference in new issue
Block a user