feat(coredeno): sidecar Start/Stop/IsRunning lifecycle
Process launch with context cancellation, socket directory auto-creation, channel-based stop synchronization. Uses sleep as fake Deno in tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6e283c1284
commit
e341fbcdcf
3 changed files with 126 additions and 0 deletions
|
|
@ -58,6 +58,7 @@ type Sidecar struct {
|
|||
cmd *exec.Cmd
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewSidecar creates a Sidecar with the given options.
|
||||
|
|
|
|||
69
pkg/coredeno/lifecycle.go
Normal file
69
pkg/coredeno/lifecycle.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package coredeno
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Start launches the Deno sidecar process with the given entrypoint args.
|
||||
func (s *Sidecar) Start(ctx context.Context, args ...string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.cmd != nil {
|
||||
return fmt.Errorf("coredeno: already running")
|
||||
}
|
||||
|
||||
// Ensure socket directory exists
|
||||
sockDir := filepath.Dir(s.opts.SocketPath)
|
||||
if err := os.MkdirAll(sockDir, 0755); err != nil {
|
||||
return fmt.Errorf("coredeno: mkdir %s: %w", sockDir, err)
|
||||
}
|
||||
|
||||
// Remove stale socket
|
||||
os.Remove(s.opts.SocketPath)
|
||||
|
||||
s.ctx, s.cancel = context.WithCancel(ctx)
|
||||
s.cmd = exec.CommandContext(s.ctx, s.opts.DenoPath, args...)
|
||||
s.done = make(chan struct{})
|
||||
if err := s.cmd.Start(); err != nil {
|
||||
s.cmd = nil
|
||||
s.cancel()
|
||||
return fmt.Errorf("coredeno: start: %w", err)
|
||||
}
|
||||
|
||||
// Monitor in background — waits for exit, then signals done
|
||||
go func() {
|
||||
s.cmd.Wait()
|
||||
s.mu.Lock()
|
||||
s.cmd = nil
|
||||
s.mu.Unlock()
|
||||
close(s.done)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop cancels the context and waits for the process to exit.
|
||||
func (s *Sidecar) Stop() error {
|
||||
s.mu.RLock()
|
||||
if s.cmd == nil {
|
||||
s.mu.RUnlock()
|
||||
return nil
|
||||
}
|
||||
done := s.done
|
||||
s.mu.RUnlock()
|
||||
|
||||
s.cancel()
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning returns true if the sidecar process is alive.
|
||||
func (s *Sidecar) IsRunning() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.cmd != nil
|
||||
}
|
||||
56
pkg/coredeno/lifecycle_test.go
Normal file
56
pkg/coredeno/lifecycle_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package coredeno
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStart_Good(t *testing.T) {
|
||||
sockDir := t.TempDir()
|
||||
sc := NewSidecar(Options{
|
||||
DenoPath: "sleep",
|
||||
SocketPath: filepath.Join(sockDir, "test.sock"),
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err := sc.Start(ctx, "10") // sleep 10 — will be killed by Stop
|
||||
require.NoError(t, err)
|
||||
assert.True(t, sc.IsRunning())
|
||||
|
||||
err = sc.Stop()
|
||||
require.NoError(t, err)
|
||||
assert.False(t, sc.IsRunning())
|
||||
}
|
||||
|
||||
func TestStop_Good_NotStarted(t *testing.T) {
|
||||
sc := NewSidecar(Options{DenoPath: "sleep"})
|
||||
err := sc.Stop()
|
||||
assert.NoError(t, err, "stopping a not-started sidecar should be a no-op")
|
||||
}
|
||||
|
||||
func TestSocketDirCreated_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
sockPath := filepath.Join(dir, "sub", "deno.sock")
|
||||
sc := NewSidecar(Options{
|
||||
DenoPath: "sleep",
|
||||
SocketPath: sockPath,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err := sc.Start(ctx, "10")
|
||||
require.NoError(t, err)
|
||||
defer sc.Stop()
|
||||
|
||||
_, err = os.Stat(filepath.Join(dir, "sub"))
|
||||
assert.NoError(t, err, "socket directory should be created")
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue