Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cd7fd04

Browse files
committedSep 12, 2024·
feat!: performance improvements and better thread safety
1 parent 30a45be commit cd7fd04

File tree

2 files changed

+59
-48
lines changed

2 files changed

+59
-48
lines changed
 

‎doc.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
/*
2-
Package event provides a generic event system for Go.
3-
*/
1+
// Package event provides a generic and thread-safe event system for Go.
2+
// It allows multiple listeners to subscribe to events carrying data of any type.
3+
// Listeners can be added and notified when events are triggered, and the event
4+
// can be closed to prevent further operations.
45
package event

‎event.go

+55-45
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,84 @@
11
package event
22

3-
import "sync"
3+
import (
4+
"errors"
5+
"sync"
6+
)
47

5-
// Event represents an event system that can handle multiple listeners.
8+
// ErrEventClosed is returned when an operation is attempted on a closed event.
9+
var ErrEventClosed = errors.New("event is closed")
10+
11+
// Event represents a generic, thread-safe event system that can handle multiple listeners.
12+
// The type parameter T specifies the type of data that the event carries when triggered.
613
type Event[T any] struct {
7-
listeners []chan T
8-
mu sync.Mutex
14+
listeners []func(T)
15+
mu sync.RWMutex
916
closed bool
1017
}
1118

12-
// New creates a new event.
19+
// New creates and returns a new Event instance for the specified type T.
1320
func New[T any]() *Event[T] {
14-
// Create a new event
15-
return &Event[T]{
16-
listeners: []chan T{},
17-
}
21+
return &Event[T]{}
1822
}
1923

20-
// Trigger triggers the event and notifies all listeners.
21-
func (e *Event[T]) Trigger(value T) {
22-
e.mu.Lock()
23-
defer e.mu.Unlock()
24+
// Trigger notifies all registered listeners by invoking their callback functions with the provided value.
25+
// It runs each listener in a separate goroutine and waits for all listeners to complete.
26+
// Returns ErrEventClosed if the event has been closed.
27+
func (e *Event[T]) Trigger(value T) error {
28+
e.mu.RLock()
29+
if e.closed {
30+
e.mu.RUnlock()
31+
return ErrEventClosed
32+
}
33+
34+
// Copy the listeners to avoid holding the lock during execution.
35+
// This ensures that triggering the event is thread-safe even if listeners are added or removed concurrently.
36+
listeners := make([]func(T), len(e.listeners))
37+
copy(listeners, e.listeners)
38+
e.mu.RUnlock()
39+
40+
var wg sync.WaitGroup
41+
for _, listener := range listeners {
42+
wg.Add(1)
2443

25-
for _, listener := range e.listeners {
26-
go func(l chan T) {
27-
if !e.closed {
28-
l <- value
29-
}
44+
go func(f func(T)) {
45+
defer wg.Done()
46+
f(value)
3047
}(listener)
3148
}
49+
50+
wg.Wait()
51+
52+
return nil
3253
}
3354

34-
// Listen gets called when the event is triggered.
35-
func (e *Event[T]) Listen(f func(T)) {
36-
// Check if the event is closed
37-
if e.closed {
38-
return
39-
}
55+
// Listen registers a new listener callback function for the event.
56+
// The listener will be invoked with the event's data whenever Trigger is called.
57+
// Returns ErrEventClosed if the event has been closed.
58+
func (e *Event[T]) Listen(f func(T)) error {
59+
e.mu.Lock()
60+
defer e.mu.Unlock()
4061

41-
// Create listener slice if it doesn't exist
42-
if e.listeners == nil {
43-
e.listeners = []chan T{}
62+
if e.closed {
63+
return ErrEventClosed
4464
}
4565

46-
// Create a new channel
47-
ch := make(chan T)
66+
e.listeners = append(e.listeners, f)
4867

49-
e.mu.Lock()
50-
e.listeners = append(e.listeners, ch)
51-
e.mu.Unlock()
52-
53-
go func() {
54-
for v := range ch {
55-
if !e.closed {
56-
f(v)
57-
}
58-
}
59-
}()
68+
return nil
6069
}
6170

62-
// Close closes the event and all its listeners.
63-
// After calling this method, the event can't be used anymore and new listeners can't be added.
71+
// Close closes the event system, preventing any new listeners from being added or events from being triggered.
72+
// After calling Close, any subsequent calls to Trigger or Listen will return ErrEventClosed.
73+
// Existing listeners are removed, and resources are cleaned up.
6474
func (e *Event[T]) Close() {
6575
e.mu.Lock()
6676
defer e.mu.Unlock()
6777

68-
for _, listener := range e.listeners {
69-
close(listener)
78+
if e.closed {
79+
return
7080
}
7181

72-
e.listeners = nil
7382
e.closed = true
83+
e.listeners = nil // Release references to listener functions
7484
}

0 commit comments

Comments
 (0)
Please sign in to comment.