fix(remote): stop the flush losing a chunk, and reset the host form's wake fields

Own review pass over the PR:

- `_flush` took the chunk out of the buffer only AFTER awaiting the write. Input
  arriving during that await is enqueued (`waking` is still set, so it takes the
  buffer path), and the 4 KB cap then drops the OLDEST chunk — which is the one
  already on its way to the pane. The `shift()` that followed removed the NEXT
  chunk instead, so the drop-oldest bookkeeping silently lost a chunk that was
  never written, while the log line blamed the one that was. The chunk is now
  removed before the await and re-inserted at the FRONT on a failed write, so the
  order of the queue behind it is preserved. Regression test: a chunk enqueued
  during the first write of a full buffer must still reach the pane (red against
  the old order).
- `showCreateCaseModal()` reset the remote-host form fields but not the two new
  wake inputs, so one host's MAC/command carried over into the next host that
  form saved.
- The banner's pre-poll `wakeConfigured` labelled a command-only host as 'mac'.
  Nothing reads the distinction, but the field is documented as which path is
  configured, so it says the truth until the first poll corrects it.
- Stale `resolveRemote` comment ("only for sessions that have no usable target of
  their own"): after the host config became authoritative in both directions it is
  consulted on the TTL regardless.
This commit is contained in:
Randalix
2026-09-16 21:06:25 +02:00
parent acb8d4b0aa
commit 29984c639d
5 changed files with 52 additions and 5 deletions
+32
View File
@@ -372,6 +372,38 @@ describe('RemoteWakeRegistry', () => {
expect(h.registry.pendingBytes('sess-1')).toBe(3);
});
it('flushes the chunk it is writing out of the buffer first, so a concurrent enqueue cannot drop a different one', async () => {
// Input arriving DURING the flush is enqueued (`waking` is still set), and the cap
// then drops the OLDEST chunk — the one already on its way to the pane. Shifting the
// buffer after the write removed the NEXT chunk instead, so the drop-oldest
// bookkeeping lost a chunk that was never written.
const h = harness();
h.probe.mockResolvedValue(false);
let release: (() => void) | undefined;
h.waitUntilReady.mockImplementation(
() =>
new Promise<boolean>((resolve) => {
release = () => resolve(true);
})
);
const big = 'a'.repeat(REMOTE_WAKE_PENDING_MAX_BYTES - 10);
await h.registry.handleInput(h.session, big);
await h.registry.handleInput(h.session, 'bbbbbbbbbb'); // fills the cap exactly
// The third chunk arrives while the FIRST write is in flight, which is what pushes
// the buffer over the cap mid-flush.
h.writeViaMux.mockImplementationOnce(async () => {
await h.registry.handleInput(h.session, 'c');
return true;
});
release?.();
await h.registry.wake(h.session);
expect(h.writeViaMux.mock.calls.map((c) => c[0])).toEqual([big, 'bbbbbbbbbb', 'c']);
expect(h.registry.pendingBytes('sess-1')).toBe(0);
});
it('ensureAwake blocks only for the wait path and returns true without a wake command', async () => {
const h = harness({ remote: { hostId: 'x', label: 'X', host: '10.0.0.9' } });
await expect(h.registry.ensureAwake(h.session)).resolves.toBe(true);