Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ Detailed per-release notes are on the
unknown type; before it answered that no verifier was configured.

### Changed
- **`pilotctl send-message` asks the daemon for its `info` reply once, not
twice.** The auto-handshake read the daemon's `features` from a second
`info` request, although the command had fetched one a moment earlier for
its first-contact check. The reply lists every peer and connection: on a
node with 5,400 peers it is several hundred KB, and parsing it costs the
CLI 7.5 ms of CPU and 2.9 MB of allocations, more than the rest of a send.
The second request happened on every send to an agent in the trusted list
(`list-agents`, `pilot-mom`) and to any public peer not yet trusted.
- **The tunnel socket asks the kernel for 4 MB buffers** in each direction
instead of the default (about 200 KB on Linux). Every tunnel shares the one
socket, and several streams sending at once overflowed it. The kernel caps
Expand Down
13 changes: 10 additions & 3 deletions cmd/pilotctl/firstcontact.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,21 @@ func daemonHasFeature(d *driver.Driver, feature string) bool {
if daemonFeatureSet == nil {
daemonFeatureSet = map[string]bool{}
if info, err := d.Info(); err == nil {
for f := range featuresOf(info) {
daemonFeatureSet[f] = true
}
noteDaemonFeatures(info)
}
}
return daemonFeatureSet[feature]
}

// noteDaemonFeatures fills the feature cache from an info reply the
// command already holds, so daemonHasFeature does not ask for another.
// The reply lists every peer and connection: on a node with a few
// thousand peers it is several hundred KB, and parsing it costs pilotctl
// more CPU than the rest of a send.
func noteDaemonFeatures(info map[string]interface{}) {
daemonFeatureSet = featuresOf(info)
}

func featuresOf(info map[string]interface{}) map[string]bool {
out := map[string]bool{}
list, _ := info["features"].([]interface{})
Expand Down
1 change: 1 addition & 0 deletions cmd/pilotctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5009,6 +5009,7 @@ func cmdSendMessage(args []string) {
firstContact := false
if info, err := d.Info(); err == nil {
firstContact = !peerSessionUp(info, target.Node)
noteDaemonFeatures(info) // maybeAutoHandshake needs them next
}

// Auto-handshake to peers in the embedded trusted-agents list.
Expand Down
37 changes: 37 additions & 0 deletions cmd/pilotctl/zz_firstcontact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,40 @@ func TestAutoHandshakeKeepsHandshakeForOlderDaemon(t *testing.T) {
t.Fatal("older daemon: the trusted-agent handshake request must still be sent")
}
}

// A send asks the daemon for its info reply once. The features the
// auto-handshake checks come from the reply already fetched for the
// first-contact check: the reply lists every peer and connection, so on a
// busy node a second one cost more than the rest of the send.
func TestSendMessageAsksForInfoOnce(t *testing.T) {
sd := newStreamDaemon(t)
sd.useDaemonNoRegistry(t)
daemonFeatureSet = nil
t.Cleanup(func() { daemonFeatureSet = nil })
sd.onJSON(tdCmdInfo, tdCmdInfoOK, `{"node_id":1,"features":["reply_window"]}`)
// Not trusted yet, so the auto-handshake goes on to check features.
sd.onJSON(tdCmdHandshake, tdCmdHandshakeOK, `{"trusted":false}`)

out := captureStdout(t, func() {
withJSON(func() {
cmdSendMessage([]string{"0:0000.0002.BBE4", "--data", "hello"}) // 179172, a trusted agent
})
})
if !strings.Contains(out, `"status":"ok"`) {
t.Fatalf("send failed: %s", out)
}
sd.mu.Lock()
defer sd.mu.Unlock()
infos := 0
for _, f := range sd.received {
if f[0] == tdCmdInfo {
infos++
}
}
if infos != 1 {
t.Fatalf("info requests = %d, want 1", infos)
}
if !daemonFeatureSet["reply_window"] {
t.Fatal("features from the first info reply were not kept")
}
}
Loading