Skip to content

tailcat_ssh: PTY session hangs because stdout io.Copy blocks before cmd.Wait and cleanup #17

Description

@awdemos

Problem

runWithPTY in tailcat_ssh.go blocks on io.Copy(sess, ptmx) (stdout) before calling cmd.Wait(). If the shell does not exit immediately, the function never reaches cmd.Wait(), and the deferred ptmx.Close() never runs. The whole SSH session hangs.

This is the underlying issue that PR #8 fixes.

Reproduction

I added TestSSHSuite/PTYExitReturnsPromptly to exercise a short-lived interactive command through a PTY. Running it against main (commit c04c5afee) reproduces the hang:

// In tailcat_ssh_test.go, inside TestSSHSuite
{
    name: "PTYExitReturnsPromptly",
    run: func(t *testing.T, env *testSSHEnv) {
        sess := env.newSession(t, true)
        defer sess.Close()
        if err := sess.Run("echo hello"); err != nil {
            t.Fatalf("Run: %v", err)
        }
        if got := strings.TrimSpace(stdout.String()); got != "hello" {
            t.Fatalf("stdout = %q, want hello", got)
        }
    },
}

Test run on main:

=== RUN   TestSSHSuite/PTYExitReturnsPromptly
--- FAIL: TestSSHSuite (???)  // test times out / hangs

Verification that PR #8 fixes it

Checking out PR #8 (commit bb7fc094e) and running the same test:

=== RUN   TestSSHSuite/PTYExitReturnsPromptly
--- PASS: TestSSHSuite (2.34s)

Suggested fix

Apply the restructure from PR #8: run cmd.Wait() and the stdout io.Copy concurrently, cancel the window-resize goroutine and drain stdout when the command exits, and kill the shell when the client disconnects first. Also close the pty slave fd after cmd.Start and set tty = nil so deferred cleanup does not double-close it.

References

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