proc

package
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: GPL-3.0 Imports: 27 Imported by: 0

Documentation

Overview

Package proc implements the magus "process adoption" mechanism: child magus processes detect MAGUS_DAEMON_SOCKET and forward work over a Unix-domain socket RPC, sharing the parent's cache, logger, and concurrency budget.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrAlreadyAdopted is returned by New when MAGUS_DAEMON_SOCKET is already set.
	ErrAlreadyAdopted = errors.New("proc: already running under a parent magus")

	// ErrCycleDetected is set in runReply.Err when the same (target, project) pair is already in-flight.
	ErrCycleDetected = errors.New("proc: cycle detected in nested magus invocation")
)
View Source
var (
	// ErrNotAdoptable: the daemon cannot service this subcommand (only run and
	// affected adopt a daemon); the client runs it locally.
	ErrNotAdoptable error = &notAdoptedError{"proc: subcommand not adoptable"}

	// ErrVersionMismatch: the client's build version differs from the daemon's.
	ErrVersionMismatch error = &notAdoptedError{"proc: version mismatch between parent and child magus"}

	// ErrProtocolMismatch: the client sent an unrecognized non-empty Protocol value.
	ErrProtocolMismatch error = &notAdoptedError{"proc: protocol version mismatch"}
)

The not-adopted daemon sentinels. Their MESSAGE strings are the wire contract: the daemon serializes them and the client string-matches to rebuild the typed value (decodeWireError), so keep the messages stable across identifier renames.

View Source
var ErrMultipleServers = errors.New("multiple proc servers found; use --socket to select one")

ErrMultipleServers reports that discovery found several live proc servers and will not choose between them. A sentinel because a caller has to tell it apart from "nothing is running": several candidates and none send a reader somewhere different, and folding the first into the second reports a busy machine as an idle one.

Functions

func AcquireService

func AcquireService(ctx context.Context, addr, key string, svc spells.Service) error

AcquireService asks the daemon at addr to start (or reuse) a shared service and keep it warm past this invocation, returning once it is ready. addr accepts a unix:// URL or a bare path.

func AlreadyReported added in v0.4.0

func AlreadyReported(err error) bool

AlreadyReported reports whether err - or any error it wraps - says its failure has already been explained to the user, so its own text carries no information worth showing. A dispatch that printed a diagnostic and then returned a bare sentinel is the case: the sentinel's message describes magus's control flow, not the failure.

It exists because that fact has to cross a process boundary. The server sends the error's text in runReply.Err and the client prints it; without a way to ask, every adopted failure would report itself with whatever placeholder the sentinel carries. Same shape as NotAdopted above, for the same reason: the classification belongs on the error, not in a list of strings to match.

func CwdFromContext

func CwdFromContext(ctx context.Context) string

CwdFromContext returns the child's working directory stored by the proc server, or "".

func DiscoverSocket

func DiscoverSocket(ctx context.Context) (string, error)

DiscoverSocket scans SockDir for a live magus-*.sock file, preferring the stable daemon socket. Used where exactly one server has to be chosen to talk to.

The stable socket short-circuits the scan, so a machine running the daemon plus ad-hoc per-process servers still resolves to the daemon rather than reporting an ambiguity.

func DiscoverSockets added in v0.4.0

func DiscoverSockets(ctx context.Context) ([]string, error)

DiscoverSockets returns every live proc-server address in SockDir, the stable daemon socket first. Each one is a separate concurrency pool, so a reporter (`magus status`) enumerates them instead of demanding the caller pick one.

func ExitCode added in v0.4.0

func ExitCode(err error) (int, bool)

ExitCode reports the process status err - or any error it wraps - asks for, and whether it asked at all. Same shape as NotAdopted and AlreadyReported above, and for the same reason: the classification has to cross a process boundary that erases the Go type. The CLI's own error types (a usage misuse exits 2, never 1) are in another package proc must not import, so the daemon asks the error rather than naming them.

func Forward

func Forward(ctx context.Context, args []string, version, root string) (int, error)

Forward dials MAGUS_DAEMON_SOCKET, delegates args, and returns the exit code. On any transport error callers should fall back to running locally. Pass "" for root when unknown; the daemon resolves it from Cwd.

func IsJob added in v0.2.0

func IsJob(ctx context.Context) bool

IsJob reports whether ctx belongs to a background job (submitted via SubmitJob) rather than an adopted run. The daemon's dispatch handler branches on it.

func LeaseFromContext added in v0.4.0

func LeaseFromContext(ctx context.Context) string

