feat: notify when managed browsers exit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"chub/internal/platform/browser"
|
||||
"chub/internal/ui"
|
||||
)
|
||||
|
||||
// managedExitObserver keeps lifecycle observation scoped to a browser process
|
||||
// Chub started in the current application session. It never looks up a process
|
||||
// by name or attaches to an external browser.
|
||||
type managedExitObserver interface {
|
||||
Watch(instanceID string, launchGeneration uint64, handle browser.ProcessHandle)
|
||||
ExpectStop(instanceID string, launchGeneration uint64, pid int) bool
|
||||
ClearExpectedStop(instanceID string, launchGeneration uint64, pid int)
|
||||
}
|
||||
|
||||
type managedExitProcessKey struct {
|
||||
instanceID string
|
||||
launchGeneration uint64
|
||||
pid int
|
||||
}
|
||||
|
||||
type managedExitMonitor struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
report func(ui.ManagedInstanceExit)
|
||||
invalidate func()
|
||||
|
||||
mu sync.Mutex
|
||||
active map[string]managedExitProcessKey
|
||||
expected map[managedExitProcessKey]bool
|
||||
}
|
||||
|
||||
func newManagedExitMonitor(report func(ui.ManagedInstanceExit), invalidate func()) *managedExitMonitor {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &managedExitMonitor{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
report: report,
|
||||
invalidate: invalidate,
|
||||
active: make(map[string]managedExitProcessKey),
|
||||
expected: make(map[managedExitProcessKey]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops notification delivery without stopping browsers. Process waits
|
||||
// observe this context and return, while the launcher's own registry cleanup
|
||||
// continues independently.
|
||||
func (m *managedExitMonitor) Close() {
|
||||
if m != nil && m.cancel != nil {
|
||||
m.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *managedExitMonitor) Watch(instanceID string, launchGeneration uint64, handle browser.ProcessHandle) {
|
||||
if m == nil || handle == nil || instanceID == "" || launchGeneration == 0 || handle.PID() <= 0 {
|
||||
return
|
||||
}
|
||||
key := managedExitProcessKey{instanceID: instanceID, launchGeneration: launchGeneration, pid: handle.PID()}
|
||||
m.mu.Lock()
|
||||
if _, alreadyWatching := m.active[instanceID]; alreadyWatching {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
m.active[instanceID] = key
|
||||
m.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
exitCode, _ := handle.Wait(m.ctx)
|
||||
if m.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
expected := m.expected[key]
|
||||
delete(m.expected, key)
|
||||
if current, ok := m.active[instanceID]; ok && current == key {
|
||||
delete(m.active, instanceID)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
if m.report != nil {
|
||||
m.report(ui.ManagedInstanceExit{InstanceID: instanceID, LaunchGeneration: launchGeneration, PID: key.pid, ExitCode: exitCode, ExpectedStop: expected})
|
||||
}
|
||||
if m.invalidate != nil {
|
||||
m.invalidate()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *managedExitMonitor) ExpectStop(instanceID string, launchGeneration uint64, pid int) bool {
|
||||
if m == nil || instanceID == "" || launchGeneration == 0 || pid <= 0 {
|
||||
return false
|
||||
}
|
||||
key := managedExitProcessKey{instanceID: instanceID, launchGeneration: launchGeneration, pid: pid}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if current, ok := m.active[instanceID]; !ok || current != key {
|
||||
return false
|
||||
}
|
||||
m.expected[key] = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *managedExitMonitor) ClearExpectedStop(instanceID string, launchGeneration uint64, pid int) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
key := managedExitProcessKey{instanceID: instanceID, launchGeneration: launchGeneration, pid: pid}
|
||||
m.mu.Lock()
|
||||
delete(m.expected, key)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user