Subprocess Timeout Runner

Run a child process under a context deadline, capture stdout and stderr together, and report a clear timeout error.

GoResilience

Code

snippet
package main

import (
	"bytes"
	"context"
	"errors"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"time"
)

func RunWithTimeout(ctx context.Context, timeout time.Duration, name string, args ...string) (string, error) {
	runCtx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	command := exec.CommandContext(runCtx, name, args...)
	var output bytes.Buffer
	command.Stdout = &output
	command.Stderr = &output
	err := command.Run()

	if errors.Is(runCtx.Err(), context.DeadlineExceeded) {
		return output.String(), fmt.Errorf("%s timed out after %s", name, timeout)
	}
	if err != nil {
		return output.String(), fmt.Errorf("%s failed: %w: %s", name, err, output.String())
	}
	return output.String(), nil
}

func main() {
	child := flag.Bool("child", false, "run as the slow child process")
	flag.Parse()
	if *child {
		time.Sleep(2 * time.Second)
		fmt.Println("child finished")
		return
	}

	output, err := RunWithTimeout(context.Background(), 300*time.Millisecond, os.Args[0], "-child")
	fmt.Printf("output=%q err=%v\n", output, err)
}