Supervisor probes before spawning: an external server on the port is used, never fought or killed
This commit is contained in:
@@ -29,6 +29,7 @@ type Process struct {
|
||||
cmd *exec.Cmd
|
||||
stopping bool
|
||||
ready bool
|
||||
external bool // someone else serves the port; not our process
|
||||
lastActivity time.Time
|
||||
}
|
||||
|
||||
@@ -118,7 +119,10 @@ func (p *Process) NoteActivity() {
|
||||
|
||||
// EnsureRunning starts the child if it is not running. It returns as soon
|
||||
// as the process is spawned; readiness is WaitReady's job (and the proxy's
|
||||
// retry backoff bridges the gap for plain proxied requests).
|
||||
// retry backoff bridges the gap for plain proxied requests). When the URL
|
||||
// already answers — e.g. the ComfyUI desktop app grabbed the port — no
|
||||
// child is spawned: the external server is used as-is, and the idle
|
||||
// watcher never touches it (it only kills its own child).
|
||||
func (p *Process) EnsureRunning() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
@@ -126,6 +130,18 @@ func (p *Process) EnsureRunning() error {
|
||||
if p.cmd != nil {
|
||||
return nil
|
||||
}
|
||||
pctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
err := p.probe(pctx)
|
||||
cancel()
|
||||
if err == nil {
|
||||
p.ready = true
|
||||
if !p.external {
|
||||
p.external = true
|
||||
p.log.Info(p.name + " is already served externally; not spawning a managed instance")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
p.external = false
|
||||
cmd := exec.Command(p.argv[0], p.argv[1:]...)
|
||||
cmd.Dir = p.dir
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
|
||||
@@ -57,8 +57,10 @@ func TestHelperProcess(t *testing.T) {
|
||||
|
||||
func newHelper(t *testing.T, name string) *Process {
|
||||
t.Helper()
|
||||
// The probe always fails: nothing external serves the port, so
|
||||
// EnsureRunning spawns the helper child.
|
||||
p, err := New(name, `"`+os.Args[0]+`" -test.run=TestHelperProcess`, "",
|
||||
func(context.Context) error { return nil }, 5*time.Second, slog.Default())
|
||||
func(context.Context) error { return errors.New("nothing there") }, 5*time.Second, slog.Default())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -103,6 +105,38 @@ func TestEnsureRunningAndStop(t *testing.T) {
|
||||
waitStopped(t, p, 5*time.Second)
|
||||
}
|
||||
|
||||
func TestEnsureRunningPrefersExternalServer(t *testing.T) {
|
||||
// The port is already served (e.g. the ComfyUI desktop app): no child
|
||||
// is spawned, the supervisor reports ready, and Stop is a no-op.
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer srv.Close()
|
||||
probe := func(ctx context.Context) error {
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL, nil)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
p, err := New("external", `"`+os.Args[0]+`"`, "", probe, 5*time.Second, slog.Default())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := p.EnsureRunning(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p.Running() {
|
||||
t.Fatal("spawned a child even though the port is already served")
|
||||
}
|
||||
if !p.Ready() {
|
||||
t.Fatal("external server should count as ready")
|
||||
}
|
||||
p.Stop() // must not touch the external server
|
||||
}
|
||||
|
||||
func TestWaitReady(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
Reference in New Issue
Block a user