Skip to content

Commit 8c04049

Browse files
authored
add OptOnStartCombined (#68)
1 parent ef89e36 commit 8c04049

File tree

4 files changed

+69
-7
lines changed

4 files changed

+69
-7
lines changed

actor/combine.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func (b *CombineBuilder) Build() Actor {
2626
a := &combinedActor{
2727
actors: b.actors,
2828
onStopFunc: b.options.Combined.OnStopFunc,
29+
onStartFunc: b.options.Combined.OnStartFunc,
2930
stopTogether: b.options.Combined.StopTogether,
3031
stopping: &atomic.Bool{},
3132
}
@@ -44,8 +45,10 @@ func (b *CombineBuilder) WithOptions(opt ...CombinedOption) *CombineBuilder {
4445
type combinedActor struct {
4546
actors []Actor
4647
onStopFunc func()
48+
onStartFunc func(Context)
4749
stopTogether bool
4850

51+
ctx *context
4952
runningCount atomic.Int32
5053
running bool
5154
runningLock sync.Mutex
@@ -70,6 +73,11 @@ func (a *combinedActor) onActorStopped() {
7073
if a.stopTogether && a.stopping.CompareAndSwap(false, true) {
7174
// Run stop in goroutine because wrapped actor
7275
// should not wait for other actors to stop.
76+
//
77+
// Also if a.Stop() is called from same gorutine it would
78+
// be recoursive call without exit condition. Therfore
79+
// it is need to call a.Stop() from other goroutine,
80+
// regardless of first invariant.
7381
go a.Stop()
7482
}
7583
}
@@ -82,6 +90,12 @@ func (a *combinedActor) Stop() {
8290
return
8391
}
8492

93+
if ctx := a.ctx; ctx != nil {
94+
ctx.end()
95+
96+
a.ctx = nil
97+
}
98+
8599
a.running = false
86100
a.runningLock.Unlock()
87101

@@ -98,11 +112,18 @@ func (a *combinedActor) Start() {
98112
return
99113
}
100114

115+
ctx := newContext()
116+
a.ctx = ctx
117+
101118
a.stopping.Store(false)
102119
a.running = true
103120

104121
a.runningLock.Unlock()
105122

123+
if fn := a.onStartFunc; fn != nil {
124+
fn(ctx)
125+
}
126+
106127
for _, actor := range a.actors {
107128
a.runningCount.Add(1)
108129
actor.Start()

actor/combine_test.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,17 @@ func Test_Combine_OptStopTogether(t *testing.T) {
7575
}
7676
}
7777

78-
func Test_Combine_OptOnStop(t *testing.T) {
78+
func Test_Combine_OptOnStopOptOnStart(t *testing.T) {
7979
t.Parallel()
8080

8181
const actorsCount = 5
8282

83+
onStatC, onStartOpt := createCombinedOnStartOption(t, 1)
8384
onStopC, onStopOpt := createCombinedOnStopOption(t, 1)
8485
actors := createActors(actorsCount)
8586

8687
a := Combine(actors...).
87-
WithOptions(onStopOpt).
88+
WithOptions(onStopOpt, onStartOpt).
8889
Build()
8990

9091
a.Start()
@@ -94,6 +95,7 @@ func Test_Combine_OptOnStop(t *testing.T) {
9495
a.Stop() // should have no effect
9596
a.Stop() // should have no effect
9697
assert.Equal(t, `🌚`, <-onStopC)
98+
assert.Equal(t, `🌞`, <-onStatC)
9799
}
98100

99101
func Test_Combine_OptOnStop_AfterActorStops(t *testing.T) {
@@ -143,14 +145,29 @@ func createActor(i int, opts ...Option) Actor {
143145
func createCombinedOnStopOption(t *testing.T, count int) (<-chan any, CombinedOption) {
144146
t.Helper()
145147

146-
onStopC := make(chan any, count)
147-
onStopFunc := func() {
148+
c := make(chan any, count)
149+
fn := func() {
148150
select {
149-
case onStopC <- `🌚`:
151+
case c <- `🌚`:
150152
default:
151153
t.Fatal("onStopFunc should be called only once")
152154
}
153155
}
154156

155-
return onStopC, OptOnStopCombined(onStopFunc)
157+
return c, OptOnStopCombined(fn)
158+
}
159+
160+
func createCombinedOnStartOption(t *testing.T, count int) (<-chan any, CombinedOption) {
161+
t.Helper()
162+
163+
c := make(chan any, count)
164+
fn := func(_ Context) {
165+
select {
166+
case c <- `🌞`:
167+
default:
168+
t.Fatal("onStart should be called only once")
169+
}
170+
}
171+
172+
return c, OptOnStartCombined(fn)
156173
}

actor/options.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,20 @@ func OptStopTogether() CombinedOption {
7070
}
7171
}
7272

73-
// OptOnStopCombined will is called after all combined actors are stopped.
73+
// OptOnStopCombined is called after all combined actors are stopped.
7474
func OptOnStopCombined(f func()) CombinedOption {
7575
return func(o *options) {
7676
o.Combined.OnStopFunc = f
7777
}
7878
}
7979

80+
// OptOnStartCombined is called before all.
81+
func OptOnStartCombined(f func(Context)) CombinedOption {
82+
return func(o *options) {
83+
o.Combined.OnStartFunc = f
84+
}
85+
}
86+
8087
type (
8188
option func(o *options)
8289

@@ -99,6 +106,7 @@ type optionsActor struct {
99106
type optionsCombined struct {
100107
StopTogether bool
101108
OnStopFunc func()
109+
OnStartFunc func(Context)
102110
}
103111

104112
type optionsMailbox struct {

actor/options_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,20 @@ func testCombinedOptions(t *testing.T) {
116116
assert.Empty(t, opts.Actor)
117117
assert.Empty(t, opts.Mailbox)
118118
}
119+
120+
{ // Assert that OnStartCombined will be set
121+
opts := NewOptions(OptOnStartCombined(func(Context) {}))
122+
assert.NotNil(t, opts.Combined.OnStartFunc)
123+
124+
assert.Empty(t, opts.Actor)
125+
assert.Empty(t, opts.Mailbox)
126+
}
127+
128+
{ // Assert that OnStopCombined will be set
129+
opts := NewOptions(OptOnStopCombined(func() {}))
130+
assert.NotNil(t, opts.Combined.OnStopFunc)
131+
132+
assert.Empty(t, opts.Actor)
133+
assert.Empty(t, opts.Mailbox)
134+
}
119135
}

0 commit comments

Comments
 (0)