The runtime says a state or log operation of a bundle that failed or timed out, with its time (issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -345,6 +345,7 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
|
||||
go func(m message) {
|
||||
var result any = map[string]any{}
|
||||
var err error
|
||||
asked := time.Now()
|
||||
switch m.Method {
|
||||
case "mesh/publish":
|
||||
err = mesh.Publish(m.Params)
|
||||
@@ -479,6 +480,13 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
|
||||
}
|
||||
return
|
||||
}
|
||||
// A state or log operation the bus refused or let time out reaches a caller as a call that
|
||||
// does not answer: say it here, with how long it took, so the next hang is readable.
|
||||
if err != nil && (strings.HasPrefix(m.Method, "mesh/state.") || strings.HasPrefix(m.Method, "mesh/log.") ||
|
||||
busStateCalls[m.Method]) {
|
||||
logf("[mesh-tools] %s's bundle asked %s and it failed after %s: %v", module, m.Method,
|
||||
time.Since(asked).Round(time.Millisecond), err)
|
||||
}
|
||||
if len(m.ID) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package launch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type refusingState struct{ subscribing }
|
||||
|
||||
func (*refusingState) State(string, json.RawMessage) (json.RawMessage, error) {
|
||||
return nil, errors.New("the bus did not answer in 10s")
|
||||
}
|
||||
|
||||
// A bundle that asks the runtime for a state once it is started, as a tool does.
|
||||
const askingBundle = `#!/bin/sh
|
||||
while IFS= read -r line; do
|
||||
id=$(printf '%s' "$line" | sed -n 's/.*"id":\([0-9]*\)[,}].*/\1/p')
|
||||
case "$line" in
|
||||
*'"method":"initialize"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{}}\n' "$id" ;;
|
||||
*'"method":"tools/list"'*) printf '{"jsonrpc":"2.0","id":%s,"result":{"tools":[]}}\n' "$id"
|
||||
printf '{"jsonrpc":"2.0","id":"q1","method":"mesh/state.get","params":{"state":"copied","key":"k"}}\n' ;;
|
||||
esac
|
||||
done
|
||||
`
|
||||
|
||||
// A state operation the bus refuses or lets time out is said in the runtime's log with its method, its bundle and its
|
||||
// time (issue 1966: a hand-act seat whose calls hung said nothing).
|
||||
func TestAFailedStateOperationIsSaidInTheRuntimesLog(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
entry := filepath.Join(dir, "bundle")
|
||||
if err := os.WriteFile(entry, []byte(askingBundle), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var mu sync.Mutex
|
||||
var lines []string
|
||||
logf := func(f string, a ...any) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
lines = append(lines, fmt.Sprintf(f, a...))
|
||||
}
|
||||
l, err := Start("asker", entry, os.Environ(), &refusingState{}, logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(l.Stop)
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
mu.Lock()
|
||||
all := strings.Join(lines, "\n")
|
||||
mu.Unlock()
|
||||
if strings.Contains(all, "asker's bundle asked mesh/state.get and it failed after") && strings.Contains(all, "did not answer in 10s") {
|
||||
return
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("the failed state operation was not said; the log: %v", lines)
|
||||
}
|
||||
Reference in New Issue
Block a user