The tray says why the engine is not running, and setup will not run under a live app
Seen on the demo PC: Start did nothing and Stop stayed grey. The supervisor's engine had failed because a second engine already held port 8010, and the tray reported that as nothing at all. The supervisor now keeps the engine's last lines and turns the known ones into a sentence - 'port 8010 is already in use - another Behavision or its engine is still running', 'run behavision-setup again' - which the tray and the window show. Tray clicks no longer run on the menu loop, so a stop that waits for the process cannot make the menu look dead. Two ways that second process came to exist are closed: setup refuses to run while Behavision.exe or the agent is up, and the app watches agent.json so a claim made underneath it - which rotates the API token - is picked up instead of leaving camera sync refused until a restart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
@@ -92,6 +92,12 @@ func run() error {
|
||||
}
|
||||
}
|
||||
|
||||
if running := behavisionRunning(); running != "" {
|
||||
return fmt.Errorf("%s is running. Quit Behavision from the tray icon first, then run setup again.\n\n"+
|
||||
"Setting up underneath a running copy starts a second engine on the same port and, in a demo,\n"+
|
||||
"re-claims the shop while the open app still holds the old credentials.", running)
|
||||
}
|
||||
|
||||
py, ver, err := findPython()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -210,6 +216,22 @@ func engineSource() (string, error) {
|
||||
// `py -3` first on Windows: the launcher is what the official installer puts
|
||||
// on PATH, and `python` there is often the Microsoft Store stub that prints an
|
||||
// advert and exits 9009 instead of running anything.
|
||||
// behavisionRunning names a Behavision process if one is up. Windows only -
|
||||
// that is the platform setup ships on - and by image name via tasklist, which
|
||||
// needs no extra privilege.
|
||||
func behavisionRunning() string {
|
||||
if runtime.GOOS != "windows" {
|
||||
return ""
|
||||
}
|
||||
for _, name := range []string{"Behavision.exe", "behavision-agent.exe"} {
|
||||
out, err := exec.Command("tasklist", "/FI", "IMAGENAME eq "+name, "/NH").Output()
|
||||
if err == nil && strings.Contains(strings.ToLower(string(out)), strings.ToLower(name)) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findPython() (string, string, error) {
|
||||
type cand struct {
|
||||
exe string
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -218,13 +219,26 @@ func (s *Supervisor) runOnce(ctx context.Context) error {
|
||||
kill = k
|
||||
defer release()
|
||||
|
||||
// The last few lines the engine printed travel with the failure, because
|
||||
// "engine exited: exit status 1" sends somebody to a log file on a shop
|
||||
// PC, and the one line that matters - "port 8010 is already in use" - was
|
||||
// right there.
|
||||
var tailMu sync.Mutex
|
||||
var tail []string
|
||||
pumped := make(chan struct{})
|
||||
go func() {
|
||||
defer close(pumped)
|
||||
sc := bufio.NewScanner(stdout)
|
||||
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||
for sc.Scan() {
|
||||
fmt.Fprintln(s.opts.LogWriter, sc.Text())
|
||||
line := sc.Text()
|
||||
fmt.Fprintln(s.opts.LogWriter, line)
|
||||
tailMu.Lock()
|
||||
tail = append(tail, line)
|
||||
if len(tail) > 12 {
|
||||
tail = tail[1:]
|
||||
}
|
||||
tailMu.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -238,6 +252,12 @@ func (s *Supervisor) runOnce(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
if waitErr != nil {
|
||||
tailMu.Lock()
|
||||
reason := explain(tail)
|
||||
tailMu.Unlock()
|
||||
if reason != "" {
|
||||
return fmt.Errorf("%s (%v)", reason, waitErr)
|
||||
}
|
||||
return fmt.Errorf("engine exited: %w", waitErr)
|
||||
}
|
||||
return errors.New("engine exited unexpectedly with status 0")
|
||||
@@ -373,3 +393,28 @@ func LogFile(path string) (*os.File, error) {
|
||||
}
|
||||
return os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
||||
}
|
||||
|
||||
// explain turns the engine's last output into the sentence the tray shows.
|
||||
// The cases are the ones seen on real installs; anything else shows the last
|
||||
// non-empty line verbatim.
|
||||
func explain(tail []string) string {
|
||||
last := ""
|
||||
for _, l := range tail {
|
||||
low := strings.ToLower(l)
|
||||
switch {
|
||||
case strings.Contains(low, "address already in use") || strings.Contains(low, "only one usage of each socket address"):
|
||||
return "port 8010 is already in use - another Behavision or its engine is still running"
|
||||
case strings.Contains(low, "no module named behavision"):
|
||||
return "the engine is not installed in this Python - run behavision-setup again"
|
||||
case strings.Contains(low, "modulenotfounderror") || strings.Contains(low, "importerror"):
|
||||
return "the engine is missing a library - run behavision-setup again"
|
||||
}
|
||||
if strings.TrimSpace(l) != "" {
|
||||
last = strings.TrimSpace(l)
|
||||
}
|
||||
}
|
||||
if len(last) > 120 {
|
||||
last = last[:120] + "…"
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user