MCPcopy Create free account
hub / github.com/coder/agentapi / StartProcess

Function StartProcess

lib/termexec/termexec.go:37–106  ·  view source on GitHub ↗
(ctx context.Context, args StartProcessConfig)

Source from the content-addressed store, hash-verified

35}
36
37func StartProcess(ctx context.Context, args StartProcessConfig) (*Process, error) {
38 logger := logctx.From(ctx)
39 clock := args.Clock
40 if clock == nil {
41 clock = quartz.NewReal()
42 }
43 xp, err := xpty.New(args.TerminalWidth, args.TerminalHeight, false)
44 if err != nil {
45 return nil, err
46 }
47 execCmd := exec.Command(args.Program, args.Args...)
48 // vt100 is the terminal type that the vt10x library emulates.
49 // Setting this signals to the process that it should only use compatible
50 // escape sequences.
51 execCmd.Env = append(os.Environ(), "TERM=vt100")
52 if err := xp.StartProcessInTerminal(execCmd); err != nil {
53 return nil, err
54 }
55
56 process := &Process{xp: xp, execCmd: execCmd, clock: clock}
57
58 go func() {
59 // HACK: Working around xpty concurrency limitations
60 //
61 // Problem:
62 // 1. We need to track when the terminal screen was last updated (for ReadScreen)
63 // 2. xpty only updates terminal state through xp.ReadRune()
64 // 3. xp.ReadRune() has a bug - it panics when SetReadDeadline is used
65 // 4. Without deadlines, ReadRune blocks until the process outputs data
66 //
67 // Why this matters:
68 // If we wrapped ReadRune + lastScreenUpdate in a mutex, this goroutine would
69 // hold the lock while waiting for process output. Since ReadRune blocks indefinitely,
70 // ReadScreen callers would be locked out until new output arrives. Even worse,
71 // after output arrives, this goroutine could immediately reacquire the lock
72 // for the next ReadRune call, potentially starving ReadScreen callers indefinitely.
73 //
74 // Solution:
75 // Instead of using xp.ReadRune(), we directly use its internal components:
76 // - pp.ReadRune() - handles the blocking read from the process
77 // - xp.Term.WriteRune() - updates the terminal state
78 //
79 // This lets us apply the mutex only around the terminal update and timestamp,
80 // keeping reads non-blocking while maintaining thread safety.
81 //
82 // Warning: This depends on xpty internals and may break if xpty changes.
83 // A proper fix would require forking xpty or getting upstream changes.
84 pp := util.GetUnexportedField(xp, "pp").(*xpty.PassthroughPipe)
85 for {
86 r, _, err := pp.ReadRune()
87 if err != nil {
88 if err != io.EOF {
89 logger.Error("Error reading from pseudo terminal", "error", err)
90 }
91 // TODO: handle this error better. if this happens, the terminal
92 // state will never be updated anymore and the process will appear
93 // unresponsive.
94 return

Callers 1

SetupProcessFunction · 0.92

Calls 2

FromFunction · 0.92
GetUnexportedFieldFunction · 0.92

Tested by

no test coverage detected