carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// Command zddprobe is the probe app for the zero-downtime deploy roll notes
2// (docs/superpowers/plans/2026-10-07-zero-downtime-deploys-6-roll-notes.md).
3// It serves HTTP on a unix socket. Every request sleeps 50 ms, so requests are
4// in flight across a swap, then answers "ok <version>". SIGTERM or SIGINT
5// drains in-flight requests, waiting at most 15 s, and exits 0.
6//
7// The platform starts an exec-backed instance as
8// `<binary> --socket <sock> --db <db>`, and `carlos deploy`'s vet starts it as
9// `<binary> -socket <sock>`. The probe takes both. It keeps no data, so --db
10// is accepted and ignored.
11//
12// On a platform launch, the one that passes --db, the probe checks before
13// it listens what a real app may rely on (zero-downtime spec rev 19, rule
14// 5), and exits 1 with a line on stderr if either is wrong: CARLOS_HOST
15// must be set, and the file name of --socket must be the flag-off one,
16// app.sock (nested) or $CARLOS_HOST.sock (flat). A generation launch that
17// named its socket anything else crash-looped a tenant whose framework
18// reads its host from that name (amadan #272); the probe ignored the name
19// and passed. Vet's boot passes no --db, no CARLOS_HOST and a socket name
20// of its own choosing, so it skips the check and the probe serves. (The
21// flag package reads -db and --db alike, so --db being given is the test.)
22//
23// version is set at build time with -ldflags "-X main.version=...".
24//
25// With ZDDPROBE_TRACE=1 the probe writes one line, "serving", to stdout as a
26// request enters the handler, so a test can tell a request is in flight
27// before it signals. Without it the probe writes nothing to stdout.
28package main
29
30import (
31 "context"
32 "errors"
33 "flag"
34 "fmt"
35 "io"
36 "net"
37 "net/http"
38 "os"
39 "os/signal"
40 "path/filepath"
41 "syscall"
42 "time"
43)
44
45var version = "dev"
46
47func main() {
48 sigs := make(chan os.Signal, 1)
49 signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
50 if err := run(os.Args[1:], sigs); err != nil {
51 fmt.Fprintln(os.Stderr, err)
52 os.Exit(1)
53 }
54}
55
56// checkLaunch is rule 5's check of a platform launch: host (CARLOS_HOST)
57// is set, and sock's file name is app.sock or <host>.sock, the names a
58// flag-off launch gives.
59func checkLaunch(sock, host string) error {
60 if host == "" {
61 return errors.New("zddprobe: CARLOS_HOST is empty; the platform sets it on every instance launch")
62 }
63 if base := filepath.Base(sock); base != "app.sock" && base != host+".sock" {
64 return fmt.Errorf("zddprobe: --socket file name is %q; want app.sock or %s.sock, the flag-off names", base, host)
65 }
66 return nil
67}
68
69func run(args []string, stop <-chan os.Signal) error {
70 fs := flag.NewFlagSet("zddprobe", flag.ContinueOnError)
71 fs.SetOutput(os.Stderr)
72 sock := fs.String("socket", "", "unix socket to serve on")
73 fs.String("db", "", "accepted for the platform's argv and ignored")
74 if err := fs.Parse(args); err != nil {
75 return err
76 }
77 if *sock == "" {
78 return errors.New("zddprobe: --socket is required")
79 }
80 platform := false
81 fs.Visit(func(f *flag.Flag) { platform = platform || f.Name == "db" })
82 if platform {
83 if err := checkLaunch(*sock, os.Getenv("CARLOS_HOST")); err != nil {
84 return err
85 }
86 }
87 _ = os.Remove(*sock)
88 l, err := net.Listen("unix", *sock)
89 if err != nil {
90 return err
91 }
92
93 trace := os.Getenv("ZDDPROBE_TRACE") == "1"
94 mux := http.NewServeMux()
95 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
96 if trace {
97 fmt.Fprintln(os.Stdout, "serving")
98 }
99 time.Sleep(50 * time.Millisecond) // keep requests in flight across the switch
100 _, _ = io.WriteString(w, "ok "+version+"\n")
101 })
102 srv := &http.Server{Handler: mux}
103
104 drained := make(chan error, 1)
105 go func() {
106 <-stop
107 ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
108 defer cancel()
109 drained <- srv.Shutdown(ctx)
110 }()
111 if err := srv.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) {
112 return err
113 }
114 return <-drained
115}
116