Skip to content

Commit abfe562

Browse files
authored
[#91] Added Depenedncy Mapping of components to manager (#92)
1 parent 9d8c099 commit abfe562

File tree

4 files changed

+236
-80
lines changed

4 files changed

+236
-80
lines changed

lifecycle/component.go

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var ErrCompAlreadyStopped = errors.New("component already stopped")
2727

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

30+
var ErrCyclicDependency = errors.New("cyclic dependency")
31+
3032
// Component is the interface that wraps the basic Start and Stop methods.
3133
type Component interface {
3234
// Id is the unique identifier for the component.
@@ -44,6 +46,8 @@ type Component interface {
4446

4547
// ComponentManager is the interface that manages multiple components.
4648
type ComponentManager interface {
49+
// AddDependency will add a dependency between the two components.
50+
AddDependency(id, dependsOn string) error
4751
// GetState will return the current state of the LifeCycle for the component with the given id.
4852
GetState(id string) ComponentState
4953
//List will return a list of all the Components.

lifecycle/simple_component.go

+139-54
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sync"
77
"syscall"
88

9+
"oss.nandlabs.io/golly/collections"
910
"oss.nandlabs.io/golly/errutils"
1011
)
1112

@@ -117,6 +118,31 @@ type SimpleComponentManager struct {
117118
componentIds []string
118119
cMutex *sync.RWMutex
119120
waitChan chan struct{}
121+
dependencies map[string]collections.List[string]
122+
}
123+
124+
// AddDependency will add a dependency between the two components.
125+
func (scm *SimpleComponentManager) AddDependency(id, dependsOn string) (err error) {
126+
scm.cMutex.Lock()
127+
defer scm.cMutex.Unlock()
128+
if _, exists := scm.components[id]; !exists {
129+
return ErrCompNotFound
130+
}
131+
if _, exists := scm.components[dependsOn]; !exists {
132+
return ErrCompNotFound
133+
}
134+
135+
//detect cyclic dependencies
136+
if v, ok := scm.dependencies[dependsOn]; ok && v.Contains(id) {
137+
return ErrCyclicDependency
138+
}
139+
140+
if _, exists := scm.dependencies[id]; !exists {
141+
scm.dependencies[id] = collections.NewArrayList[string]()
142+
}
143+
scm.dependencies[id].Add(dependsOn)
144+
logger.InfoF("Added dependency %s depends on %s:", id, dependsOn)
145+
return
120146
}
121147

122148
// GetState will return the current state of the LifeCycle for the component with the given id.
@@ -171,21 +197,54 @@ func (scm *SimpleComponentManager) Start(id string) (err error) {
171197
scm.cMutex.Lock()
172198
defer scm.cMutex.Unlock()
173199
component, exists := scm.components[id]
174-
if exists {
175-
if component.State() != Running {
176-
var err error = nil
177-
go func(c Component, scm *SimpleComponentManager) {
178-
err = component.Start()
179-
if err != nil {
180-
logger.ErrorF("Error starting component: %v", err)
181-
}
182-
}(component, scm)
183-
return err
200+
if !exists {
201+
return ErrCompNotFound
202+
}
203+
if component.State() == Running {
204+
return
205+
}
206+
// Start the dependencies first
207+
if v, ok := scm.dependencies[id]; ok {
208+
logger.DebugF("Component %s has dependencies. Starting dependencies", id)
209+
dependecyWait := sync.WaitGroup{}
210+
var multiError *errutils.MultiError = errutils.NewMultiErr(nil)
211+
for ite := v.Iterator(); ite.HasNext(); {
212+
dependentComp := scm.components[ite.Next()]
213+
if dependentComp.State() != Running {
214+
dependecyWait.Add(1)
215+
go func(c Component, scm *SimpleComponentManager) {
216+
logger.DebugF("Starting dependent component %s", dependentComp.Id())
217+
err = dependentComp.Start()
218+
if err != nil {
219+
multiError.Add(err)
220+
logger.ErrorF("Error starting component: %v", err)
221+
} else {
222+
logger.DebugF("Started dependent component %s", dependentComp.Id())
223+
}
224+
dependecyWait.Done()
225+
}(dependentComp, scm)
226+
} else {
227+
logger.DebugF("Dependent component %s already running", dependentComp.Id())
228+
}
229+
}
230+
dependecyWait.Wait()
231+
232+
if multiError.HasErrors() {
233+
return multiError
184234
} else {
185-
return ErrCompAlreadyStarted
235+
logger.Info("All dependencies started")
186236
}
237+
238+
}
239+
logger.DebugF("Starting component %s", id)
240+
err = component.Start()
241+
if err != nil {
242+
logger.ErrorF("Error starting component: %v", err)
243+
} else {
244+
logger.DebugF("Started component %s", id)
187245
}
188-
return ErrCompNotFound
246+
247+
return
189248
}
190249

191250
// StartAll will start all the Components. Returns the number of components started
@@ -211,32 +270,79 @@ func (scm *SimpleComponentManager) StartAndWait() {
211270

212271
}
213272

273+
// Stop will stop the LifeCycle for the component with the given id. It returns if the component was stopped.
274+
func (scm *SimpleComponentManager) Stop(id string) (err error) {
275+
276+
component, exists := scm.components[id]
277+
if !exists {
278+
return ErrCompNotFound
279+
}
280+
if component.State() == Stopped {
281+
return
282+
}
283+
// check if the component has dependencies
284+
if v, ok := scm.dependencies[id]; ok {
285+
logger.DebugF("Component %s has dependencies", id)
286+
dependecyWait := sync.WaitGroup{}
287+
var multiError *errutils.MultiError = errutils.NewMultiErr(nil)
288+
for ite := v.Iterator(); ite.HasNext(); {
289+
dependentComp := scm.components[ite.Next()]
290+
logger.DebugF("Checking dependent component %s", dependentComp.Id())
291+
if dependentComp.State() != Stopped {
292+
dependecyWait.Add(1)
293+
go func(c Component, scm *SimpleComponentManager) {
294+
logger.InfoF("Stopping dependent component %s", c.Id())
295+
err = dependentComp.Stop()
296+
if err != nil {
297+
multiError.Add(err)
298+
logger.ErrorF("Error stopping component: %v", err)
299+
} else {
300+
logger.DebugF("Stopped dependent component %s", c.Id())
301+
}
302+
dependecyWait.Done()
303+
304+
}(dependentComp, scm)
305+
} else {
306+
logger.InfoF("Dependent component %s already stopped", dependentComp.Id())
307+
}
308+
}
309+
dependecyWait.Wait()
310+
if multiError.HasErrors() {
311+
return multiError
312+
} else {
313+
logger.DebugF("All dependencies stopped proceeding to stop component %s", id)
314+
}
315+
}
316+
scm.cMutex.Lock()
317+
defer scm.cMutex.Unlock()
318+
if component.State() == Running {
319+
logger.Debug("Stopping component ", id)
320+
err := component.Stop()
321+
322+
if err != nil {
323+
logger.ErrorF("Error stopping component: %v", err)
324+
} else {
325+
logger.InfoF("Stopped component %s", id)
326+
}
327+
328+
}
329+
return
330+
}
331+
214332
// StopAll will stop all the Components.
215333
func (scm *SimpleComponentManager) StopAll() error {
216334
logger.InfoF("Stopping all components")
217335
err := errutils.NewMultiErr(nil)
218-
scm.cMutex.Lock()
219-
defer scm.cMutex.Unlock()
220-
wg := &sync.WaitGroup{}
221336
for i := len(scm.componentIds) - 1; i >= 0; i-- {
222-
component := scm.components[scm.componentIds[i]]
223-
if component.State() == Running {
224-
wg.Add(1)
225-
go func(c Component, wg *sync.WaitGroup) {
226-
e := component.Stop()
227-
if e != nil {
228-
logger.ErrorF("Error stopping component: %v", err)
229-
err.Add(e)
230-
}
231-
232-
wg.Done()
233-
}(component, wg)
337+
e := scm.Stop(scm.componentIds[i])
338+
if e != nil {
339+
logger.ErrorF("Error stopping component: %v", err)
340+
err.Add(e)
234341
}
235342
}
236-
wg.Wait()
343+
logger.Info("All components stopped")
237344
select {
238345
case <-scm.waitChan:
239-
logger.Info("All components stopped")
240346
default:
241347
close(scm.waitChan)
242348
}
@@ -247,28 +353,6 @@ func (scm *SimpleComponentManager) StopAll() error {
247353
}
248354
}
249355

250-
// Stop will stop the LifeCycle for the component with the given id. It returns if the component was stopped.
251-
func (scm *SimpleComponentManager) Stop(id string) error {
252-
scm.cMutex.Lock()
253-
defer scm.cMutex.Unlock()
254-
component, exists := scm.components[id]
255-
if exists {
256-
if component.State() == Running {
257-
err := component.Stop()
258-
if err != nil {
259-
logger.ErrorF("Error stopping component: %v", err)
260-
}
261-
return err
262-
} else if component.State() == Stopped {
263-
return ErrCompAlreadyStopped
264-
} else {
265-
return ErrInvalidComponentState
266-
}
267-
268-
}
269-
return ErrCompNotFound
270-
}
271-
272356
// Unregister will unregister a Component.
273357
func (scm *SimpleComponentManager) Unregister(id string) {
274358
scm.cMutex.Lock()
@@ -304,9 +388,10 @@ func (scm *SimpleComponentManager) Wait() {
304388
// NewSimpleComponentManager will return a new SimpleComponentManager.
305389
func NewSimpleComponentManager() ComponentManager {
306390
manager := &SimpleComponentManager{
307-
components: make(map[string]Component),
308-
cMutex: &sync.RWMutex{},
309-
waitChan: make(chan struct{}),
391+
components: make(map[string]Component),
392+
cMutex: &sync.RWMutex{},
393+
waitChan: make(chan struct{}),
394+
dependencies: make(map[string]collections.List[string]),
310395
}
311396
sigs := make(chan os.Signal, 1)
312397

lifecycle/simple_component_test.go

+70-5
Original file line numberDiff line numberDiff line change
@@ -166,24 +166,89 @@ func TestSimpleComponentManager_StartAll(t *testing.T) {
166166
// It checks if the StartAndWait method waits for the components to finish.
167167
func TestSimpleComponentManager_StopAll(t *testing.T) {
168168
manager := NewSimpleComponentManager()
169-
component := &SimpleComponent{
170-
CompId: "test",
169+
componentA := &SimpleComponent{
170+
CompId: "comp-a",
171171
StopFunc: func() error {
172+
logger.InfoF("Stopping component %s", "comp-a")
172173
return nil
173174
},
174175
StartFunc: func() error {
176+
logger.InfoF("Starting component %s", "comp-a")
175177
return nil
176178
},
177179
}
178-
manager.Register(component)
180+
componentB := &SimpleComponent{
181+
CompId: "comp-b",
182+
StopFunc: func() error {
183+
logger.InfoF("Stopping component %s", "comp-b")
184+
return nil
185+
},
186+
StartFunc: func() error {
187+
logger.InfoF("Starting component %s", "comp-b")
188+
return nil
189+
},
190+
}
191+
manager.Register(componentA)
192+
manager.Register(componentB)
179193
manager.StartAll()
180194
time.Sleep(500 * time.Millisecond)
181195

182196
manager.StopAll()
183197
time.Sleep(500 * time.Millisecond)
184-
if component.State() != Stopped {
185-
t.Errorf("StopAll() state = %v, want %v", component.CompState, Stopped)
198+
if componentA.State() != Stopped {
199+
t.Errorf("StopAll() state = %v, want %v", componentA.CompState, Stopped)
200+
}
201+
202+
if componentB.State() != Stopped {
203+
t.Errorf("StopAll() state = %v, want %v", componentB.CompState, Stopped)
204+
}
205+
206+
}
207+
208+
// TestSimpleComponentManager_StartAndWait tests the StartAndWait method of the SimpleComponentManager struct.
209+
// It verifies the behavior of the StartAndWait method by checking if the components are started correctly.
210+
// The test case includes starting a component with a StartFunc that returns no error.
211+
// It checks if the StartAndWait method waits for the components to finish.
212+
func TestSimpleComponentManager_DependencyCheck(t *testing.T) {
213+
manager := NewSimpleComponentManager()
214+
componentA := &SimpleComponent{
215+
CompId: "comp-a",
216+
StopFunc: func() error {
217+
logger.InfoF("Stopping component %s", "comp-a")
218+
return nil
219+
},
220+
StartFunc: func() error {
221+
logger.InfoF("Starting component %s", "comp-a")
222+
return nil
223+
},
224+
}
225+
componentB := &SimpleComponent{
226+
CompId: "comp-b",
227+
StopFunc: func() error {
228+
logger.InfoF("Stopping component %s", "comp-b")
229+
return nil
230+
},
231+
StartFunc: func() error {
232+
logger.InfoF("Starting component %s", "comp-b")
233+
return nil
234+
},
186235
}
236+
manager.Register(componentA)
237+
manager.Register(componentB)
238+
manager.AddDependency("comp-a", "comp-b")
239+
manager.StartAll()
240+
time.Sleep(500 * time.Millisecond)
241+
242+
manager.StopAll()
243+
time.Sleep(500 * time.Millisecond)
244+
if componentA.State() != Stopped {
245+
t.Errorf("StopAll() state = %v, want %v", componentA.CompState, Stopped)
246+
}
247+
248+
if componentB.State() != Stopped {
249+
t.Errorf("StopAll() state = %v, want %v", componentB.CompState, Stopped)
250+
}
251+
187252
}
188253

189254
// TestSimpleComponentManager_Unregister tests the Unregister method of the SimpleComponentManager struct.

0 commit comments

Comments
 (0)