Skip to content

Commit

Permalink
lifecycle: Adds DefaultLifecycle constructor
Browse files Browse the repository at this point in the history
Adds a constructor for the DefaultLifecycle which allows to specify the current state of the appended hooks.

Includes a safe check if attempted to start the same lifecycle multiple times.

Signed-off-by: Ovidiu Tirla <[email protected]>
  • Loading branch information
ovidiutirla authored and joamaki committed Oct 7, 2024
1 parent aa37668 commit fe7b4c5
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 25 deletions.
22 changes: 21 additions & 1 deletion cell/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ type augmentedHook struct {
moduleID FullModuleID
}

func NewDefaultLifecycle(hooks []HookInterface, numStarted int, logThreshold time.Duration) *DefaultLifecycle {
h := make([]augmentedHook, 0, len(hooks))
for _, hook := range hooks {
h = append(h, augmentedHook{hook, nil})
}
return &DefaultLifecycle{
mu: sync.Mutex{},
hooks: h,
numStarted: numStarted,
LogThreshold: logThreshold,
}
}

func (lc *DefaultLifecycle) Append(hook HookInterface) {
lc.mu.Lock()
defer lc.mu.Unlock()
Expand All @@ -92,7 +105,7 @@ func (lc *DefaultLifecycle) Start(log *slog.Logger, ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

for _, hook := range lc.hooks {
for i, hook := range lc.hooks {
fnName, exists := getHookFuncName(hook, true)

if !exists {
Expand All @@ -102,6 +115,13 @@ func (lc *DefaultLifecycle) Start(log *slog.Logger, ctx context.Context) error {
}

l := log.With("function", fnName)

// Do not attempt to start already started hooks.
if i < lc.numStarted {
l.Error("Hook appears to be running. Skipping")
continue
}

l.Debug("Executing start hook")
t0 := time.Now()
if err := hook.Start(ctx); err != nil {
Expand Down
64 changes: 40 additions & 24 deletions cell/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,64 @@ import (
"errors"
"log/slog"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/cilium/hive/cell"
)

var (
started, stopped int

errLifecycle = errors.New("nope")

goodHook = cell.Hook{
OnStart: func(cell.HookContext) error {
started++
return nil
},
OnStop: func(cell.HookContext) error {
stopped++
return nil
},
}

badStartHook = cell.Hook{
OnStart: func(cell.HookContext) error {
return errLifecycle
},
}

badStopHook = cell.Hook{
OnStart: func(cell.HookContext) error {
started++
return nil
},
OnStop: func(cell.HookContext) error {
return errLifecycle
},
nilHook = cell.Hook{OnStart: nil, OnStop: nil}
)

func TestNewDefaultLifecycle(t *testing.T) {
var started, stopped int
goodHook := cell.Hook{
OnStart: func(cell.HookContext) error { started++; return nil },
OnStop: func(cell.HookContext) error { stopped++; return nil },
}

nilHook = cell.Hook{nil, nil}
)
log := slog.Default()
lc := cell.NewDefaultLifecycle([]cell.HookInterface{goodHook}, 0, time.Second)

err := lc.Start(log, context.TODO())
assert.NoError(t, err, "expected Start to succeed")
err = lc.Stop(log, context.TODO())
assert.NoError(t, err, "expected Stop to succeed")

assert.Equal(t, 1, started)
assert.Equal(t, 1, stopped)

// Construct already started hooks
started = 0
stopped = 0

lc = cell.NewDefaultLifecycle([]cell.HookInterface{goodHook}, 1, time.Second)
err = lc.Stop(log, context.TODO())
assert.NoError(t, err, "expected Stop to succeed")

assert.Equal(t, 0, started)
assert.Equal(t, 1, stopped)
}

func TestLifecycle(t *testing.T) {
var started, stopped int
goodHook := cell.Hook{
OnStart: func(cell.HookContext) error { started++; return nil },
OnStop: func(cell.HookContext) error { stopped++; return nil },
}
badStopHook := cell.Hook{
OnStart: func(cell.HookContext) error { started++; return nil },
OnStop: func(cell.HookContext) error { return errLifecycle },
}
log := slog.Default()
var lc cell.DefaultLifecycle

Expand Down

0 comments on commit fe7b4c5

Please sign in to comment.