LeaseFromContext returns the lease the adopted client was launched under, or "" when it claimed none or claimed one that failed validation.

A caller that also reads the environment channel must prefer this: it is the lease of the process that ASKED for the run, while the daemon's own environment describes whoever happened to start the daemon.

func LookupStableSocket

func LookupStableSocket(ctx context.Context) (string, bool)

LookupStableSocket returns the address of the stable daemon socket if alive; bool is false when absent.

func NotAdopted added in v0.2.0

func NotAdopted(err error) bool

NotAdopted reports whether err - or any error it wraps - is a call the daemon did not adopt (a non-adoptable subcommand, or a build/protocol mismatch on an otherwise adoptable one): the daemon answered but will not take the call, so a caller runs it locally and quietly instead of treating it as a failure. Prefer this over matching the individual sentinels: it stays correct as reasons are added and sees through wrapping. Errors that do not implement NotAdopted() (e.g. a transport failure) report false - treat those as genuine forward failures.

func ReleaseService

func ReleaseService(ctx context.Context, addr, key string) error

ReleaseService tells the daemon at addr that this invocation no longer needs the shared service for key; the daemon keeps it warm and reaps it later. addr accepts a unix:// URL or a bare path.

func ReloadConfig added in v0.4.0

func ReloadConfig(ctx context.Context, addr string) (dropped, busy int, err error)

ReloadConfig asks the daemon to drop the workspaces it holds open, so the next command against each reopens it and re-reads its config. It reports how many were dropped and how many were left alone because a run was in flight.

The counterpart of StopAllServices: a partial reset that leaves the daemon running, for the case where editing magus.yaml would otherwise mean restarting it.

func RootFromContext

func RootFromContext(ctx context.Context) string

RootFromContext returns the workspace root stored by the proc server, or "".

func RunChildSync

func RunChildSync(ctx context.Context, lim *cache.Limiter, fn func() error) error

RunChildSync yields the caller's concurrency slot for the duration of fn so a child magus process can acquire it, keeping the total budget flat. If lim is nil or no slot is held fn runs unchanged (avoids over-releasing the semaphore).

func Shutdown

func Shutdown(ctx context.Context, addr string) error

Shutdown dials the proc server at addr and requests a graceful shutdown. addr accepts a unix:// URL or a bare path.

func SockDir

func SockDir() string

SockDir returns the directory where magus proc sockets are stored.

func SocketLive added in v0.2.0

func SocketLive(ctx context.Context, addr string) bool

SocketLive reports whether a daemon is currently accepting on addr, which may be a unix:// URL or a bare socket path. It is the shared liveness probe behind idempotent `server start` (skip when one is already up) and `server stop` verification (confirm the daemon is actually gone after a shutdown request). A malformed address is treated as not-live rather than an error, since callers only care whether a daemon answers.

func StableSocketName

func StableSocketName() string

StableSocketName returns the file basename of the stable multi-workspace daemon socket.

func StopAllServices

func StopAllServices(ctx context.Context, addr string) (int, error)

StopAllServices asks the daemon at addr to stop every service it hosts (leaving the daemon running) and returns how many were stopped. addr accepts a unix:// URL or a bare path.

func SubmitJob added in v0.2.0

func SubmitJob(ctx context.Context, addr string, args []string, version string) (string, error)

SubmitJob dials the proc server at addr and submits a fire-and-forget background job - the daemon runs `magus <args>` asynchronously and this returns as soon as it is accepted, with the job's invocation id (a Dashboard deep-link). It scopes the job to the caller's working directory (computed here, like Forward, so there is no transposition-prone dir argument); the daemon walks up from it to the workspace root. Used by the VCS refresh hook, which must not block a checkout. addr accepts a unix:// URL or a path. version is the caller's build version; it is sent as an adoption identity (see adoptionIdentity) so a background job is version-gated exactly like a forwarded run - a stale dev daemon will not silently run a fresh client's job with the wrong code.

func WithCwd added in v0.2.0

func WithCwd(ctx context.Context, cwd string) context.Context

WithCwd returns ctx carrying the client's working directory, readable via CwdFromContext.

func WithLease added in v0.4.0

func WithLease(ctx context.Context, lease string) context.Context

WithLease returns ctx carrying the client's lease, readable via LeaseFromContext. An id failing types.ValidLeaseID - including the empty string a client that predates the field sends - stores nothing, so a reader sees "".

The validation is here rather than at the call site because the value crosses a socket any local process may dial: a lease id is exempt from the trail's redaction, so storing an unvalidated one would let the wire carry a credential onto an event line. Dropping it matches what trail.LeaseFromEnv does with a malformed environment value.

