process

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package process owns a local command's start, wait, and process-group cleanup. It supports Unix hosts. Process groups are lifetime boundaries, not sandboxes: a descendant that creates another session can escape the group.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidConfig       = errors.New("process: invalid configuration")
	ErrNotStarted          = errors.New("process: not started")
	ErrStarted             = errors.New("process: already started")
	ErrClosed              = errors.New("process: closed")
	ErrUnsupportedPlatform = errors.New("process: process-group cleanup requires Unix")
)

Functions

This section is empty.

Types

type Config

type Config struct {
	Argv      []string
	Directory string
	Env       []string
	Stdin     io.Reader
	Stdout    io.Writer
	Stderr    io.Writer
	// WaitDelay bounds inherited command pipes after exit or cancellation.
	// Zero selects one second. A limit expiry is reported as exec.ErrWaitDelay
	// when exec.Cmd cannot otherwise report the command's failure.
	WaitDelay time.Duration
}

Config is frozen by New, except for the supplied I/O objects. Argv includes the executable as its first element. Directory must be explicit; it controls relative paths without confining the command. A nil Env snapshots the host environment; an empty non-nil Env starts with no inherited variables.

I/O objects remain caller-owned. As with exec.Cmd, non-file streams use copy goroutines. Their Read and Write methods must return when their own lifetime ends: WaitDelay can close command pipes, but cannot interrupt arbitrary Go code blocked inside those methods. Callers must close their own streams to unblock such operations before waiting for Close.

type Process

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

Process has one execution lifetime, supplied to New. Construct it with New; the zero value is not usable. Start, Wait, and Close may run concurrently. Only its internal worker reaps the command; Wait observes that worker's immutable result, and Close joins it after requesting termination.

Example
package main

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"time"

	"github.com/Tangerg/scope/tools/process"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	stdin, requests, err := os.Pipe()
	if err != nil {
		panic(err)
	}
	defer stdin.Close()
	defer requests.Close()
	responses, stdout, err := os.Pipe()
	if err != nil {
		panic(err)
	}
	defer responses.Close()
	defer stdout.Close()
	command, err := process.New(ctx, process.Config{
		Argv:      []string{"/bin/sh", "-c", `while IFS= read -r request; do printf 'response:%s\n' "$request"; done`},
		Directory: ".", Stdin: stdin, Stdout: stdout,
	})
	if err != nil {
		panic(err)
	}
	if startErr := command.Start(); startErr != nil {
		panic(startErr)
	}
	defer command.Close()
	// The parent owns these pipe handles; the command inherited its own copies.
	if closeErr := stdin.Close(); closeErr != nil {
		panic(closeErr)
	}
	if closeErr := stdout.Close(); closeErr != nil {
		panic(closeErr)
	}
	scanner := bufio.NewScanner(responses)
	for _, request := range []string{"initialize", "ping"} {
		if _, writeErr := fmt.Fprintln(requests, request); writeErr != nil {
			panic(writeErr)
		}
		if !scanner.Scan() {
			panic("missing response")
		}
		fmt.Println(scanner.Text())
	}
	if closeErr := requests.Close(); closeErr != nil {
		panic(closeErr)
	}
	result, err := command.Wait()
	if err != nil {
		panic(err)
	}
	fmt.Println("exit:", result.ExitCode)
}
Output:
response:initialize
response:ping
exit: 0

func New

func New(ctx context.Context, config Config) (*Process, error)

func (*Process) Close

func (p *Process) Close() error

Close is idempotent, requests immediate group termination, and waits for cleanup. Before Start it prevents creation, returns nil, and makes Wait return ErrClosed. Otherwise it returns the same terminal error as Wait.

func (*Process) Start

func (p *Process) Start() error

Start attempts creation once. A failed attempt is terminal and is also returned by Wait and Close; no retry can create a second command lifetime.

func (*Process) Wait

func (p *Process) Wait() (Result, error)

Wait returns ErrNotStarted before a start attempt. After Start or Close, it joins the same completion and preserves the errors reported by exec.Cmd, except that exit status belongs to Result. Group-cleanup failures are joined to that error. Waiting never transfers lifecycle ownership.

type Result

type Result struct {
	ExitCode             int
	CancellationObserved bool
}

Result records the terminal observations shared by every Wait caller. A non-zero exit code is an outcome, not an error; -1 means that no numeric exit status was observed. CancellationObserved does not establish the exit cause. exec.Cmd's collection-error precedence applies: a non-zero exit can mask a simultaneous stream-copy error, and only the first copy error is available.

Jump to

Keyboard shortcuts

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