carlos / platform Public

activator, schedule: strict spawn and timer-control adapters (claim Plan 6 Task 5)

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codex agent, operated by paul@keymail.dev pushed by claude agent, operated by paul@keymail.dev 2e6536f5d54087ef8ba79abbe4b9c932ac1d977e parent 9046d84
5 files changed, +248 −0
  • internal/activator/claimspawn.go +31 −0
  • internal/activator/claimspawn_kernel_linux_test.go +84 −0
  • internal/activator/claimspawn_test.go +40 −0
  • internal/schedule/claimtimers.go +63 −0
  • internal/schedule/claimtimers_test.go +30 −0

CI passed — run details

diff --git a/internal/activator/claimspawn.go b/internal/activator/claimspawn.go
new file mode 100644
index 000000000..75a5eb498
--- /dev/null
+++ b/internal/activator/claimspawn.go
@@ -0,0 +1,31 @@
+package activator
+
+import (
+ "context"
+ "os/exec"
+
+ "github.com/carlosframework/platform/internal/cgroup"
+ "github.com/carlosframework/platform/internal/hostwrite"
+)
+
+func spawnClaimed(ctx context.Context, enabled bool, manager *cgroup.Manager, host string, limit cgroup.Limit, selected hostwrite.Work, identity hostwrite.Identity, build func() *exec.Cmd) (*exec.Cmd, *cgroup.Prepared, error) {
+ if !enabled {
+ cmd, err := manager.Start(host, limit, build)
+ return cmd, nil, err
+ }
+ if selected == nil || build == nil {
+ return nil, nil, hostwrite.ErrUnavailable
+ }
+ var cmd *exec.Cmd
+ var prepared *cgroup.Prepared
+ err := selected.With(ctx, identity, func() error {
+ var err error
+ prepared, err = manager.PrepareStrict(host, limit)
+ if err != nil {
+ return err
+ }
+ cmd = build()
+ return prepared.Start(cmd)
+ })
+ return cmd, prepared, err
+}
diff --git a/internal/activator/claimspawn_kernel_linux_test.go b/internal/activator/claimspawn_kernel_linux_test.go
new file mode 100644
index 000000000..5cb96322d
--- /dev/null
+++ b/internal/activator/claimspawn_kernel_linux_test.go
@@ -0,0 +1,84 @@
+//go:build linux
+
+package activator
+
+import (
+ "context"
+ "errors"
+ "os"
+ "os/exec"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/cgroup"
+ "github.com/carlosframework/platform/internal/hostwrite"
+)
+
+func TestClaimSpawnKernelChild(t *testing.T) {
+ if os.Getenv("CARLOS_CLAIM_SPAWN_CHILD") != "1" {
+ t.Skip("synthetic contained child only")
+ }
+ time.Sleep(time.Minute)
+}
+
+func TestClaimSpawnCleanupRespawn(t *testing.T) {
+ if os.Getenv("CARLOS_CLAIM_SPAWN_KERNEL") != "1" {
+ t.Skip("requires a disposable delegated user service")
+ }
+ manager := cgroup.Setup(t.Logf)
+ if manager == nil {
+ t.Fatal("missing delegated cgroup")
+ }
+ const host = "claimspawn.example"
+ calls := 0
+ dir := t.TempDir()
+ build := func() *exec.Cmd {
+ cmd := exec.Command(os.Args[0], "-test.run=^TestClaimSpawnKernelChild$", "-test.count=1")
+ cmd.Env = []string{"CARLOS_CLAIM_SPAWN_CHILD=1"}
+ cmd.Dir = dir
+ return cmd
+ }
+ for attempt := range 2 {
+ cmd, prepared, err := spawnClaimed(t.Context(), true, manager, host, cgroup.TierDefault, claimSpawnWork{false, &calls}, hostwrite.Identity{}, build)
+ if err != nil || cmd == nil || prepared == nil {
+ t.Fatalf("spawn %d: cmd=%v prepared=%v err=%v", attempt, cmd, prepared, err)
+ }
+ // The unit's KillMode covers fatal test exits. Normal cleanup below
+ // must release the original reservation without removing the group.
+ if err := prepared.Close(); !errors.Is(err, cgroup.ErrStrictRefused) {
+ t.Fatalf("populated close: %v", err)
+ }
+ cleanup, err := manager.OpenCleanup(host)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second)
+ err = cleanup.KillAndEmpty(ctx)
+ cancel()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var exitErr *exec.ExitError
+ if err := cmd.Wait(); err != nil && !errors.As(err, &exitErr) {
+ t.Fatal(err)
+ }
+ if cmd.ProcessState == nil {
+ t.Fatal("child was not reaped")
+ }
+ if err := cleanup.Close(); err != nil {
+ t.Fatal(err)
+ }
+ if other, err := manager.PrepareStrict(host, cgroup.TierDefault); other != nil || !errors.Is(err, cgroup.ErrStrictState) {
+ t.Fatalf("cleanup close released original reservation: %v", err)
+ }
+ if populated, err := prepared.Populated(); err != nil || populated {
+ t.Fatalf("pinned group not proven empty: %v %v", populated, err)
+ }
+ if err := prepared.Close(); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if calls != 2 {
+ t.Fatal(calls)
+ }
+}
diff --git a/internal/activator/claimspawn_test.go b/internal/activator/claimspawn_test.go
new file mode 100644
index 000000000..427255522
--- /dev/null
+++ b/internal/activator/claimspawn_test.go
@@ -0,0 +1,40 @@
+package activator
+
+import (
+ "context"
+ "errors"
+ "os/exec"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/cgroup"
+ "github.com/carlosframework/platform/internal/hostwrite"
+)
+
+type claimSpawnWork struct {
+ deny bool
+ calls *int
+}
+
+func (w claimSpawnWork) With(_ context.Context, _ hostwrite.Identity, fn func() error) error {
+ *w.calls++
+ if w.deny {
+ return hostwrite.ErrUnavailable
+ }
+ return fn()
+}
+func TestClaimSpawnNeverFallsBack(t *testing.T) {
+ calls, builds := 0, 0
+ build := func() *exec.Cmd { builds++; return exec.Command("must-not-execute") }
+ for _, deny := range []bool{true, false} {
+ cmd, prepared, err := spawnClaimed(context.Background(), true, nil, "shop.example", cgroup.TierDefault, claimSpawnWork{deny, &calls}, hostwrite.Identity{}, build)
+ if err == nil || cmd != nil || prepared != nil {
+ t.Fatal(cmd, prepared, err)
+ }
+ if deny && !errors.Is(err, hostwrite.ErrUnavailable) {
+ t.Fatal(err)
+ }
+ }
+ if builds != 0 || calls != 2 {
+ t.Fatal(builds, calls)
+ }
+}
diff --git a/internal/schedule/claimtimers.go b/internal/schedule/claimtimers.go
new file mode 100644
index 000000000..ff2b208d2
--- /dev/null
+++ b/internal/schedule/claimtimers.go
@@ -0,0 +1,63 @@
+package schedule
+
+import (
+ "context"
+ "errors"
+
+ "github.com/carlosframework/platform/internal/hostwrite"
+ "github.com/carlosframework/platform/internal/registry"
+)
+
+var errTimerHost = errors.New("schedule: selected timer host differs")
+
+type SelectedTimers struct {
+ Store TimerStore
+ Work hostwrite.Work
+ Context context.Context
+ Host string
+}
+
+func (s SelectedTimers) write(host string, fn func() error) error {
+ if s.Store == nil || s.Work == nil || s.Context == nil {
+ return hostwrite.ErrUnavailable
+ }
+ if host != s.Host {
+ return errTimerHost
+ }
+ return s.Work.With(s.Context, hostwrite.Identity{}, fn)
+}
+func (s SelectedTimers) UpsertTimer(t registry.Timer) error {
+ return s.write(t.Host, func() error { return s.Store.UpsertTimer(t) })
+}
+func (s SelectedTimers) DeleteTimer(host, name string) error {
+ return s.write(host, func() error { return s.Store.DeleteTimer(host, name) })
+}
+func (s SelectedTimers) GetTimer(host, name string) (*registry.Timer, error) {
+ if s.Store == nil {
+ return nil, hostwrite.ErrUnavailable
+ }
+ if host != s.Host {
+ return nil, errTimerHost
+ }
+ return s.Store.GetTimer(host, name)
+}
+func (s SelectedTimers) TimersFor(host string) ([]registry.Timer, error) {
+ if s.Store == nil {
+ return nil, hostwrite.ErrUnavailable
+ }
+ if host != s.Host {
+ return nil, errTimerHost
+ }
+ return s.Store.TimersFor(host)
+}
+func (s SelectedTimers) CountTimers(host, kind string) (int, error) {
+ if s.Store == nil {
+ return 0, hostwrite.ErrUnavailable
+ }
+ if host != s.Host {
+ return 0, errTimerHost
+ }
+ return s.Store.CountTimers(host, kind)
+}
+
+var _ TimerStore = SelectedTimers{}
diff --git a/internal/schedule/claimtimers_test.go b/internal/schedule/claimtimers_test.go
new file mode 100644
index 000000000..2fcd705ef
--- /dev/null
+++ b/internal/schedule/claimtimers_test.go
@@ -0,0 +1,30 @@
+package schedule
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/hostwrite"
+ "github.com/carlosframework/platform/internal/registry"
+)
+
+type timerDeny struct{}
+
+func (timerDeny) With(context.Context, hostwrite.Identity, func() error) error {
+ return hostwrite.ErrUnavailable
+}
+func TestSelectedTimerRejectsRetiredProducer(t *testing.T) {
+ // A nil underlying pointer would panic if a denied write reached the registry.
+ var reg *registry.Registry
+ store := SelectedTimers{Store: reg, Work: timerDeny{}, Context: context.Background(), Host: "shop.example"}
+ if err := store.UpsertTimer(registry.Timer{Host: "shop.example"}); !errors.Is(err, hostwrite.ErrUnavailable) {
+ t.Fatal(err)
+ }
+ if err := store.DeleteTimer("shop.example", "one"); !errors.Is(err, hostwrite.ErrUnavailable) {
+ t.Fatal(err)
+ }
+ if err := store.DeleteTimer("other.example", "one"); !errors.Is(err, errTimerHost) {
+ t.Fatal(err)
+ }
+}