From dcf20c78c8cb85bded669c43e7ca6d7a6eb2e030 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 05:49:58 +0000 Subject: [PATCH] feat(process): add cleanup tasks to core service --- actions.go | 17 +++++++++++++++++ service.go | 13 +++++++++++++ service_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/actions.go b/actions.go index c4d4856..2181c6c 100644 --- a/actions.go +++ b/actions.go @@ -142,6 +142,23 @@ type TaskProcessList struct { RunningOnly bool } +// TaskProcessRemove removes a completed managed process through Core.PERFORM. +// +// Example: +// +// c.PERFORM(process.TaskProcessRemove{ID: "proc-1"}) +type TaskProcessRemove struct { + // ID identifies a managed process started by this service. + ID string +} + +// TaskProcessClear removes all completed managed processes through Core.PERFORM. +// +// Example: +// +// c.PERFORM(process.TaskProcessClear{}) +type TaskProcessClear struct{} + // ActionProcessStarted is broadcast when a process begins execution. // // Example: diff --git a/service.go b/service.go index 739f964..9c4ca09 100644 --- a/service.go +++ b/service.go @@ -783,6 +783,19 @@ func (s *Service) handleTask(c *core.Core, task core.Task) core.Result { } return core.Result{Value: infos, OK: true} + case TaskProcessRemove: + if m.ID == "" { + return core.Result{Value: coreerr.E("Service.handleTask", "task process remove requires an id", nil), OK: false} + } + + if err := s.Remove(m.ID); err != nil { + return core.Result{Value: err, OK: false} + } + + return core.Result{OK: true} + case TaskProcessClear: + s.Clear() + return core.Result{OK: true} default: return core.Result{} } diff --git a/service_test.go b/service_test.go index f481352..d531687 100644 --- a/service_test.go +++ b/service_test.go @@ -957,6 +957,43 @@ func TestService_OnStartup(t *testing.T) { assert.Equal(t, proc.Info().PID, info.PID) }) + t.Run("registers process.remove task", func(t *testing.T) { + svc, c := newTestService(t) + + err := svc.OnStartup(context.Background()) + require.NoError(t, err) + + proc, err := svc.Start(context.Background(), "echo", "remove-through-core") + require.NoError(t, err) + <-proc.Done() + + result := c.PERFORM(TaskProcessRemove{ID: proc.ID}) + require.True(t, result.OK) + + _, err = svc.Get(proc.ID) + assert.ErrorIs(t, err, ErrProcessNotFound) + }) + + t.Run("registers process.clear task", func(t *testing.T) { + svc, c := newTestService(t) + + err := svc.OnStartup(context.Background()) + require.NoError(t, err) + + first, err := svc.Start(context.Background(), "echo", "clear-through-core-1") + require.NoError(t, err) + second, err := svc.Start(context.Background(), "echo", "clear-through-core-2") + require.NoError(t, err) + <-first.Done() + <-second.Done() + + require.Len(t, svc.List(), 2) + + result := c.PERFORM(TaskProcessClear{}) + require.True(t, result.OK) + assert.Len(t, svc.List(), 0) + }) + t.Run("registers process.output task", func(t *testing.T) { svc, c := newTestService(t)