Skip to content

Commit

Permalink
[#lifecycle] The StartAll and StopAll functions are made async
Browse files Browse the repository at this point in the history
  • Loading branch information
nandagopalan committed Sep 21, 2024
1 parent 4834d23 commit e2ec605
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 47 deletions.
21 changes: 16 additions & 5 deletions lifecycle/component.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package lifecycle

import "errors"

type ComponentState int

const (
Expand All @@ -17,6 +19,14 @@ const (
Starting
)

var ErrCompNotFound = errors.New("component not found")

var ErrCompAlreadyStarted = errors.New("component already started")

var ErrCompAlreadyStopped = errors.New("component already stopped")

var ErrInvalidComponentState = errors.New("invalid component state")

// Component is the interface that wraps the basic Start and Stop methods.
type Component interface {
// Id is the unique identifier for the component.
Expand All @@ -40,15 +50,16 @@ type ComponentManager interface {
// Register will register a new Components.
Register(component Component) Component
// StartAll will start all the Components. Returns the number of components started
StartAll() int
StartAll()
//StartAndWait will start all the Components and wait for them to finish.
StartAndWait()
// Start will start the LifeCycle for the component with the given id. It returns if the component was started.
Start(id string) bool
// Start will start the LifeCycle for the component with the given id.
// It returns an error if the component was not found or if the component failed to start.
Start(id string) error
// StopAll will stop all the Components.
StopAll() int
StopAll()
// Stop will stop the LifeCycle for the component with the given id. It returns if the component was stopped.
Stop(id string) bool
Stop(id string) error
// Unregister will unregister a Component.
Unregister(id string)
// Wait will wait for all the Components to finish.
Expand Down
105 changes: 66 additions & 39 deletions lifecycle/simple_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,32 @@ import (
"sync"
)

// var cMutex = &sync.RWMutex{}
// var cwg = &sync.WaitGroup{}

// SimpleComponent is the struct that implements the Component interface.
type SimpleComponent struct {
CompId string
AfterStart func(err error)
BeforeStart func()
AfterStop func(err error)
BeforeStop func()
CompState ComponentState
CompId string
// AfterStart is the function that will be called after the component is started
// The function will be called with the error returned by the StartFunc.
AfterStart func(err error)
// BeforeStart is the function that will be called before the component is started
BeforeStart func()
// AfterStop is the function that will be called after the component is stopped
// The function will be called with the error returned by the StopFunc.
AfterStop func(err error)
// BeforeStop is the function that will be called before the component is stopped.
BeforeStop func()
// CompState is the current state of the component.
CompState ComponentState
// OnStateChange is the function that will be called when the component state changes.
OnStateChange func(prevState, newState ComponentState)
StartFunc func() error
StopFunc func() error
//StartFunc is the function that will be called when the component is started.
// It returns an error if the component failed to start.
// This can be a blocking call with respect to the component.
// In onrder to make it non-blocking, use a go routine inside the function.
StartFunc func() error
// StopFunc is the function that will be called when the component is stopped.
// It returns an error if the component failed to stop.
// This will always be a blocking call with respect to the component.
StopFunc func() error
}

// ComponentId is the unique identifier for the component.
Expand Down Expand Up @@ -124,75 +136,90 @@ func (scm *SimpleComponentManager) Register(component Component) Component {
}

// StartAll will start all the Components. Returns the number of components started
func (scm *SimpleComponentManager) StartAll() int {
func (scm *SimpleComponentManager) StartAll() {
scm.cMutex.Lock()
defer scm.cMutex.Unlock()
count := 0
for _, component := range scm.components {
component.Start()
scm.cwg.Add(1)
go func(c Component) {
err := c.Start()
if err != nil {
c.Stop()
scm.cwg.Done()
} else {
count++
}
}(component)
}
scm.status = Running
return len(scm.components)

}

// StartAndWait will start all the Components. And will wait for them to be stopped.
func (scm *SimpleComponentManager) StartAndWait() {
scm.cMutex.Lock()
for _, component := range scm.components {
component.Start()
scm.cwg.Add(1)
}
scm.status = Running
// unlock the mutex
scm.cMutex.Unlock()
scm.StartAll() // Start all the components
// Wait for all the components to finish. This will block until all components are stopped.
// Keeps checking the status of the components and waits until all components are stopped.
scm.cwg.Wait()
}

// Start will start the LifeCycle for the component with the given id. It returns if the component was started.
func (scm *SimpleComponentManager) Start(id string) bool {
func (scm *SimpleComponentManager) Start(id string) (err error) {
scm.cMutex.Lock()
defer scm.cMutex.Unlock()
component, exists := scm.components[id]
if exists && component.State() != Running {
component.Start()
scm.cwg.Add(1)
return true
if exists {
if component.State() != Running {
scm.cwg.Add(1)
var err error = nil
go func(c Component) {
err = component.Start()
if err != nil {
component.Stop()
scm.cwg.Done()
}
}(component)
return err
} else {
return ErrCompAlreadyStarted
}
}
return false
return ErrCompNotFound
}

// StopAll will stop all the Components.
func (scm *SimpleComponentManager) StopAll() int {
func (scm *SimpleComponentManager) StopAll() {
scm.cMutex.Lock()
defer scm.cMutex.Unlock()
count := 0
for _, component := range scm.components {
count++
if component.State() == Running {
component.Stop()
scm.cwg.Done()
err := component.Stop()
if err == nil {
scm.cwg.Done()
}
}
}
scm.status = Stopped
return count
}

// Stop will stop the LifeCycle for the component with the given id. It returns if the component was stopped.
func (scm *SimpleComponentManager) Stop(id string) bool {
func (scm *SimpleComponentManager) Stop(id string) error {
scm.cMutex.Lock()
defer scm.cMutex.Unlock()
component, exists := scm.components[id]
if exists {
if component.State() == Running {
component.Stop()
err := component.Stop()
scm.cwg.Done()
return err
} else if component.State() == Stopped {
return ErrCompAlreadyStopped
} else {
return ErrInvalidComponentState
}

return true
}
return false
return ErrCompNotFound
}

// Unregister will unregister a Component.
Expand Down
9 changes: 6 additions & 3 deletions lifecycle/simple_component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,10 @@ func TestSimpleComponentManager_StartAll(t *testing.T) {
}
manager.Register(component)

started := manager.StartAll()
if started != 1 {
t.Errorf("StartAll() started = %v, want %v", started, 1)
manager.StartAll()
time.Sleep(1 * time.Second)
if component.State() != Running {
t.Errorf("Component State = %v, want %v", component.State(), Running)
}
}

Expand All @@ -173,8 +174,10 @@ func TestSimpleComponentManager_StopAll(t *testing.T) {
}
manager.Register(component)
manager.StartAll()
time.Sleep(500 * time.Millisecond)

manager.StopAll()
time.Sleep(500 * time.Millisecond)
if component.State() != Stopped {
t.Errorf("StopAll() state = %v, want %v", component.CompState, Stopped)
}
Expand Down

0 comments on commit e2ec605

Please sign in to comment.