Files
muxplex/muxterm/pool.go
T
Ken 46c6199087
CI / test (3.13) (push) Failing after 12m18s
CI / test (3.12) (push) Failing after 12m20s
CI / test (3.11) (push) Failing after 12m22s
perf: optimize muxplex performance across frontend and backend
- Optimize app.js rendering and event handling for faster UI responsiveness
- Optimize style.css with improved selectors and reduced re-layouts
- Optimize terminal.js WebSocket handling and message processing
- Optimize main.py with faster request handling and caching
- Optimize muxterm.py with improved state management and pooling
- Optimize pool.go with better goroutine and connection pooling
- Add performance benchmarks for frontend CSS selectors
- Add performance tests for main module routines

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-05-29 04:04:24 +00:00

144 lines
3.4 KiB
Go

package main
import (
"fmt"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"github.com/creack/pty"
)
// Session represents a PTY-backed tmux attach process.
type Session struct {
PTY *os.File
Cmd *exec.Cmd
Cols uint16
Rows uint16
}
// Pool manages named tmux PTY sessions.
type Pool struct {
mu sync.Mutex
sessions map[string]*Session
}
// NewPool returns an empty Pool.
func NewPool() *Pool {
return &Pool{
sessions: make(map[string]*Session),
}
}
// Attach reuses an existing live session or spawns a new tmux attach process.
// Dead sessions are cleaned up before spawning a replacement.
func (p *Pool) Attach(name string, cols, rows uint16) (*Session, error) {
p.mu.Lock()
defer p.mu.Unlock()
if sess, ok := p.sessions[name]; ok {
// Check if process is still alive via Signal(0).
if sess.Cmd.Process != nil && sess.Cmd.Process.Signal(syscall.Signal(0)) == nil {
return sess, nil
}
// Dead session — clean up.
sess.PTY.Close()
delete(p.sessions, name)
}
cmd := exec.Command("tmux", "attach", "-t", name)
// Always set TERM=xterm-256color so tmux can render to the PTY.
// When muxterm runs as a systemd service TERM is typically unset, and
// even when set to a basic type (e.g. "dumb") tmux fails immediately
// with "open terminal failed: terminal does not support clear screen".
// For a PTY-backed web terminal xterm-256color is always the right value.
env := make([]string, 0, len(os.Environ())+1)
for _, e := range os.Environ() {
if !strings.HasPrefix(e, "TERM=") {
env = append(env, e)
}
}
env = append(env, "TERM=xterm-256color")
cmd.Env = env
winSize := &pty.Winsize{Cols: cols, Rows: rows}
ptmx, err := pty.StartWithSize(cmd, winSize)
if err != nil {
return nil, fmt.Errorf("pty start: %w", err)
}
sess := &Session{
PTY: ptmx,
Cmd: cmd,
Cols: cols,
Rows: rows,
}
p.sessions[name] = sess
// Background goroutine: wait for process exit and clean up.
go func() {
cmd.Wait()
p.mu.Lock()
defer p.mu.Unlock()
// Only delete if the map still holds this exact session
// (Attach may have replaced it already).
if cur, ok := p.sessions[name]; ok && cur == sess {
sess.PTY.Close()
delete(p.sessions, name)
}
}()
return sess, nil
}
// Resize changes the PTY window size for a named session.
func (p *Pool) Resize(name string, cols, rows uint16) error {
p.mu.Lock()
defer p.mu.Unlock()
sess, ok := p.sessions[name]
if !ok {
return fmt.Errorf("session %q not found", name)
}
if err := pty.Setsize(sess.PTY, &pty.Winsize{Cols: cols, Rows: rows}); err != nil {
return fmt.Errorf("setsize: %w", err)
}
sess.Cols = cols
sess.Rows = rows
return nil
}
// Get returns the session for name, or nil if not present.
func (p *Pool) Get(name string) *Session {
p.mu.Lock()
defer p.mu.Unlock()
return p.sessions[name]
}
// IsAlive returns true if the named session exists and its process is alive.
func (p *Pool) IsAlive(name string) bool {
p.mu.Lock()
defer p.mu.Unlock()
sess, ok := p.sessions[name]
if !ok {
return false
}
return sess.Cmd.Process != nil && sess.Cmd.Process.Signal(syscall.Signal(0)) == nil
}
// CloseAll sends SIGTERM to all sessions and closes their PTYs.
func (p *Pool) CloseAll() {
p.mu.Lock()
defer p.mu.Unlock()
for name, sess := range p.sessions {
if sess.Cmd.Process != nil {
sess.Cmd.Process.Signal(syscall.SIGTERM)
}
sess.PTY.Close()
delete(p.sessions, name)
}
}