Skip to content

Commit 3b5137a

Browse files
authored
Merge pull request #22 from donutloop/feat/hooks
- Added: Simple hook implementation
2 parents 50e0d0f + 440faf5 commit 3b5137a

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

event/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Usage
2+
3+
Hooks holds a list of functions (func error) to call whenever the set is
4+
triggered.
5+
6+
## Example
7+
```go
8+
package main
9+
10+
import (
11+
"github.com/donutloop/toolkit/event"
12+
)
13+
14+
func main() {
15+
hooks := new(event.Hooks)
16+
hooks.Add(func() {
17+
// do things
18+
})
19+
hooks.Add(func() {
20+
// do things
21+
})
22+
hooks.Fire()
23+
}
24+
```

event/hook.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package event
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
)
7+
8+
// Hooks holds a list of parameter-less functions to call whenever the set is
9+
// triggered with Fire().
10+
type Hooks struct {
11+
funcs []func()
12+
mu sync.Mutex
13+
wg sync.WaitGroup
14+
}
15+
16+
func (h *Hooks) Add(f func()) {
17+
h.mu.Lock()
18+
h.funcs = append(h.funcs, f)
19+
h.mu.Unlock()
20+
}
21+
22+
// Fire calls all the functions in a given Hooks list. It launches a goroutine
23+
// for each function and then waits for all of them to finish before returning.
24+
func (h *Hooks) Fire() []error {
25+
h.mu.Lock()
26+
defer h.mu.Unlock()
27+
28+
errc := make(chan error, len(h.funcs))
29+
for _, hook := range h.funcs {
30+
h.wg.Add(1)
31+
go hookWrapper(&h.wg, hook, errc)
32+
}
33+
34+
h.wg.Wait()
35+
close(errc)
36+
errs := make([]error, 0, len(h.funcs))
37+
for err := range errc {
38+
errs = append(errs, err)
39+
}
40+
return errs
41+
}
42+
43+
func hookWrapper(wg *sync.WaitGroup, hook func(), errc chan error) {
44+
defer func() {
45+
if v := recover(); v != nil {
46+
errc <- fmt.Errorf("hook is panicked (%v)", v)
47+
}
48+
wg.Done()
49+
}()
50+
51+
hook()
52+
}

event/hook_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package event_test
2+
3+
import (
4+
"github.com/donutloop/toolkit/event"
5+
"testing"
6+
)
7+
8+
func TestHooksMultiFire(t *testing.T) {
9+
hooks := new(event.Hooks)
10+
hooks.Add(func() {})
11+
hooks.Add(func() {})
12+
13+
for i := 0; i < 10; i++ {
14+
errs := hooks.Fire()
15+
if len(errs) > 0 {
16+
for _, err := range errs {
17+
t.Error(err)
18+
}
19+
}
20+
}
21+
}
22+
23+
func TestHooks(t *testing.T) {
24+
triggered1 := false
25+
triggered2 := false
26+
27+
hooks := new(event.Hooks)
28+
hooks.Add(func() { triggered1 = true })
29+
hooks.Add(func() { triggered2 = true })
30+
31+
hooks.Fire()
32+
33+
if !triggered1 {
34+
t.Errorf("registered (first) hook function failed to trigger")
35+
}
36+
37+
if !triggered2 {
38+
t.Errorf("registered (second) hook function failed to trigger")
39+
}
40+
}
41+
42+
func TestHooksPanic(t *testing.T) {
43+
hooks := new(event.Hooks)
44+
hooks.Add(func() { panic("check isolation of goroutine") })
45+
errs := hooks.Fire()
46+
if len(errs) != 1 {
47+
t.Fatalf("error count is bad (%d)", len(errs))
48+
}
49+
50+
expectedMessage := "hook is panicked (check isolation of goroutine)"
51+
if errs[0].Error() != expectedMessage {
52+
t.Fatalf(`unexpected error message (actual: "%s", expected: "%s")`, errs[0].Error(), expectedMessage)
53+
}
54+
}
55+
56+
func BenchmarkHooks(b *testing.B) {
57+
hooks := new(event.Hooks)
58+
hooks.Add(func() {})
59+
for n := 0; n < b.N; n++ {
60+
hooks.Fire()
61+
}
62+
}

0 commit comments

Comments
 (0)