func WithRoot added in v0.2.0

func WithRoot(ctx context.Context, root string) context.Context

WithRoot returns ctx carrying the client-sent workspace root, readable via RootFromContext.

func WithSubOp

func WithSubOp(ctx context.Context, op *SubOp) context.Context

WithSubOp injects op into ctx for display in magus status.

Types

type Call

type Call struct {
	Args      []string  `json:"args"`
	Workspace string    `json:"workspace,omitempty"`  // empty for pre-workspace-aware servers
	StartedAt time.Time `json:"started_at,omitempty"` // zero for pre-timing-aware servers
	SubOp     string    `json:"sub_op,omitempty"`     // short label of what the call is doing now
	Inv       string    `json:"inv,omitempty"`        // the invocation id this call runs under; deep-links to its live log
}

Call describes a single adopted call currently executing.

type DaemonAdmitter added in v0.4.0

type DaemonAdmitter struct{ Addr string }

DaemonAdmitter reaches the machine budget in the daemon at Addr. It is the socket implementation of cache.MachineAdmitter, and the only thing that makes a magus running here answerable to a magus running in another worktree.

func (DaemonAdmitter) Drop added in v0.4.0

func (d DaemonAdmitter) Drop(ctx context.Context, waiter string)

Drop retires a waiter that gave up, for the same reason and with the same tolerance.

func (DaemonAdmitter) Release added in v0.4.0

func (d DaemonAdmitter) Release(ctx context.Context, id string)

Release returns a granted claim. Errors are dropped: a release that cannot be delivered is retired by the budget's own liveness reap, and a teardown must not fail over bookkeeping.

func (DaemonAdmitter) Request added in v0.4.0

Request polls the budget on behalf of waiter.

type Options

type Options struct {
	Handler         func(ctx context.Context, args []string) error // required; ctx carries Root/Cwd
	Context         context.Context                                // nil → context.Background
	Limiter         *cache.Limiter                                 // shared budget; nil → private limiter
	Concurrency     int                                            // ignored when Limiter is set; 0 → default
	Version         string                                         // "" disables version-mismatch check
	Address         string                                         // "" → auto-generate in SockDir()
	WorkspaceLister func() []Workspace                             // optional; used by daemon Status RPC
	ServiceLister   func() []types.StatusService                   // optional; hosted-services snapshot for the daemon Status RPC
	ServiceHost     ServiceHost                                    // optional; hosts shared services across invocations (daemon only)
	// OnJobDone, if set, is called after every BACKGROUND job (submitJob) completes - never for
	// an adopted foreground run - with the job's args, wall-clock duration, and outcome. The
	// ctx still carries Root/Cwd. The daemon uses it to record a KIND_JOB activity event; proc
	// stays decoupled from the trail and cache layout.
	OnJobDone func(ctx context.Context, args []string, dur time.Duration, err error)
	// ConfigReloader, if set, drops the workspaces the daemon is holding open so the next
	// command against each reopens it and re-reads its config. It reports how many were
	// dropped and how many were left alone as busy. Only the daemon sets it; a per-process
	// proc server holds one workspace for one invocation and has nothing to reload.
	ConfigReloader func() (dropped, busy int)
	// MachineBudget, if set, makes this server the arbiter of machine-wide admission:
	// every magus on the host asks it before starting a step. Only the daemon sets it,
	// and only one daemon exists per user, which is what makes the budget the machine's
	// rather than a process's.
	MachineBudget *cache.MachineBudget
}

Options configures the proc server created by New.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server listens on a Unix-domain socket and accepts forwarded RPC requests from child processes.

func New

func New(opts Options) (*Server, error)

New constructs an unstarted Server; returns ErrAlreadyAdopted when MAGUS_DAEMON_SOCKET is set. Call Start to bind the socket.

func (*Server) Addr

func (s *Server) Addr() string

Addr returns the canonical unix:// URL that children dial. Valid after New.

func (*Server) Close

func (s *Server) Close()

Close shuts down the listener, removes the socket file, and waits for all in-flight handlers. Safe to call multiple times.

func (*Server) Done added in v0.2.0

func (s *Server) Done() <-chan struct{}

Done returns a channel closed when the server has been Closed, whether by an RPC shutdown request or a signal. A blocking daemon loop selects on it so an RPC-driven `magus server stop` unblocks the process the same way a signal does: without it the shutdown handler tears down the listener but the process keeps running, since the listener's context is a sibling of the process context, not its parent.

func (*Server) IdleFor added in v0.4.0

