mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-03 22:51:37 +00:00
Solidify API:
+ use `trySend` the replicate peer sending + expose `next()` as a chan of events as output + expose `final()` as a chan of error, for the final error + add `ready()` as chan struct when routine is ready
This commit is contained in:
@ -9,7 +9,7 @@ type demuxer struct {
|
|||||||
input chan Event
|
input chan Event
|
||||||
scheduler *Routine
|
scheduler *Routine
|
||||||
processor *Routine
|
processor *Routine
|
||||||
finished chan error
|
fin chan error
|
||||||
stopped chan struct{}
|
stopped chan struct{}
|
||||||
running *uint32
|
running *uint32
|
||||||
}
|
}
|
||||||
@ -26,12 +26,12 @@ func newDemuxer(scheduler *Routine, processor *Routine) *demuxer {
|
|||||||
scheduler: scheduler,
|
scheduler: scheduler,
|
||||||
processor: processor,
|
processor: processor,
|
||||||
stopped: make(chan struct{}, 1),
|
stopped: make(chan struct{}, 1),
|
||||||
finished: make(chan error, 1),
|
fin: make(chan error, 1),
|
||||||
running: new(uint32),
|
running: new(uint32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dm *demuxer) run() {
|
func (dm *demuxer) start() {
|
||||||
starting := atomic.CompareAndSwapUint32(dm.running, uint32(0), uint32(1))
|
starting := atomic.CompareAndSwapUint32(dm.running, uint32(0), uint32(1))
|
||||||
if !starting {
|
if !starting {
|
||||||
panic("Routine has already started")
|
panic("Routine has already started")
|
||||||
@ -57,7 +57,7 @@ func (dm *demuxer) run() {
|
|||||||
for _, event := range oEvents {
|
for _, event := range oEvents {
|
||||||
dm.input <- event
|
dm.input <- event
|
||||||
}
|
}
|
||||||
case event, ok := <-dm.scheduler.output():
|
case event, ok := <-dm.scheduler.next():
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Printf("demuxer: scheduler output closed\n")
|
fmt.Printf("demuxer: scheduler output closed\n")
|
||||||
continue
|
continue
|
||||||
@ -70,7 +70,7 @@ func (dm *demuxer) run() {
|
|||||||
for _, event := range oEvents {
|
for _, event := range oEvents {
|
||||||
dm.input <- event
|
dm.input <- event
|
||||||
}
|
}
|
||||||
case event, ok := <-dm.processor.output():
|
case event, ok := <-dm.processor.next():
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Printf("demuxer: processor output closed\n")
|
fmt.Printf("demuxer: processor output closed\n")
|
||||||
continue
|
continue
|
||||||
@ -88,12 +88,12 @@ func (dm *demuxer) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dm *demuxer) handle(event Event) (Events, error) {
|
func (dm *demuxer) handle(event Event) (Events, error) {
|
||||||
received := dm.scheduler.send(event)
|
received := dm.scheduler.trySend(event)
|
||||||
if !received {
|
if !received {
|
||||||
return Events{scFull{}}, nil // backpressure
|
return Events{scFull{}}, nil // backpressure
|
||||||
}
|
}
|
||||||
|
|
||||||
received = dm.processor.send(event)
|
received = dm.processor.trySend(event)
|
||||||
if !received {
|
if !received {
|
||||||
return Events{pcFull{}}, nil // backpressure
|
return Events{pcFull{}}, nil // backpressure
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ func (dm *demuxer) handle(event Event) (Events, error) {
|
|||||||
return Events{}, nil
|
return Events{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dm *demuxer) send(event Event) bool {
|
func (dm *demuxer) trySend(event Event) bool {
|
||||||
if !dm.isRunning() {
|
if !dm.isRunning() {
|
||||||
fmt.Println("dummuxer isn't running")
|
fmt.Println("dummuxer isn't running")
|
||||||
return false
|
return false
|
||||||
@ -133,9 +133,9 @@ func (dm *demuxer) terminate(reason error) {
|
|||||||
if !stopped {
|
if !stopped {
|
||||||
panic("called terminate but already terminated")
|
panic("called terminate but already terminated")
|
||||||
}
|
}
|
||||||
dm.finished <- reason
|
dm.fin <- reason
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dm *demuxer) wait() error {
|
func (dm *demuxer) final() chan error {
|
||||||
return <-dm.finished
|
return dm.fin
|
||||||
}
|
}
|
||||||
|
@ -47,9 +47,9 @@ func (r *Reactor) Start() {
|
|||||||
r.demuxer = newDemuxer(r.scheduler, r.processor)
|
r.demuxer = newDemuxer(r.scheduler, r.processor)
|
||||||
r.tickerStopped = make(chan struct{})
|
r.tickerStopped = make(chan struct{})
|
||||||
|
|
||||||
go r.scheduler.run()
|
go r.scheduler.start()
|
||||||
go r.processor.run()
|
go r.processor.start()
|
||||||
go r.demuxer.run()
|
go r.demuxer.start()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if r.scheduler.isRunning() && r.processor.isRunning() && r.demuxer.isRunning() {
|
if r.scheduler.isRunning() && r.processor.isRunning() && r.demuxer.isRunning() {
|
||||||
@ -57,7 +57,7 @@ func (r *Reactor) Start() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
fmt.Println("waiting")
|
fmt.Println("waiting")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -65,7 +65,7 @@ func (r *Reactor) Start() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
r.demuxer.send(timeCheck{})
|
r.demuxer.trySend(timeCheck{})
|
||||||
case <-r.tickerStopped:
|
case <-r.tickerStopped:
|
||||||
fmt.Println("ticker stopped")
|
fmt.Println("ticker stopped")
|
||||||
return
|
return
|
||||||
@ -94,7 +94,7 @@ func (r *Reactor) Stop() {
|
|||||||
|
|
||||||
func (r *Reactor) Receive(event Event) {
|
func (r *Reactor) Receive(event Event) {
|
||||||
fmt.Println("receive event")
|
fmt.Println("receive event")
|
||||||
sent := r.demuxer.send(event)
|
sent := r.demuxer.trySend(event)
|
||||||
if !sent {
|
if !sent {
|
||||||
fmt.Println("demuxer is full")
|
fmt.Println("demuxer is full")
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
// * revisit panic conditions
|
// * revisit panic conditions
|
||||||
// * audit log levels
|
// * audit log levels
|
||||||
// * Convert routine to an interface with concrete implmentation
|
// * Convert routine to an interface with concrete implmentation
|
||||||
// * determine the public interface
|
|
||||||
|
|
||||||
type handleFunc = func(event Event) (Events, error)
|
type handleFunc = func(event Event) (Events, error)
|
||||||
|
|
||||||
@ -21,7 +20,8 @@ type Routine struct {
|
|||||||
errors chan error
|
errors chan error
|
||||||
out chan Event
|
out chan Event
|
||||||
stopped chan struct{}
|
stopped chan struct{}
|
||||||
finished chan error
|
rdy chan struct{}
|
||||||
|
fin chan error
|
||||||
running *uint32
|
running *uint32
|
||||||
handle handleFunc
|
handle handleFunc
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
@ -37,7 +37,8 @@ func newRoutine(name string, handleFunc handleFunc) *Routine {
|
|||||||
errors: make(chan error, 1),
|
errors: make(chan error, 1),
|
||||||
out: make(chan Event, 1),
|
out: make(chan Event, 1),
|
||||||
stopped: make(chan struct{}, 1),
|
stopped: make(chan struct{}, 1),
|
||||||
finished: make(chan error, 1),
|
rdy: make(chan struct{}, 1),
|
||||||
|
fin: make(chan error, 1),
|
||||||
running: new(uint32),
|
running: new(uint32),
|
||||||
stopping: new(uint32),
|
stopping: new(uint32),
|
||||||
logger: log.NewNopLogger(),
|
logger: log.NewNopLogger(),
|
||||||
@ -53,12 +54,13 @@ func (rt *Routine) setMetrics(metrics *Metrics) {
|
|||||||
rt.metrics = metrics
|
rt.metrics = metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *Routine) run() {
|
func (rt *Routine) start() {
|
||||||
rt.logger.Info(fmt.Sprintf("%s: run\n", rt.name))
|
rt.logger.Info(fmt.Sprintf("%s: run\n", rt.name))
|
||||||
starting := atomic.CompareAndSwapUint32(rt.running, uint32(0), uint32(1))
|
starting := atomic.CompareAndSwapUint32(rt.running, uint32(0), uint32(1))
|
||||||
if !starting {
|
if !starting {
|
||||||
panic("Routine has already started")
|
panic("Routine has already started")
|
||||||
}
|
}
|
||||||
|
rt.rdy <- struct{}{}
|
||||||
errorsDrained := false
|
errorsDrained := false
|
||||||
for {
|
for {
|
||||||
if !rt.isRunning() {
|
if !rt.isRunning() {
|
||||||
@ -113,11 +115,11 @@ func (rt *Routine) run() {
|
|||||||
}
|
}
|
||||||
func (rt *Routine) feedback() {
|
func (rt *Routine) feedback() {
|
||||||
for event := range rt.out {
|
for event := range rt.out {
|
||||||
rt.send(event)
|
rt.trySend(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *Routine) send(event Event) bool {
|
func (rt *Routine) trySend(event Event) bool {
|
||||||
if !rt.isRunning() || rt.isStopping() {
|
if !rt.isRunning() || rt.isStopping() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -154,7 +156,11 @@ func (rt *Routine) isStopping() bool {
|
|||||||
return atomic.LoadUint32(rt.stopping) == 1
|
return atomic.LoadUint32(rt.stopping) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *Routine) output() chan Event {
|
func (rt *Routine) ready() chan struct{} {
|
||||||
|
return rt.rdy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *Routine) next() chan Event {
|
||||||
return rt.out
|
return rt.out
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,21 +180,14 @@ func (rt *Routine) stop() {
|
|||||||
<-rt.stopped
|
<-rt.stopped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rt *Routine) final() chan error {
|
||||||
|
return rt.fin
|
||||||
|
}
|
||||||
|
|
||||||
func (rt *Routine) terminate(reason error) {
|
func (rt *Routine) terminate(reason error) {
|
||||||
stopped := atomic.CompareAndSwapUint32(rt.running, uint32(1), uint32(0))
|
stopped := atomic.CompareAndSwapUint32(rt.running, uint32(1), uint32(0))
|
||||||
if !stopped {
|
if !stopped {
|
||||||
panic("called stop but already stopped")
|
panic("called stop but already stopped")
|
||||||
}
|
}
|
||||||
rt.finished <- reason
|
rt.fin <- reason
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: this should probably produced the finished
|
|
||||||
// channel and let the caller deicde how long to wait
|
|
||||||
func (rt *Routine) wait() error {
|
|
||||||
return <-rt.finished
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Problem:
|
|
||||||
We can't write to channels from one thread and close channels from another thread
|
|
||||||
*/
|
|
||||||
|
@ -19,7 +19,7 @@ func simpleHandler(event Event) (Events, error) {
|
|||||||
case eventA:
|
case eventA:
|
||||||
return Events{eventB{}}, nil
|
return Events{eventB{}}, nil
|
||||||
case eventB:
|
case eventB:
|
||||||
return Events{routineFinished{}}, done
|
return Events{}, done
|
||||||
}
|
}
|
||||||
return Events{}, nil
|
return Events{}, nil
|
||||||
}
|
}
|
||||||
@ -29,53 +29,54 @@ func TestRoutine(t *testing.T) {
|
|||||||
|
|
||||||
assert.False(t, routine.isRunning(),
|
assert.False(t, routine.isRunning(),
|
||||||
"expected an initialized routine to not be running")
|
"expected an initialized routine to not be running")
|
||||||
go routine.run()
|
go routine.start()
|
||||||
go routine.feedback()
|
go routine.feedback()
|
||||||
for {
|
<-routine.ready()
|
||||||
if routine.isRunning() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
routine.send(eventA{})
|
assert.True(t, routine.trySend(eventA{}),
|
||||||
|
"expected sending to a ready routine to succeed")
|
||||||
|
|
||||||
routine.stop()
|
assert.Equal(t, done, <-routine.final(),
|
||||||
|
"expected the final event to be done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TesRoutineSend(t *testing.T) {
|
func TesRoutineSend(t *testing.T) {
|
||||||
routine := newRoutine("simpleRoutine", simpleHandler)
|
routine := newRoutine("simpleRoutine", simpleHandler)
|
||||||
|
|
||||||
assert.False(t, routine.send(eventA{}),
|
assert.False(t, routine.trySend(eventA{}),
|
||||||
"expected sending to an unstarted routine to fail")
|
"expected sending to an unstarted routine to fail")
|
||||||
|
|
||||||
go routine.run()
|
go routine.start()
|
||||||
|
|
||||||
go routine.feedback()
|
go routine.feedback()
|
||||||
for {
|
<-routine.ready()
|
||||||
if routine.isRunning() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, routine.send(eventA{}),
|
assert.True(t, routine.trySend(eventA{}),
|
||||||
"expected sending to a running routine to succeed")
|
"expected sending to a running routine to succeed")
|
||||||
|
|
||||||
routine.stop()
|
routine.stop()
|
||||||
|
|
||||||
assert.False(t, routine.send(eventA{}),
|
assert.False(t, routine.trySend(eventA{}),
|
||||||
"expected sending to a stopped routine to fail")
|
"expected sending to a stopped routine to fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type finalCount struct {
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f finalCount) Error() string {
|
||||||
|
return "end"
|
||||||
|
}
|
||||||
|
|
||||||
func genStatefulHandler(maxCount int) handleFunc {
|
func genStatefulHandler(maxCount int) handleFunc {
|
||||||
counter := 0
|
counter := 0
|
||||||
return func(event Event) (Events, error) {
|
return func(event Event) (Events, error) {
|
||||||
|
// golint fixme
|
||||||
switch event.(type) {
|
switch event.(type) {
|
||||||
case eventA:
|
case eventA:
|
||||||
counter += 1
|
counter += 1
|
||||||
if counter >= maxCount {
|
if counter >= maxCount {
|
||||||
return Events{}, done
|
return Events{}, finalCount{counter}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Events{eventA{}}, nil
|
return Events{eventA{}}, nil
|
||||||
@ -85,23 +86,27 @@ func genStatefulHandler(maxCount int) handleFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStatefulRoutine(t *testing.T) {
|
func TestStatefulRoutine(t *testing.T) {
|
||||||
handler := genStatefulHandler(10)
|
count := 10
|
||||||
|
handler := genStatefulHandler(count)
|
||||||
routine := newRoutine("statefulRoutine", handler)
|
routine := newRoutine("statefulRoutine", handler)
|
||||||
routine.setLogger(log.TestingLogger())
|
routine.setLogger(log.TestingLogger())
|
||||||
|
|
||||||
go routine.run()
|
go routine.start()
|
||||||
go routine.feedback()
|
go routine.feedback()
|
||||||
|
|
||||||
for {
|
<-routine.ready()
|
||||||
if routine.isRunning() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
routine.send(eventA{})
|
assert.True(t, routine.trySend(eventA{}),
|
||||||
|
"expected sending to a started routine to succeed")
|
||||||
|
|
||||||
routine.stop()
|
final := <-routine.final()
|
||||||
|
fnl, ok := final.(finalCount)
|
||||||
|
if ok {
|
||||||
|
assert.Equal(t, count, fnl.count,
|
||||||
|
"expected the routine to count to 10")
|
||||||
|
} else {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWithErrors(event Event) (Events, error) {
|
func handleWithErrors(event Event) (Events, error) {
|
||||||
@ -116,22 +121,17 @@ func handleWithErrors(event Event) (Events, error) {
|
|||||||
|
|
||||||
func TestErrorSaturation(t *testing.T) {
|
func TestErrorSaturation(t *testing.T) {
|
||||||
routine := newRoutine("errorRoutine", handleWithErrors)
|
routine := newRoutine("errorRoutine", handleWithErrors)
|
||||||
go routine.run()
|
go routine.start()
|
||||||
|
<-routine.ready()
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
routine.send(eventA{})
|
routine.trySend(eventA{})
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
assert.True(t, routine.trySend(errEvent{}),
|
||||||
if routine.isRunning() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
assert.True(t, routine.send(errEvent{}),
|
|
||||||
"expected send to succeed even when saturated")
|
"expected send to succeed even when saturated")
|
||||||
|
|
||||||
routine.wait()
|
assert.Equal(t, done, <-routine.final())
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user