-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
introduce runset to start/stop views and processors
- Loading branch information
Diogo Behrens
committed
May 3, 2018
1 parent
4037dcd
commit ecfb982
Showing
4 changed files
with
180 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package goka | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/lovoo/goka/multierr" | ||
) | ||
|
||
// Runnable represents a component that runs goroutines. | ||
type Runnable interface { | ||
// Run starts the runnable and canceling the context stops it. | ||
Run(context.Context) error | ||
} | ||
|
||
// Runset manages the lifecyle of a set of runnables (processors or | ||
// views). All runnables are started together and as soon as the first stops, | ||
// all other runnables are also stopped. | ||
type Runset struct { | ||
ctx context.Context | ||
cancel func() | ||
grp *multierr.ErrGroup | ||
} | ||
|
||
// Start one or more runnables and return a Runset object. | ||
func Start(runnables ...Runnable) *Runset { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
grp, ctx := multierr.NewErrGroup(ctx) | ||
|
||
s := Runset{ctx, cancel, grp} | ||
|
||
for _, r := range runnables { | ||
grp.Go(func() error { | ||
defer cancel() | ||
return r.Run(ctx) | ||
}) | ||
} | ||
|
||
return &s | ||
} | ||
|
||
// Stop all runnables in the runset. | ||
func (r *Runset) Stop() { | ||
r.cancel() | ||
} | ||
|
||
// Wait for all runnables to stop, returning the aggregated errors if any. | ||
func (r *Runset) Wait() error { | ||
return r.grp.Wait().NilOrError() | ||
} | ||
|
||
// Done returns a channel that is closed once the Runset is stopping. Runnables | ||
// may not yet have returned when the channel is closed. | ||
func (r *Runset) Done() <-chan struct{} { | ||
return r.ctx.Done() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package goka | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"log" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
"testing" | ||
|
||
"github.com/facebookgo/ensure" | ||
) | ||
|
||
type mockRunnable struct { | ||
ch chan error | ||
} | ||
|
||
func newMockRunnable() *mockRunnable { | ||
return &mockRunnable{ | ||
ch: make(chan error), | ||
} | ||
} | ||
|
||
func (r *mockRunnable) Run(ctx context.Context) error { | ||
select { | ||
case <-ctx.Done(): | ||
return nil | ||
case err := <-r.ch: | ||
return err | ||
} | ||
} | ||
|
||
func TestRunset_Start(t *testing.T) { | ||
p := newMockRunnable() | ||
rs := Start(p) | ||
rs.Stop() | ||
err := doTimed(t, func() { | ||
err := rs.Wait() | ||
ensure.Nil(t, err) | ||
}) | ||
ensure.Nil(t, err) | ||
|
||
// should not return | ||
p = newMockRunnable() | ||
rs = Start(p) | ||
err = doTimed(nil, func() { | ||
_ = rs.Wait() | ||
}) | ||
ensure.NotNil(t, err) | ||
rs.Stop() | ||
|
||
// should return if terminates without error | ||
p = newMockRunnable() | ||
rs = Start(p) | ||
close(p.ch) | ||
err = doTimed(nil, func() { | ||
err := rs.Wait() | ||
ensure.Nil(t, err) | ||
}) | ||
ensure.Nil(t, err) | ||
|
||
// should return if terminates with error | ||
p = newMockRunnable() | ||
rs = Start(p) | ||
p.ch <- errors.New("some error") | ||
err = doTimed(nil, func() { | ||
err := rs.Wait() | ||
ensure.NotNil(t, err) | ||
}) | ||
ensure.Nil(t, err) | ||
|
||
// should return | ||
p = newMockRunnable() | ||
rs = Start(p) | ||
p.ch <- errors.New("some error") | ||
err = doTimed(nil, func() { | ||
<-rs.Done() | ||
}) | ||
ensure.Nil(t, err) | ||
|
||
} | ||
|
||
// Example shows how to control the lifecycle of runnables (processors or | ||
// views) using Runsets. | ||
func ExampleRunset() { | ||
var ( | ||
brokers = []string{"127.0.0.1:9092"} | ||
group Group = "group" | ||
topic Stream = "topic" | ||
) | ||
|
||
f := func(ctx Context, m interface{}) { | ||
fmt.Printf("Hello world: %v", m) | ||
} | ||
|
||
p, err := NewProcessor(brokers, DefineGroup(group, Input(topic, rawCodec, f))) | ||
if err != nil { | ||
log.Fatalln(err) | ||
} | ||
|
||
// start processor creating a Runset. | ||
rs := Start(p) | ||
|
||
// wait for bad things to happen | ||
wait := make(chan os.Signal, 1) | ||
signal.Notify(wait, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) | ||
select { | ||
case <-rs.Done(): | ||
case <-wait: // wait for SIGINT/SIGTERM | ||
rs.Stop() // gracefully stop processor | ||
} | ||
if err := rs.Wait(); err != nil { | ||
log.Fatalln(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters