Release 3.8.0.1: unify apphost, harden lifecycle and core SHA checks.

This commit is contained in:
M4
2026-07-29 21:43:41 +03:00
parent 64c097d1e7
commit 54b5b87990
26 changed files with 534 additions and 619 deletions
+27 -3
View File
@@ -33,6 +33,8 @@ type Manager struct {
profile *config.Profile
stderr io.Writer
binDir string
// lastStop tracks in-flight engine.Stop so Connect waits for ports to free.
lastStop sync.WaitGroup
}
func NewManager(cfgPath string, cfg *config.Config, stderr io.Writer) (*Manager, error) {
@@ -68,13 +70,25 @@ func (m *Manager) RecoverSystemProxy() {
sysproxy.ClearSentinel(m.cfgPath)
}
func (m *Manager) waitEngineStopped() {
m.lastStop.Wait()
}
func (m *Manager) Connect(ctx context.Context, profileName string) error {
// Wait outside the lock so a slow Disconnect can finish freeing :1080/:1081.
m.waitEngineStopped()
m.mu.Lock()
defer m.mu.Unlock()
if m.engine != nil && m.engine.Running() {
return fmt.Errorf("already connected; disconnect first")
}
if m.engine != nil {
// Defunct reference (stop finished or engine died) — drop before start.
m.engine = nil
m.profile = nil
}
if profileName != "" {
m.cfg.Active = profileName
@@ -122,6 +136,9 @@ func (m *Manager) Disconnect() error {
engine := m.engine
m.engine = nil
m.profile = nil
if engine != nil {
m.lastStop.Add(1)
}
m.mu.Unlock()
var first error
@@ -143,14 +160,17 @@ func (m *Manager) Disconnect() error {
}
if engine != nil {
done := make(chan error, 1)
go func() { done <- engine.Stop() }()
go func() {
defer m.lastStop.Done()
done <- engine.Stop()
}()
select {
case err := <-done:
if err != nil && first == nil {
first = err
}
case <-time.After(3 * time.Second):
// Engine Stop can block on userspace relays; don't freeze the UI.
// Stop continues in background; Connect will wait on lastStop.
if first == nil {
first = fmt.Errorf("отключение заняло слишком много времени")
}
@@ -236,10 +256,14 @@ func (m *Manager) BinDir() string { return m.binDir }
func (m *Manager) ConfigPath() string { return m.cfgPath }
// Config returns a deep snapshot for UI reads. Mutations must go through Manager methods.
func (m *Manager) Config() *config.Config {
m.mu.Lock()
defer m.mu.Unlock()
return m.cfg
if m.cfg == nil {
return &config.Config{}
}
return m.cfg.Clone()
}
func (m *Manager) SetSystemProxy(enabled bool) {