func (s *Server) IdleFor(now time.Time) (idle time.Duration, busy bool)

IdleFor reports how long since a client last asked this server for anything, and whether it is doing nothing right now. A daemon nobody asked for uses the pair to decide it is no longer wanted; see the admission self-exit in cmd/magus.

Busy covers work in flight AND the machine budget, because a daemon holding claims is serving runs that are not talking to it: they took their claim, went quiet for the length of a build, and will come back to release it. Exiting under them would drop every claim on the machine.

func (*Server) Start

func (s *Server) Start() error

Start binds the socket and begins serving. Must be called once; on error the Server is unusable.

type ServiceHost

type ServiceHost interface {
	// Acquire starts (or reuses) the service identified by key, returning once it is
	// ready, and increments its dependent count.
	Acquire(ctx context.Context, key string, svc spells.Service) error
	// Release drops one dependent of key; the host keeps it warm and reaps it later.
	Release(key string)
	// StopAll stops every hosted service and returns how many were stopped, leaving
	// the daemon running.
	StopAll() int
}

ServiceHost hosts long-running shared services on behalf of adopted magus invocations, keeping them warm across separate runs. The daemon supplies one via Options; a per-process proc server leaves it nil (no cross-invocation hosting). Acquire/Release mirror the ref-counted lifecycle of cache.Limiter and service.Registry, the shared vocabulary for held resources.

type StatusReply

type StatusReply struct {
	ParentPID     int         `json:"parent_pid"`
	DaemonVersion string      `json:"daemon_version,omitempty"`
	Mode          string      `json:"mode,omitempty"` // "daemon" (multi-workspace) | "proc" (per-process)
	Capacity      int         `json:"capacity"`
	Running       int         `json:"running"`
	Queued        int         `json:"queued"`
	Calls         []Call      `json:"calls,omitempty"`
	Workspaces    []Workspace `json:"workspaces,omitempty"` // nil for per-process proc servers
	// Services are the long-running shared services the daemon is hosting right now.
	// Nil for a per-process proc server (no cross-invocation service host).
	Services []types.StatusService `json:"services,omitempty"`
	// Machine is the host-wide admission budget this daemon arbitrates: what every
	// magus on the machine holds and who is queued for it. Nil for a per-process proc
	// server, which arbitrates nothing beyond itself.
	Machine *types.MachineSnapshot `json:"machine,omitempty"`
}

StatusReply carries a point-in-time view of the parent's pool.

func QueryStatus

func QueryStatus(ctx context.Context, addr string) (*StatusReply, error)

QueryStatus dials the proc server at addr and returns a live pool snapshot. addr accepts a unix:// URL or a bare path.

type SubOp

type SubOp struct {
	// contains filtered or unexported fields
}

SubOp holds the current sub-operation label for an inflight request. Concurrent-safe; all methods are nil-safe.

func SubOpFromContext

func SubOpFromContext(ctx context.Context) *SubOp

SubOpFromContext returns the SubOp in ctx, or nil.

func (*SubOp) Load

func (o *SubOp) Load() string

Load returns the current sub-operation label, or "" if none is set.

func (*SubOp) Set

func (o *SubOp) Set(s string)

Set records s as the current sub-operation label, or clears it when s is empty.

type Workspace

type Workspace struct {
	Root       string    `json:"root"`
	LoadedAt   time.Time `json:"loaded_at"`
	LastAccess time.Time `json:"last_access"`
	// Live cache activity for this workspace's long-lived cache. Zero for pre-cache-aware
	// daemons or an Inspect workspace with no cache.
	CacheHit   int   `json:"cache_hit,omitempty"`
	CacheMiss  int   `json:"cache_miss,omitempty"`
	CacheError int   `json:"cache_error,omitempty"`
	CacheBytes int64 `json:"cache_bytes,omitempty"`
	// Work the hits replayed instead of ran, summed from each entry's recorded duration.
	CacheSavedMs int64 `json:"cache_saved_ms,omitempty"`
	// SecretProvider is the selected provider spell's name; empty = built-in env provider.
	SecretProvider string `json:"secret_provider,omitempty"`
}

Workspace describes one workspace currently loaded by the daemon.

Directories

Path Synopsis
Package endpoint is the parsed-transport-address value type, split out of internal/proc as a leaf with no OS or daemon dependencies (only context/fmt/net/strings).
Package endpoint is the parsed-transport-address value type, split out of internal/proc as a leaf with no OS or daemon dependencies (only context/fmt/net/strings).
Package run is the shared subprocess helper for magus spells.
Package run is the shared subprocess helper for magus spells.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL