@@ -23,7 +23,6 @@ import (
23
23
"sync"
24
24
25
25
"k8s.io/client-go/util/workqueue"
26
- "k8s.io/utils/ptr"
27
26
"sigs.k8s.io/controller-runtime/pkg/event"
28
27
"sigs.k8s.io/controller-runtime/pkg/handler"
29
28
"sigs.k8s.io/controller-runtime/pkg/predicate"
@@ -33,23 +32,18 @@ import (
33
32
// (e.g. GitHub Webhook callback). Channel requires the user to wire the external
34
33
// source (e.g. http handler) to write GenericEvents to the underlying channel.
35
34
type Channel [T any ] struct {
36
- // once ensures the event distribution goroutine will be performed only once
37
- once sync.Once
38
-
39
- // source is the source channel to fetch GenericEvents
40
- Source <- chan event.TypedGenericEvent [T ]
35
+ // Broadcaster contains the source channel for events.
36
+ Broadcaster * ChannelBroadcaster [T ]
41
37
42
38
Handler handler.TypedEventHandler [T ]
43
39
44
40
Predicates []predicate.TypedPredicate [T ]
45
41
46
- BufferSize * int
47
-
48
- // dest is the destination channels of the added event handlers
49
- dest []chan event.TypedGenericEvent [T ]
42
+ DestBufferSize int
50
43
51
- // destLock is to ensure the destination channels are safely added/removed
52
- destLock sync.Mutex
44
+ mu sync.Mutex
45
+ // isStarted is true if the source has been started. A source can only be started once.
46
+ isStarted bool
53
47
}
54
48
55
49
func (cs * Channel [T ]) String () string {
@@ -62,89 +56,72 @@ func (cs *Channel[T]) Start(
62
56
queue workqueue.RateLimitingInterface ,
63
57
) error {
64
58
// Source should have been specified by the user.
65
- if cs .Source == nil {
66
- return fmt .Errorf ("must specify Channel.Source " )
59
+ if cs .Broadcaster == nil {
60
+ return fmt .Errorf ("must create Channel with a non-nil Broadcaster " )
67
61
}
68
62
if cs .Handler == nil {
69
- return errors .New ("must specify Channel. Handler" )
63
+ return errors .New ("must create Channel with a non-nil Handler" )
70
64
}
71
-
72
- if cs .BufferSize == nil {
73
- cs .BufferSize = ptr .To (1024 )
65
+ if cs .DestBufferSize == 0 {
66
+ return errors .New ("must create Channel with a >0 DestBufferSize" )
74
67
}
75
68
76
- dst := make (chan event.TypedGenericEvent [T ], * cs .BufferSize )
77
-
78
- cs .destLock .Lock ()
79
- cs .dest = append (cs .dest , dst )
80
- cs .destLock .Unlock ()
69
+ cs .mu .Lock ()
70
+ defer cs .mu .Unlock ()
71
+ if cs .isStarted {
72
+ return fmt .Errorf ("cannot start an already started Channel source" )
73
+ }
74
+ cs .isStarted = true
81
75
82
- cs . once . Do ( func () {
83
- // Distribute GenericEvents to all EventHandler / Queue pairs Watching this source
84
- go cs .syncLoop ( ctx )
85
- } )
76
+ // Create a destination channel for the event handler
77
+ // and add it to the list of destinations
78
+ destination := make ( chan event. TypedGenericEvent [ T ], cs .DestBufferSize )
79
+ cs . Broadcaster . AddListener ( destination )
86
80
87
81
go func () {
88
- for evt := range dst {
89
- shouldHandle := true
90
- for _ , p := range cs .Predicates {
91
- if ! p .Generic (evt ) {
92
- shouldHandle = false
93
- break
94
- }
95
- }
96
-
97
- if shouldHandle {
98
- func () {
99
- ctx , cancel := context .WithCancel (ctx )
100
- defer cancel ()
101
- cs .Handler .Generic (ctx , evt , queue )
102
- }()
103
- }
104
- }
82
+ // Remove the listener and wait for the broadcaster
83
+ // to stop sending events to the destination channel.
84
+ defer cs .Broadcaster .RemoveListener (destination )
85
+
86
+ cs .processReceivedEvents (
87
+ ctx ,
88
+ destination ,
89
+ queue ,
90
+ cs .Handler ,
91
+ cs .Predicates ,
92
+ )
105
93
}()
106
94
107
95
return nil
108
96
}
109
97
110
- func (cs * Channel [T ]) doStop () {
111
- cs .destLock .Lock ()
112
- defer cs .destLock .Unlock ()
113
-
114
- for _ , dst := range cs .dest {
115
- close (dst )
116
- }
117
- }
118
-
119
- func (cs * Channel [T ]) distribute (evt event.TypedGenericEvent [T ]) {
120
- cs .destLock .Lock ()
121
- defer cs .destLock .Unlock ()
122
-
123
- for _ , dst := range cs .dest {
124
- // We cannot make it under goroutine here, or we'll meet the
125
- // race condition of writing message to closed channels.
126
- // To avoid blocking, the dest channels are expected to be of
127
- // proper buffer size. If we still see it blocked, then
128
- // the controller is thought to be in an abnormal state.
129
- dst <- evt
130
- }
131
- }
132
-
133
- func (cs * Channel [T ]) syncLoop (ctx context.Context ) {
98
+ func (cs * Channel [T ]) processReceivedEvents (
99
+ ctx context.Context ,
100
+ destination <- chan event.TypedGenericEvent [T ],
101
+ queue workqueue.RateLimitingInterface ,
102
+ eventHandler handler.TypedEventHandler [T ],
103
+ predicates []predicate.TypedPredicate [T ],
104
+ ) {
105
+ eventloop:
134
106
for {
135
107
select {
136
108
case <- ctx .Done ():
137
- // Close destination channels
138
- cs .doStop ()
139
109
return
140
- case evt , stillOpen := <- cs . Source :
110
+ case event , stillOpen := <- destination :
141
111
if ! stillOpen {
142
- // if the source channel is closed, we're never gonna get
143
- // anything more on it, so stop & bail
144
- cs .doStop ()
145
112
return
146
113
}
147
- cs .distribute (evt )
114
+
115
+ // Check predicates against the event first
116
+ // and continue the outer loop if any of them fail.
117
+ for _ , p := range predicates {
118
+ if ! p .Generic (event ) {
119
+ continue eventloop
120
+ }
121
+ }
122
+
123
+ // Call the event handler with the event.
124
+ eventHandler .Generic (ctx , event , queue )
148
125
}
149
126
}
150
127
}
0 commit comments