1
1
package subscriber
2
2
3
3
import (
4
+ "context"
4
5
"sort"
5
6
"sync"
6
7
"time"
@@ -23,13 +24,16 @@ const safeClaimRetriesCount = 10
23
24
// leave and update their subscriptions, and generates notifications of such
24
25
// changes. It also provides an API to for a partition consumer to claim and
25
26
// release a group-topic-partition.
27
+ //
28
+ // FIXME: It is assumed that all members of the group are registered with the
29
+ // FIXME: `static` pattern. If a member that pattern is either `white_list` or
30
+ // FIXME: `black_list` joins the group the result will be unpredictable.
26
31
type T struct {
27
32
actDesc * actor.Descriptor
28
33
cfg * config.Proxy
29
34
group string
30
35
groupZNode * kazoo.Consumergroup
31
36
groupMemberZNode * kazoo.ConsumergroupInstance
32
- topics []string
33
37
topicsCh chan []string
34
38
subscriptionsCh chan map [string ][]string
35
39
stopCh chan none.T
@@ -149,133 +153,126 @@ func (ss *T) run() {
149
153
150
154
var (
151
155
nilOrSubscriptionsCh chan <- map [string ][]string
152
- nilOrGroupUpdatedCh <- chan zk. Event
156
+ nilOrWatchCh <- chan none. T
153
157
nilOrTimeoutCh <- chan time.Time
154
- pendingTopics []string
155
- pendingSubscriptions map [string ][]string
158
+ cancelWatch context.CancelFunc
156
159
shouldSubmitTopics = false
157
- shouldFetchMembers = false
158
160
shouldFetchSubscriptions = false
159
- members []* kazoo.ConsumergroupInstance
161
+ topics []string
162
+ subscriptions map [string ][]string
160
163
)
161
164
for {
162
165
select {
163
- case topics : = <- ss .topicsCh :
164
- pendingTopics = normalizeTopics (topics )
165
- shouldSubmitTopics = ! topicsEqual ( pendingTopics , ss . topics )
166
- case nilOrSubscriptionsCh <- pendingSubscriptions :
166
+ case topics = <- ss .topicsCh :
167
+ sort . Strings (topics )
168
+ shouldSubmitTopics = true
169
+ case nilOrSubscriptionsCh <- subscriptions :
167
170
nilOrSubscriptionsCh = nil
168
- case <- nilOrGroupUpdatedCh :
169
- nilOrGroupUpdatedCh = nil
170
- shouldFetchMembers = true
171
+ case <- nilOrWatchCh :
172
+ nilOrWatchCh = nil
173
+ cancelWatch ()
174
+ shouldFetchSubscriptions = true
171
175
case <- nilOrTimeoutCh :
176
+ nilOrTimeoutCh = nil
172
177
case <- ss .stopCh :
178
+ if cancelWatch != nil {
179
+ cancelWatch ()
180
+ }
173
181
return
174
182
}
175
183
176
184
if shouldSubmitTopics {
177
- if err = ss .submitTopics (pendingTopics ); err != nil {
185
+ if err = ss .submitTopics (topics ); err != nil {
178
186
ss .actDesc .Log ().WithError (err ).Error ("Failed to submit topics" )
179
187
nilOrTimeoutCh = time .After (ss .cfg .Consumer .RetryBackoff )
180
188
continue
181
189
}
182
- ss .actDesc .Log ().Infof ("Submitted: topics=%v" , pendingTopics )
190
+ ss .actDesc .Log ().Infof ("Submitted: topics=%v" , topics )
183
191
shouldSubmitTopics = false
184
- shouldFetchMembers = true
185
- }
186
-
187
- if shouldFetchMembers {
188
- members , nilOrGroupUpdatedCh , err = ss .groupZNode .WatchInstances ()
189
- if err != nil {
190
- ss .actDesc .Log ().WithError (err ).Error ("Failed to watch members" )
191
- nilOrTimeoutCh = time .After (ss .cfg .Consumer .RetryBackoff )
192
- continue
192
+ if cancelWatch != nil {
193
+ cancelWatch ()
193
194
}
194
- shouldFetchMembers = false
195
195
shouldFetchSubscriptions = true
196
- // To avoid unnecessary rebalancing in case of a deregister/register
197
- // sequences that happen when a member updates its topic subscriptions,
198
- // we delay subscription fetching.
199
- nilOrTimeoutCh = time .After (ss .cfg .Consumer .RebalanceDelay )
200
- continue
201
196
}
202
197
203
198
if shouldFetchSubscriptions {
204
- pendingSubscriptions , err = ss .fetchSubscriptions (members )
199
+ subscriptions , nilOrWatchCh , cancelWatch , err = ss .fetchSubscriptions ()
205
200
if err != nil {
206
201
ss .actDesc .Log ().WithError (err ).Error ("Failed to fetch subscriptions" )
207
202
nilOrTimeoutCh = time .After (ss .cfg .Consumer .RetryBackoff )
208
203
continue
209
204
}
210
205
shouldFetchSubscriptions = false
211
- ss .actDesc .Log ().Infof ("Fetched subscriptions: %v" , pendingSubscriptions )
206
+ ss .actDesc .Log ().Infof ("Fetched subscriptions: %v" , subscriptions )
212
207
nilOrSubscriptionsCh = ss .subscriptionsCh
213
208
}
214
209
}
215
210
}
216
211
217
- // fetchSubscriptions retrieves registration records for the specified members
218
- // from ZooKeeper.
219
- //
220
- // FIXME: It is assumed that all members of the group are registered with the
221
- // FIXME: `static` pattern. If a member that pattern is either `white_list` or
222
- // FIXME: `black_list` joins the group the result will be unpredictable.
223
- func (ss * T ) fetchSubscriptions (members []* kazoo.ConsumergroupInstance ) (map [string ][]string , error ) {
212
+ // fetchSubscriptions retrieves subscription topics for all group members and
213
+ // returns a channel that will be closed
214
+ func (ss * T ) fetchSubscriptions () (map [string ][]string , <- chan none.T , context.CancelFunc , error ) {
215
+ members , groupUpdateWatchCh , err := ss .groupZNode .WatchInstances ()
216
+ if err != nil {
217
+ return nil , nil , nil , errors .Wrapf (err , "failed to watch members" )
218
+ }
219
+
220
+ memberUpdateWatchChs := make ([]<- chan zk.Event , len (members ))
224
221
subscriptions := make (map [string ][]string , len (members ))
225
- for _ , member := range members {
222
+ for i , member := range members {
226
223
var registration * kazoo.Registration
227
- registration , err := member .Registration ()
224
+ registration , memberUpdateWatchCh , err := member .WatchRegistration ()
228
225
for err != nil {
229
- return nil , errors .Wrapf (err , "Failed to fetch registration, member=%s" , member .ID )
226
+ return nil , nil , nil , errors .Wrapf (err , "failed to watch registration, member=%s" , member .ID )
230
227
}
231
- // Sort topics to ensure deterministic output.
228
+ memberUpdateWatchChs [i ] = memberUpdateWatchCh
229
+
232
230
topics := make ([]string , 0 , len (registration .Subscription ))
233
231
for topic := range registration .Subscription {
234
232
topics = append (topics , topic )
235
233
}
236
- subscriptions [member .ID ] = normalizeTopics (topics )
234
+ // Sort topics to ensure deterministic output.
235
+ sort .Strings (topics )
236
+ subscriptions [member .ID ] = topics
237
237
}
238
- return subscriptions , nil
238
+
239
+ aggregateWatchCh := make (chan none.T )
240
+ ctx , cancel := context .WithCancel (context .Background ())
241
+
242
+ go forwardWatch (ctx , groupUpdateWatchCh , aggregateWatchCh )
243
+ for _ , memberUpdateWatchCh := range memberUpdateWatchChs {
244
+ go forwardWatch (ctx , memberUpdateWatchCh , aggregateWatchCh )
245
+ }
246
+
247
+ return subscriptions , aggregateWatchCh , cancel , nil
239
248
}
240
249
241
250
func (ss * T ) submitTopics (topics []string ) error {
242
- if len (ss . topics ) ! = 0 {
251
+ if len (topics ) = = 0 {
243
252
err := ss .groupMemberZNode .Deregister ()
244
253
if err != nil && err != kazoo .ErrInstanceNotRegistered {
245
- return errors .Wrap (err , "Failed to deregister" )
254
+ return errors .Wrap (err , "failed to deregister" )
246
255
}
256
+ return nil
247
257
}
248
- ss .topics = nil
249
- if len (topics ) != 0 {
250
- err := ss .groupMemberZNode .Register (topics )
251
- for err != nil {
252
- return errors .Wrap (err , "Failed to register" )
253
- }
258
+ err := ss .groupMemberZNode .Register (topics )
259
+ for err != nil {
260
+ return errors .Wrap (err , "failed to register" )
254
261
}
255
- ss .topics = topics
256
262
return nil
257
263
}
258
264
259
- func normalizeTopics (s []string ) []string {
260
- if len (s ) == 0 {
261
- return nil
262
- }
263
- sort .Strings (s )
264
- return s
265
+ func millisSince (t time.Time ) time.Duration {
266
+ return time .Now ().Sub (t ) / time .Millisecond * time .Millisecond
265
267
}
266
268
267
- func topicsEqual (lhs , rhs []string ) bool {
268
- if len (lhs ) != len (rhs ) {
269
- return false
270
- }
271
- for i := range lhs {
272
- if lhs [i ] != rhs [i ] {
273
- return false
269
+ func forwardWatch (ctx context.Context , watchCh <- chan zk.Event , downstreamCh chan <- none.T ) {
270
+ select {
271
+ case <- watchCh :
272
+ select {
273
+ case downstreamCh <- none .V :
274
+ case <- ctx .Done ():
274
275
}
276
+ case <- ctx .Done ():
275
277
}
276
- return true
277
- }
278
-
279
- func millisSince (t time.Time ) time.Duration {
280
- return time .Now ().Sub (t ) / time .Millisecond * time .Millisecond
281
278
}
0 commit comments