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
3 changes: 1 addition & 2 deletions e2e/cmd/rpcbench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ func run(addr, token, template string, n int) error {
return nil
}

// statRPC drives one fs_stat over the upgraded conn and closes it — the
// protocol is one RPC per connection.
// statRPC: the protocol is one RPC per connection.
func statRPC(conn net.Conn) error {
defer func() { _ = conn.Close() }()
sc := silkd.NewConn(conn)
Expand Down
6 changes: 2 additions & 4 deletions e2e/cmd/smoke/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func smokeGit(ctx context.Context, sb *sandbox.Sandbox) error {
// smokeEgress claims a second sandbox on the egress lane and pins the
// positive half of silkd's lane detection: git must actually run there, so a
// push with no remote fails inside git — never with the none-lane
// unimplemented guard. Needs no reachable network.
// unimplemented guard.
func smokeEgress(ctx context.Context, client *sandbox.Client, template string) error {
sb, err := client.New(ctx, template, sandbox.WithNetwork(sandbox.NetEgress))
if err != nil {
Expand Down Expand Up @@ -388,8 +388,7 @@ func smokeFork(ctx context.Context, sb *sandbox.Sandbox) error {
// smokePromote proves promote-to-template through the owner-bound handle:
// claiming from it must clone the parent's state — on a real node a cold
// boot of this never-registered image ref would fail, so success itself
// proves the golden path — and delete removes it. The name-based
// DeleteTemplate covers the node-local Client surface too.
// proves the golden path — and delete removes it.
func smokePromote(ctx context.Context, client *sandbox.Client, sb *sandbox.Sandbox) error {
tpl, err := sb.Promote(ctx, "smoke-tpl:v1")
if err != nil {
Expand Down Expand Up @@ -656,7 +655,6 @@ func isSilkdKind(err error, kind string) bool {
return errors.As(err, &er) && er.Kind == kind
}

// lspWrite frames one JSON-RPC message the LSP way (Content-Length header).
func lspWrite(w io.Writer, body string) error {
_, err := fmt.Fprintf(w, "Content-Length: %d\r\n\r\n%s", len(body), body)
return err
Expand Down
3 changes: 0 additions & 3 deletions mcp/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ func toolCreateSandbox(ctx context.Context, s *server, raw json.RawMessage) (str
return jsonText(map[string]any{"sandbox_id": sb.ID, "deadline": sb.Deadline}), nil
}

// sandboxArg is the shared sandbox_id field of the tool argument structs;
// parseAndBox reads it through id().
type sandboxArg struct {
SandboxID string `json:"sandbox_id"`
}
Expand Down Expand Up @@ -388,7 +386,6 @@ func toolNodeInfo(ctx context.Context, s *server, _ json.RawMessage) (string, er
return jsonText(info), nil
}

// boxArg resolves a bare sandbox_id argument to a live handle.
func (s *server) boxArg(raw json.RawMessage) (*sandbox.Sandbox, error) {
_, sb, err := parseAndBox[sandboxArg](s, raw)
return sb, err
Expand Down
37 changes: 15 additions & 22 deletions protocol/wire/frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,8 +668,7 @@ func NewFrameScanner(r io.Reader) *bufio.Scanner {
}

// DecodeResponse parses one frame into its type's concrete Go type. Byte
// fields are freshly allocated per frame, so callers may retain them; pooling
// them would require a copy-out at every retention site first.
// fields are freshly allocated per frame, so callers may retain them.
func DecodeResponse(line []byte) (Response, error) {
typ, err := frameTag(line, respTagHead, "type")
if err != nil {
Expand All @@ -682,12 +681,19 @@ func DecodeResponse(line []byte) (Response, error) {
return dec(line)
}

// fastBulk decodes a bulk frame's base64 data field by slicing it out
// (base64's alphabet is JSON-escape-free) and skipping json.Unmarshal, which
// otherwise dominates the download path. Only the exact canonical shape both
// producers emit — {"type":"<tag>","data":"<base64>"} — takes the slice;
// anything else falls back to slow, the full parse, so no byte of a frame
// escapes validation.
// AppendBulkRequest renders a data-carrying request frame —
// {"v":1,"op":<op>,"data":"<base64>"} plus newline — into buf, reused across
// calls on the bulk send paths (base64's alphabet needs no JSON escaping).
func AppendBulkRequest(buf []byte, op string, data []byte) []byte {
buf = append(buf[:0], requestHead...)
buf = append(buf, op...)
buf = append(buf, `","data":"`...)
buf = base64.StdEncoding.AppendEncode(buf, data)
return append(buf, '"', '}', '\n')
}

// fastBulk slices the base64 data out of a canonical bulk frame, skipping the
// json.Unmarshal that dominates downloads; any other shape falls back to slow.
func fastBulk(tag string, slow func([]byte) (Response, error), mk func([]byte) Response) func([]byte) (Response, error) {
head := []byte(`{"type":"` + tag + `","data":"`)
return func(line []byte) (Response, error) {
Expand All @@ -701,27 +707,14 @@ func fastBulk(tag string, slow func([]byte) (Response, error), mk func([]byte) R
}
out := make([]byte, base64.StdEncoding.DecodedLen(len(b64)))
n, err := base64.StdEncoding.Decode(out, b64)
// Decode skips CR/LF (raw control bytes JSON forbids); a canonical
// payload decodes to exactly the encoded length, so any skip falls
// back to the full parse.
// Decode skips CR/LF, so a length mismatch means non-canonical input.
if err != nil || base64.StdEncoding.EncodedLen(n) != len(b64) {
return slow(line)
}
return mk(out[:n]), nil
}
}

// AppendBulkRequest renders a data-carrying request frame —
// {"v":1,"op":<op>,"data":"<base64>"} plus newline — into buf, reused across
// calls on the bulk send paths (base64's alphabet needs no JSON escaping).
func AppendBulkRequest(buf []byte, op string, data []byte) []byte {
buf = append(buf[:0], requestHead...)
buf = append(buf, op...)
buf = append(buf, `","data":"`...)
buf = base64.StdEncoding.AppendEncode(buf, data)
return append(buf, '"', '}', '\n')
}

// encodeTagged marshals v as a flat object and splices the tag head in front
// of its fields, so wire tags never live on the structs themselves. The
// spare capacity byte lets Conn.Send append the newline without a copy.
Expand Down
7 changes: 3 additions & 4 deletions sandboxd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ type TenantSpec struct {
// that tenant answers 401), and a
// node serving the egress lane can only redirect egress claims to peers if it
// too has an egress attachment (a no-egress node answers 409 rather than
// redirecting). Both are acceptable for a homogeneous cluster.
// redirecting).
type MeshConfig struct {
NodeID string `json:"node_id"` // unique name; defaults to Bind
Bind string `json:"bind"` // memberlist host:port
Expand Down Expand Up @@ -254,9 +254,8 @@ type Config struct {
// addressing fields (never payloads) to <data_dir>/audit.jsonl.
AuditLog bool `json:"audit_log,omitempty"`

// MaxForkCount caps children per fork call — each child is a full-RAM VM,
// so this bounds a single request's memory blast radius to the node's
// capacity. Defaults to 16.
// MaxForkCount caps children per fork call — each child is a full-RAM VM.
// Defaults to 16.
MaxForkCount int `json:"max_fork_count,omitempty"`

// RefillConcurrency caps concurrent VM provisioning node-wide — warm-pool
Expand Down
7 changes: 1 addition & 6 deletions sandboxd/egress/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,7 @@ func (p *Proxy) untrack(conn net.Conn) {
p.connMu.Unlock()
}

// serveConnect gates an HTTPS/opaque tunnel by host. A plain allow hijacks and
// splices the end-to-end TLS to the origin untouched. A matched rule with
// Intercept instead terminates the TLS (interception, see intercept.go) so the
// request is filtered by method and the secret injected; deny answers a typed 403.
// serveConnect gates an HTTPS/opaque tunnel by host.
func (p *Proxy) serveConnect(w http.ResponseWriter, r *http.Request) {
host := hostOnly(r.Host)
// Host-gate the interception decision: the tunnel's CONNECT verb is not the
Expand Down Expand Up @@ -177,8 +174,6 @@ func (p *Proxy) serveConnect(w http.ResponseWriter, r *http.Request) {
splice(client, upstream)
}

// serveForward gates an absolute-form plaintext request by host and method,
// injects the rule's credential, and relays it to the origin.
func (p *Proxy) serveForward(w http.ResponseWriter, r *http.Request) {
if !r.URL.IsAbs() {
http.Error(w, "egress: proxy requires an absolute-form request URI", http.StatusBadRequest)
Expand Down
8 changes: 2 additions & 6 deletions sandboxd/engine/engine.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// Package engine drives VM lifecycle through the cocoon CLI and dials the
// in-guest silkd over hybrid vsock.
//
// cocoon runs as a subprocess deliberately: the CLI is cocoon's stable
// contract (it exports no lifecycle library), it is the exact interface every
// latency figure was measured through, and no lifecycle call sits on the
// warm-claim path.
// in-guest silkd over hybrid vsock. The CLI is cocoon's only stable contract
// (it exports no lifecycle library); no lifecycle call sits on the warm-claim path.
package engine

import (
Expand Down
7 changes: 3 additions & 4 deletions sandboxd/engine/portconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (g *guestPortConn) Read(p []byte) (int, error) {
g.pending = frame.Data
case "done", "":
return 0, io.EOF // the guest closed the forwarded port
default: // error frame or anything terminal
default:
return 0, fmt.Errorf("port stream ended: %s", frame.Type)
}
}
Expand All @@ -87,9 +87,8 @@ func (g *guestPortConn) Write(p []byte) (int, error) {
}

// fastPortData slices the canonical data frame's base64 out without a JSON
// parse — json.Unmarshal otherwise dominates the download relay, and the
// SDK's fastBulk sets the contract: only the exact canonical shape takes the
// slice, anything else falls back to the full parse.
// parse; the SDK's fastBulk sets the contract: only the exact canonical
// shape takes the slice, anything else falls back to the full parse.
func fastPortData(line []byte) ([]byte, bool) {
after, ok := bytes.CutPrefix(line, portDataHead)
if !ok {
Expand Down
10 changes: 3 additions & 7 deletions sandboxd/mesh/mesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,8 @@ func (m *Mesh) Join(seeds []string) error {

// UpdateSelf republishes this node's warm-pool counts and promoted-template
// set, bumping the epoch so peers adopt the new view. An unchanged view is
// not republished — the periodic tick would otherwise gossip a fresh epoch
// every second for nothing. templates must arrive sorted: the unchanged
// compare is order-sensitive.
// not republished. templates must arrive sorted: the unchanged compare is
// order-sensitive.
func (m *Mesh) UpdateSelf(ctx context.Context, pools map[string]int, templates []string) {
m.updateMu.Lock()
defer m.updateMu.Unlock()
Expand All @@ -120,7 +119,7 @@ func (m *Mesh) UpdateSelf(ctx context.Context, pools map[string]int, templates [
m.mu.Unlock()
// Persist the candidate before publishing it: memberlist gossips self the
// instant it enters the view, so a crash before the write would strand peers
// on an epoch a backwards-clock restart can't beat. Hold old state on failure.
// on an epoch a backwards-clock restart can't beat.
if err := m.persistEpoch(epoch); err != nil {
log.WithFunc("mesh.UpdateSelf").Warnf(ctx, "persist epoch: %v", err)
return
Expand Down Expand Up @@ -185,8 +184,6 @@ func (m *Mesh) Candidates(keyHash string) []string {
case 1:
return []string{pool[0].addr}
}
// Power-of-two-choices: sample two, order by warmer. This is load
// spreading, not security — a weak PRNG is the right tool.
i := rand.IntN(len(pool)) //nolint:gosec // placement jitter, not crypto
j := rand.IntN(len(pool) - 1) //nolint:gosec // placement jitter, not crypto
if j >= i {
Expand Down Expand Up @@ -247,7 +244,6 @@ func (m *Mesh) Shutdown() error {
return m.ml.Shutdown()
}

// persistEpoch durably records the epoch.
func (m *Mesh) persistEpoch(epoch uint64) error {
return storeEpoch(m.epochPath, epoch)
}
Expand Down
2 changes: 1 addition & 1 deletion sandboxd/pool/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func (m *Manager) wakeArchived(ctx context.Context, sb *types.Sandbox) (string,
m.recDone(ck)
}
}()
dir, _, release, err := m.ckpts.Fetch(ctx, ck)
dir, _, _, release, err := m.ckpts.Fetch(ctx, ck)
if errors.Is(err, store.ErrNotFound) {
return "", ErrUnknownSandbox // record disagrees with the store
}
Expand Down
2 changes: 1 addition & 1 deletion sandboxd/pool/archive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,7 @@ func mustArchive(t *testing.T, m *Manager, sb *types.Sandbox) {

func ckExists(t *testing.T, m *Manager, ck string) bool {
t.Helper()
_, _, release, err := m.ckpts.Fetch(t.Context(), ck)
_, _, _, release, err := m.ckpts.Fetch(t.Context(), ck) //nolint:dogsled // existence only needs Fetch success
if err != nil {
return false
}
Expand Down
22 changes: 7 additions & 15 deletions sandboxd/pool/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ var (
)

// Checkpoint captures a claimed sandbox's state under a fresh id; the source
// keeps running (a hibernated one is captured from its wake image). Branches
// clone that exact state, and a source can be checkpointed again — a tree.
// tenant attributes the record; empty means the operator (root).
// keeps running (a hibernated one is captured from its wake image). tenant
// attributes the record; empty means the operator (root).
func (m *Manager) Checkpoint(ctx context.Context, id string, cred Cred, name, tenant string) (types.Checkpoint, error) {
sb, ok := m.resolve(id, cred)
if !ok {
Expand Down Expand Up @@ -64,9 +63,8 @@ func (m *Manager) Checkpoint(ctx context.Context, id string, cred Cred, name, te
// is the capability to branch.
func (m *Manager) ClaimCheckpoint(ctx context.Context, ckptID string, ttl time.Duration, tenant string) (*types.Sandbox, error) {
// Reject a bad or unknown id before recLock: a rejected id must not leave
// a lock-map entry (only a delete evicts one). Checkpoints are immutable,
// so this parse stands in for the fetched meta below. It precedes quota:
// a full node must still answer "not here", or the tiers never run.
// a lock-map entry (only a delete evicts one). It precedes quota: a full
// node must still answer "not here".
ckpt, err := m.loadCheckpoint(ctx, ckptID)
if err != nil {
return nil, err
Expand Down Expand Up @@ -126,10 +124,6 @@ func (m *Manager) Checkpoints(ctx context.Context, tenant string) ([]types.Check
// broadcasts to peers when fleet-scoped so a healed copy does not outlive it.
// A tenant may delete only its own records — anything else answers
// ErrUnknownCheckpoint, never a hint the id exists; root deletes anything.
// Existence is checked under the record lock (heal broke the "local miss
// means truly gone" assumption), plus vetoIfHealPending for a heal whose
// transfer runs unlocked. Every exit evicts the lock entry (recDoneEvict) —
// a checkpoint id is effectively one-shot, so the map must not grow per call.
func (m *Manager) DeleteCheckpoint(ctx context.Context, ckptID, tenant string, scope DeleteScope) error {
// Reject a bad id before recLock: a rejected id must not leave a
// lock-map entry.
Expand Down Expand Up @@ -177,7 +171,7 @@ func (m *Manager) FetchCheckpoint(ctx context.Context, ckptID string) (string, [
}
l := m.recLock(ckptID)
l.RLock()
dir, meta, release, err := m.ckpts.Fetch(ctx, ckptID)
dir, meta, _, release, err := m.ckpts.Fetch(ctx, ckptID)
if err != nil {
l.RUnlock()
m.recDone(ckptID)
Expand Down Expand Up @@ -233,7 +227,7 @@ func (m *Manager) claimLoaded(ctx context.Context, ckpt types.Checkpoint, ttl ti
l := m.recLock(ckpt.ID)
l.RLock()
defer func() { l.RUnlock(); m.recDone(ckpt.ID) }()
dir, _, release, err := m.ckpts.Fetch(ctx, ckpt.ID)
dir, _, _, release, err := m.ckpts.Fetch(ctx, ckpt.ID)
if errors.Is(err, store.ErrNotFound) {
return nil, ErrUnknownCheckpoint // deleted between the pre-check and the lock
}
Expand Down Expand Up @@ -382,8 +376,7 @@ func (m *Manager) pinnedArchiveCks() map[string]struct{} {
}

// sweepExpiredCheckpoints ages out checkpoints older than the configured
// TTL; explicit deletes never wait for it. It runs detached from the Run
// loop, so the guard keeps a slow backend from stacking sweeps.
// TTL; explicit deletes never wait for it. It runs detached from the Run loop.
func (m *Manager) sweepExpiredCheckpoints(ctx context.Context) {
if !m.ckptSweeping.CompareAndSwap(false, true) {
return
Expand Down Expand Up @@ -425,7 +418,6 @@ func (m *Manager) deleteCkLocked(ctx context.Context, ckID string) error {
return nil
}

// loadCheckpoint reads and parses a checkpoint's meta from the local store.
func (m *Manager) loadCheckpoint(ctx context.Context, ckptID string) (types.Checkpoint, error) {
if !store.CheckpointIDRe.MatchString(ckptID) {
return types.Checkpoint{}, ErrUnknownCheckpoint
Expand Down
9 changes: 3 additions & 6 deletions sandboxd/pool/hibernate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import (

// Hibernate atomically snapshots a claimed sandbox and stops its VM, freeing
// memory; the next agent access wakes it. Idempotent on an already-hibernated
// sandbox. When to hibernate is the caller's policy — the node only provides
// the transition.
// sandbox.
func (m *Manager) Hibernate(ctx context.Context, id string, cred Cred) error {
sb, ok := m.resolve(id, cred)
if !ok {
Expand Down Expand Up @@ -298,10 +297,8 @@ func (m *Manager) setPendingSnap(sb *types.Sandbox, snap string) claimSnapshot {

// resolvePendingSnap settles a hibernate intent whose engine result was never
// confirmed: the snapshot's presence decides whether to adopt it as
// HibernateSnap or clear the intent. Reports an adopted (completed but
// unrecorded) hibernate so the caller bills it; an unusable snapshot list
// keeps the intent and errors. No-op field read when nothing is pending.
// The caller holds sb.Transition.
// HibernateSnap or clear the intent. An unusable snapshot list keeps the
// intent and errors. The caller holds sb.Transition.
func (m *Manager) resolvePendingSnap(ctx context.Context, sb *types.Sandbox) (adopted bool, err error) {
if sb.PendingSnap == "" {
return false, nil
Expand Down
Loading