Skip to content

Commit

Permalink
core: eliminate potential race conditions from Events and Every funct…
Browse files Browse the repository at this point in the history
…ions

Signed-off-by: deadprogram <[email protected]>
  • Loading branch information
deadprogram committed Dec 8, 2016
1 parent 6ea64d9 commit d136374
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 23 deletions.
11 changes: 11 additions & 0 deletions eventer.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package gobot

import "sync"

type eventChannel chan *Event

type eventer struct {
Expand All @@ -11,6 +13,9 @@ type eventer struct {

// map of out channels used by subscribers
outs map[eventChannel]eventChannel

// mutex to protect the eventChannel map
eventsMutex sync.Mutex
}

// Eventer is the interface which describes how a Driver or Adaptor
Expand Down Expand Up @@ -58,9 +63,11 @@ func NewEventer() Eventer {
for {
select {
case evt := <-evtr.in:
evtr.eventsMutex.Lock()
for _, out := range evtr.outs {
out <- evt
}
evtr.eventsMutex.Unlock()
}
}
}()
Expand Down Expand Up @@ -97,13 +104,17 @@ func (e *eventer) Publish(name string, data interface{}) {

// Subscribe to any events from this eventer
func (e *eventer) Subscribe() eventChannel {
e.eventsMutex.Lock()
defer e.eventsMutex.Unlock()
out := make(eventChannel)
e.outs[out] = out
return out
}

// Unsubscribe from the event channel
func (e *eventer) Unsubscribe(events eventChannel) {
e.eventsMutex.Lock()
defer e.eventsMutex.Unlock()
delete(e.outs, events)
}

Expand Down
4 changes: 2 additions & 2 deletions examples/every_done.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ func main() {
robot := gobot.NewRobot(
"hello",
func() {
done := gobot.Every(500*time.Millisecond, func() {
done := gobot.Every(750*time.Millisecond, func() {
fmt.Println("Greetings human")
})

gobot.After(5*time.Second, func() {
done <- true
done.Stop()
fmt.Println("We're done here")
})
},
Expand Down
18 changes: 7 additions & 11 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,23 @@ var eventError = func(e *Event) (err error) {
return
}

// Every triggers f every t time until the end of days, or when a
// bool value is sent to the channel returned by the Every function.
// Every triggers f every t time.Duration until the end of days, or when a Stop()
// is called on the Ticker that is returned by the Every function.
// It does not wait for the previous execution of f to finish before
// it fires the next f.
func Every(t time.Duration, f func()) chan bool {
done := make(chan bool)
c := time.Tick(t)
func Every(t time.Duration, f func()) *time.Ticker {
ticker := time.NewTicker(t)

go func() {
for {
select {
case <-done:
return
default:
<-c
go f()
case <-ticker.C:
f()
}
}
}()

return done
return ticker
}

// After triggers f after t duration.
Expand Down
37 changes: 27 additions & 10 deletions utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,42 @@ func TestEvery(t *testing.T) {
}
}

func TestEveryWhenDone(t *testing.T) {
i := 0
done := Every(20*time.Millisecond, func() {
i++
func TestEveryWhenStopped(t *testing.T) {
sem := make(chan bool)

done := Every(50*time.Millisecond, func() {
sem <- true
})
time.Sleep(10 * time.Millisecond)
done <- true
time.Sleep(50 * time.Millisecond)
if i > 1 {
t.Error("Test should have stopped after 20ms")

select {
case <-sem:
done.Stop()
case <-time.After(60 * time.Millisecond):
t.Errorf("Every was not called")
}

select {
case <-time.After(60 * time.Millisecond):
case <-sem:
t.Error("Every should have stopped")
}
}

func TestAfter(t *testing.T) {
i := 0
sem := make(chan bool)

After(1*time.Millisecond, func() {
i++
sem <- true
})
time.Sleep(2 * time.Millisecond)

select {
case <-sem:
case <-time.After(10 * time.Millisecond):
t.Errorf("After was not called")
}

gobottest.Assert(t, i, 1)
}

Expand Down

0 comments on commit d136374

Please sign in to comment.