Skip to content

Deferred bash responses can be starved by runtime maintenance after task completion #172

Description

@hheei

Short description

A completed foreground Bash task can remain unanswered until the host transport times out because the standalone NDJSON loop runs runtime maintenance before flushing ready deferred responses.

What happened?

On macOS arm64 with AFT 0.49.0 under a Pi harness, two Bash requests each timed out at the host transport boundary after 130 seconds. The corresponding persisted AFT Bash task metadata showed that one task had already reached a terminal state after about 4 seconds. After the two Bash timeouts, bash_drain_completions also received no response and eventually caused a bridge restart.

This is not a slow shell command: the Bash child had already exited, but its deferred response was not written to NDJSON stdout.

Expected behavior: once a foreground Bash task is terminal, its response should be flushed promptly even if watcher/index/configure maintenance is busy.

Diagnostics

  • Host: Pi-based integration
  • AFT bridge package: 0.49.0
  • AFT binary: 0.49.0
  • Platform: macOS arm64
  • Source revision inspected: 566bcde25cbb2c24f09fdc5e4840a58a65a97677

Related: #117 fixed passive status polls incorrectly killing a busy bridge. That fix is already an ancestor of the revision above. This report is a separate failure mode: a terminal Bash task has a ready deferred response, but the binary does not emit it.

Source analysis

In crates/aft/src/main.rs, the periodic/request path currently does:

drain_runtime_events(registry);
write_ready_pending(registry.current(), pending)

A synchronous stall in drain_runtime_events can therefore starve every ready deferred response, including a completed foreground Bash task and bash_drain_completions. The observed sequence is consistent with this ordering.

I do not have a stack trace from the original stalled process, so I cannot identify the exact maintenance sub-phase that blocked. The ordering itself is independently reproducible as a starvation condition.

Proposed fix

Flush ready pending responses before runtime maintenance, then flush again after maintenance so responses that become ready during the drain are also emitted:

let ctx = registry.current();
let mut written = write_ready_pending(ctx, pending)?;
drain_runtime_events(registry);
written += write_ready_pending(ctx, pending)?;
Ok(written)

The same ordering should be used by the test writer helper.

Regression coverage performed

I added a focused regression that registers a ready deferred response and a configure-warning maintenance event. It asserts the pending response is polled/written before the maintenance callback runs; the prior ordering fails that assertion.

Validation of the local patch:

  • pending response suite: 8 passed
  • Bash terminal/promote state tests: 4 passed
  • release binary built successfully
  • direct @cortexkit/aft-bridge smoke against the built binary completed a real Bash task and retrieved its output

I can provide the minimal patch and test if that would help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions