145 lines
2.5 KiB
Go
145 lines
2.5 KiB
Go
package app
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"termtap.dev/internal/model"
|
|
"termtap.dev/internal/process"
|
|
"termtap.dev/internal/proxy"
|
|
)
|
|
|
|
type Session struct {
|
|
Events <-chan model.Event
|
|
|
|
ch chan model.Event
|
|
proxy *model.ProxyServer
|
|
proc *model.Process
|
|
cmd model.Command
|
|
addr string
|
|
once sync.Once
|
|
|
|
restartMu sync.Mutex
|
|
restarting bool
|
|
stopped bool
|
|
}
|
|
|
|
var ErrRestartInProgress = errors.New("restart already in progress")
|
|
var ErrSessionStopped = errors.New("session is stopped")
|
|
|
|
func StartSession(cmd model.Command, addr string) (*Session, error) {
|
|
msgs := make(chan model.Event, 256)
|
|
|
|
ps, err := proxy.NewProxyServer(addr, msgs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
go StartProxy(ps, msgs)
|
|
|
|
proc, err := StartProcess(cmd, addr, msgs)
|
|
if err != nil {
|
|
proxy.Destroy(ps, msgs)
|
|
return nil, err
|
|
}
|
|
|
|
return &Session{
|
|
Events: msgs,
|
|
ch: msgs,
|
|
proxy: ps,
|
|
proc: proc,
|
|
cmd: cmd,
|
|
addr: addr,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Session) Stop() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
|
|
s.once.Do(func() {
|
|
s.restartMu.Lock()
|
|
s.stopped = true
|
|
proc := s.proc
|
|
proxyServer := s.proxy
|
|
ch := s.ch
|
|
s.restartMu.Unlock()
|
|
|
|
StopProcess(proc, ch, syscall.SIGTERM)
|
|
proxy.Destroy(proxyServer, ch)
|
|
})
|
|
}
|
|
|
|
func (s *Session) RestartProcess() error {
|
|
if s == nil {
|
|
return fmt.Errorf("session is nil")
|
|
}
|
|
|
|
s.restartMu.Lock()
|
|
if s.stopped {
|
|
s.restartMu.Unlock()
|
|
return ErrSessionStopped
|
|
}
|
|
if s.restarting {
|
|
s.restartMu.Unlock()
|
|
return ErrRestartInProgress
|
|
}
|
|
s.restarting = true
|
|
current := s.proc
|
|
cmd := s.cmd
|
|
addr := s.addr
|
|
ch := s.ch
|
|
s.restartMu.Unlock()
|
|
|
|
defer func() {
|
|
s.restartMu.Lock()
|
|
s.restarting = false
|
|
s.restartMu.Unlock()
|
|
}()
|
|
|
|
ch <- model.Event{
|
|
Time: time.Now().Local(),
|
|
Type: model.EventTypeProcessRestarting,
|
|
Body: fmt.Sprintf("restarting process '%s'", process.CommandString(cmd)),
|
|
}
|
|
|
|
if current != nil {
|
|
StopProcess(current, ch, syscall.SIGTERM)
|
|
if !waitForProcessStop(current, 3*time.Second) {
|
|
return fmt.Errorf("timeout while waiting for process to stop")
|
|
}
|
|
}
|
|
|
|
proc, err := StartProcess(cmd, addr, ch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.restartMu.Lock()
|
|
defer s.restartMu.Unlock()
|
|
if s.stopped {
|
|
StopProcess(proc, ch, syscall.SIGTERM)
|
|
return fmt.Errorf("session stopped during restart: %w", ErrSessionStopped)
|
|
}
|
|
s.proc = proc
|
|
|
|
return nil
|
|
}
|
|
|
|
func waitForProcessStop(proc *model.Process, timeout time.Duration) bool {
|
|
if proc == nil || proc.Done == nil {
|
|
return true
|
|
}
|
|
|
|
select {
|
|
case <-proc.Done:
|
|
return true
|
|
case <-time.After(timeout):
|
|
return false
|
|
}
|
|
}
|