15
15
import logging
16
16
import random
17
17
import re
18
+ import threading
18
19
import time
19
20
20
21
from kafka .vendor import six
21
22
22
23
import kafka .errors as Errors
23
24
from kafka .protocol .list_offsets import OffsetResetStrategy
24
25
from kafka .structs import OffsetAndMetadata
25
- from kafka .util import ensure_valid_topic_name
26
+ from kafka .util import ensure_valid_topic_name , synchronized
26
27
27
28
log = logging .getLogger (__name__ )
28
29
@@ -84,6 +85,7 @@ def __init__(self, offset_reset_strategy='earliest'):
84
85
self .assignment = OrderedDict ()
85
86
self .rebalance_listener = None
86
87
self .listeners = []
88
+ self ._lock = threading .RLock ()
87
89
88
90
def _set_subscription_type (self , subscription_type ):
89
91
if not isinstance (subscription_type , SubscriptionType ):
@@ -93,6 +95,7 @@ def _set_subscription_type(self, subscription_type):
93
95
elif self .subscription_type != subscription_type :
94
96
raise Errors .IllegalStateError (self ._SUBSCRIPTION_EXCEPTION_MESSAGE )
95
97
98
+ @synchronized
96
99
def subscribe (self , topics = (), pattern = None , listener = None ):
97
100
"""Subscribe to a list of topics, or a topic regex pattern.
98
101
@@ -147,6 +150,7 @@ def subscribe(self, topics=(), pattern=None, listener=None):
147
150
raise TypeError ('listener must be a ConsumerRebalanceListener' )
148
151
self .rebalance_listener = listener
149
152
153
+ @synchronized
150
154
def change_subscription (self , topics ):
151
155
"""Change the topic subscription.
152
156
@@ -178,6 +182,7 @@ def change_subscription(self, topics):
178
182
self .subscription = set (topics )
179
183
self ._group_subscription .update (topics )
180
184
185
+ @synchronized
181
186
def group_subscribe (self , topics ):
182
187
"""Add topics to the current group subscription.
183
188
@@ -191,13 +196,15 @@ def group_subscribe(self, topics):
191
196
raise Errors .IllegalStateError (self ._SUBSCRIPTION_EXCEPTION_MESSAGE )
192
197
self ._group_subscription .update (topics )
193
198
199
+ @synchronized
194
200
def reset_group_subscription (self ):
195
201
"""Reset the group's subscription to only contain topics subscribed by this consumer."""
196
202
if not self .partitions_auto_assigned ():
197
203
raise Errors .IllegalStateError (self ._SUBSCRIPTION_EXCEPTION_MESSAGE )
198
204
assert self .subscription is not None , 'Subscription required'
199
205
self ._group_subscription .intersection_update (self .subscription )
200
206
207
+ @synchronized
201
208
def assign_from_user (self , partitions ):
202
209
"""Manually assign a list of TopicPartitions to this consumer.
203
210
@@ -222,6 +229,7 @@ def assign_from_user(self, partitions):
222
229
self ._set_assignment ({partition : self .assignment .get (partition , TopicPartitionState ())
223
230
for partition in partitions })
224
231
232
+ @synchronized
225
233
def assign_from_subscribed (self , assignments ):
226
234
"""Update the assignment to the specified partitions
227
235
@@ -258,6 +266,7 @@ def _set_assignment(self, partition_states, randomize=False):
258
266
for tp in topic_partitions [topic ]:
259
267
self .assignment [tp ] = partition_states [tp ]
260
268
269
+ @synchronized
261
270
def unsubscribe (self ):
262
271
"""Clear all topic subscriptions and partition assignments"""
263
272
self .subscription = None
@@ -266,6 +275,7 @@ def unsubscribe(self):
266
275
self .subscribed_pattern = None
267
276
self .subscription_type = SubscriptionType .NONE
268
277
278
+ @synchronized
269
279
def group_subscription (self ):
270
280
"""Get the topic subscription for the group.
271
281
@@ -281,6 +291,7 @@ def group_subscription(self):
281
291
"""
282
292
return self ._group_subscription
283
293
294
+ @synchronized
284
295
def seek (self , partition , offset ):
285
296
"""Manually specify the fetch offset for a TopicPartition.
286
297
@@ -298,15 +309,18 @@ def seek(self, partition, offset):
298
309
raise TypeError ("offset must be type in or OffsetAndMetadata" )
299
310
self .assignment [partition ].seek (offset )
300
311
312
+ @synchronized
301
313
def assigned_partitions (self ):
302
314
"""Return set of TopicPartitions in current assignment."""
303
315
return set (self .assignment .keys ())
304
316
317
+ @synchronized
305
318
def paused_partitions (self ):
306
319
"""Return current set of paused TopicPartitions."""
307
320
return set (partition for partition in self .assignment
308
321
if self .is_paused (partition ))
309
322
323
+ @synchronized
310
324
def fetchable_partitions (self ):
311
325
"""Return ordered list of TopicPartitions that should be Fetched."""
312
326
fetchable = list ()
@@ -315,10 +329,12 @@ def fetchable_partitions(self):
315
329
fetchable .append (partition )
316
330
return fetchable
317
331
332
+ @synchronized
318
333
def partitions_auto_assigned (self ):
319
334
"""Return True unless user supplied partitions manually."""
320
335
return self .subscription_type in (SubscriptionType .AUTO_TOPICS , SubscriptionType .AUTO_PATTERN )
321
336
337
+ @synchronized
322
338
def all_consumed_offsets (self ):
323
339
"""Returns consumed offsets as {TopicPartition: OffsetAndMetadata}"""
324
340
all_consumed = {}
@@ -327,6 +343,7 @@ def all_consumed_offsets(self):
327
343
all_consumed [partition ] = state .position
328
344
return all_consumed
329
345
346
+ @synchronized
330
347
def request_offset_reset (self , partition , offset_reset_strategy = None ):
331
348
"""Mark partition for offset reset using specified or default strategy.
332
349
@@ -338,33 +355,40 @@ def request_offset_reset(self, partition, offset_reset_strategy=None):
338
355
offset_reset_strategy = self ._default_offset_reset_strategy
339
356
self .assignment [partition ].reset (offset_reset_strategy )
340
357
358
+ @synchronized
341
359
def set_reset_pending (self , partitions , next_allowed_reset_time ):
342
360
for partition in partitions :
343
361
self .assignment [partition ].set_reset_pending (next_allowed_reset_time )
344
362
363
+ @synchronized
345
364
def has_default_offset_reset_policy (self ):
346
365
"""Return True if default offset reset policy is Earliest or Latest"""
347
366
return self ._default_offset_reset_strategy != OffsetResetStrategy .NONE
348
367
368
+ @synchronized
349
369
def is_offset_reset_needed (self , partition ):
350
370
return self .assignment [partition ].awaiting_reset
351
371
372
+ @synchronized
352
373
def has_all_fetch_positions (self ):
353
374
for state in six .itervalues (self .assignment ):
354
375
if not state .has_valid_position :
355
376
return False
356
377
return True
357
378
379
+ @synchronized
358
380
def missing_fetch_positions (self ):
359
381
missing = set ()
360
382
for partition , state in six .iteritems (self .assignment ):
361
383
if state .is_missing_position ():
362
384
missing .add (partition )
363
385
return missing
364
386
387
+ @synchronized
365
388
def has_valid_position (self , partition ):
366
389
return partition in self .assignment and self .assignment [partition ].has_valid_position
367
390
391
+ @synchronized
368
392
def reset_missing_positions (self ):
369
393
partitions_with_no_offsets = set ()
370
394
for tp , state in six .iteritems (self .assignment ):
@@ -377,32 +401,40 @@ def reset_missing_positions(self):
377
401
if partitions_with_no_offsets :
378
402
raise Errors .NoOffsetForPartitionError (partitions_with_no_offsets )
379
403
404
+ @synchronized
380
405
def partitions_needing_reset (self ):
381
406
partitions = set ()
382
407
for tp , state in six .iteritems (self .assignment ):
383
408
if state .awaiting_reset and state .is_reset_allowed ():
384
409
partitions .add (tp )
385
410
return partitions
386
411
412
+ @synchronized
387
413
def is_assigned (self , partition ):
388
414
return partition in self .assignment
389
415
416
+ @synchronized
390
417
def is_paused (self , partition ):
391
418
return partition in self .assignment and self .assignment [partition ].paused
392
419
420
+ @synchronized
393
421
def is_fetchable (self , partition ):
394
422
return partition in self .assignment and self .assignment [partition ].is_fetchable ()
395
423
424
+ @synchronized
396
425
def pause (self , partition ):
397
426
self .assignment [partition ].pause ()
398
427
428
+ @synchronized
399
429
def resume (self , partition ):
400
430
self .assignment [partition ].resume ()
401
431
432
+ @synchronized
402
433
def reset_failed (self , partitions , next_retry_time ):
403
434
for partition in partitions :
404
435
self .assignment [partition ].reset_failed (next_retry_time )
405
436
437
+ @synchronized
406
438
def move_partition_to_end (self , partition ):
407
439
if partition in self .assignment :
408
440
try :
@@ -411,6 +443,7 @@ def move_partition_to_end(self, partition):
411
443
state = self .assignment .pop (partition )
412
444
self .assignment [partition ] = state
413
445
446
+ @synchronized
414
447
def position (self , partition ):
415
448
return self .assignment [partition ].position
416
449
0 commit comments