Skip to content

sess.winch cap-1 channel can block SSH request loop during image build #10

@Gandalf-Le-Dev

Description

@Gandalf-Le-Dev

Problem

wish/ssh's `sess.winch` is a cap-1 buffered `chan Window` created once per session (`session.go:358`). Every `window-change` SSH request does `sess.winch <- win` (`session.go:396`). If the buffer is full, the send blocks the SSH request-handling goroutine until a reader drains.

In hopbox's session flow:

  1. Client connects. pty-req fires. Initial Window pushed into `winch` — buffer fills.
  2. Wizard (for new users/boxes) runs its own consumer draining `winch`. OK.
  3. Wizard exits. Consumer goroutine stops.
  4. Gateway proceeds to image build, container ensure, session bookkeeping.
  5. Gateway's `resizeCh` forwarder starts (`server.go:363`) — only after all that.

Between steps 3 and 5 there is no consumer on `winch`. If the user resizes their terminal during image build (first build can take 10s+), the buffer fills after the first resize and any subsequent resize blocks wish's request-handling goroutine. Eventually the session may stall on unrelated SSH requests.

Evidence

  • wish/ssh: `/home/dev/go/pkg/mod/github.com/charmbracelet/ssh@.../session.go` lines 358, 396.
  • hopbox flow: `internal/gateway/server.go` around line 358 (after wizard exit, before resize goroutine on line 363).
  • Not the cause of Handle SIGWINCH cleanly #3 (separate zombie-exec issue), but found during that investigation.

Proposals

Pick one:

A) Drain-to-latest drainer. Start a throwaway drain goroutine at the top of `sessionHandler` that keeps reading `winch` and storing the last size in an atomic. Hand the last-known size off to the real consumer when ready, and either close the drain or pipe into a forwarder.

B) Replace wish's winch with a local unbuffered channel. Take ownership from the start; wish's `sess.winch` becomes our responsibility end-to-end.

C) Buffer enlargement. Fork/wrap wish to use a larger buffer on `winch`. Avoids architectural change but doesn't eliminate the worst case.

Effort

Small. Most of the work is choosing the right interception point and making sure it plays well with the wizard consumer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions