buz 2.15.6__py3-none-any.whl → 2.15.8rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  import json
2
3
  import traceback
3
4
  from abc import abstractmethod
@@ -39,6 +40,7 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
39
40
  __SECONDS_BETWEEN_EXECUTIONS_IF_THERE_ARE_NO_TASKS_IN_THE_QUEUE = 1
40
41
  __SECONDS_BETWEEN_POLLS_IF_THERE_ARE_TASKS_IN_THE_QUEUE = 1
41
42
  __SECONDS_BETWEEN_POLLS_IF_THERE_ARE_NO_NEW_TASKS = 1
43
+ __SECONDS_TO_WAIT_BETWEEN_REBALANCING_IN_PROGRESS = 0.1
42
44
  __MAX_NUMBER_OF_CONCURRENT_POLLING_TASKS = 20
43
45
 
44
46
  def __init__(
@@ -103,8 +105,9 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
103
105
  )
104
106
  self.__wait_for_connection_to_cluster_ms: Optional[int] = wait_for_connection_to_cluster_ms
105
107
  self.__polling_tasks_semaphore = Semaphore(self.__max_number_of_concurrent_polling_tasks)
106
- self.__task_execution_mutex = Lock()
108
+ self.__consumer_and_partition_mutex: dict[str, Lock] = defaultdict(Lock)
107
109
  self.__is_worked_initialized = False
110
+ self.__number_of_rebalancing_processes_in_progress: int = 0
108
111
 
109
112
  async def configure_http_check_server(self, health_check_port: int) -> web.TCPSite:
110
113
  self._logger.info(f"Starting health check server on port {health_check_port}")
@@ -114,7 +117,7 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
114
117
 
115
118
  runner = web.AppRunner(app)
116
119
  await runner.setup()
117
- site = web.TCPSite(runner, "localhost", health_check_port)
120
+ site = web.TCPSite(runner, "0.0.0.0", health_check_port)
118
121
  await site.start()
119
122
  return site
120
123
 
@@ -156,16 +159,13 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
156
159
  f" - Session timeout ms: {self.__session_timeout_ms}\n"
157
160
  f" - Max poll interval ms: {self.__max_poll_interval_ms}\n"
158
161
  f" - Heartbeat interval ms: {self.__heartbeat_interval_ms}\n"
159
- f" - Seconds between executions if there are no tasks in the queue: "
160
- f"{self.__seconds_between_executions_if_there_are_no_tasks_in_the_queue}\n"
161
- f" - Seconds between polls if there are tasks in the queue: "
162
- f"{self.__seconds_between_polls_if_there_are_tasks_in_the_queue}\n"
163
- f" - Seconds between polls if there are no new tasks: "
164
- f"{self.__seconds_between_polls_if_there_are_no_new_tasks}\n"
162
+ f" - Seconds between executions if there are no tasks in the queue: {self.__seconds_between_executions_if_there_are_no_tasks_in_the_queue}\n"
163
+ f" - Seconds between polls if there are tasks in the queue: {self.__seconds_between_polls_if_there_are_tasks_in_the_queue}\n"
164
+ f" - Seconds between polls if there are no new tasks: {self.__seconds_between_polls_if_there_are_no_new_tasks}\n"
165
165
  f" - Max number of concurrent polling tasks: {self.__max_number_of_concurrent_polling_tasks}\n"
166
166
  f" - Wait for connection to cluster ms: {self.__wait_for_connection_to_cluster_ms}\n"
167
167
  f" - Health check port: {self.__health_check_port}\n"
168
- f" - Number of subscribers: {len(self.__subscribers)}"
168
+ f" - Number of subscribers: {len(self.__subscribers)}",
169
169
  )
170
170
 
171
171
  async def __handle_graceful_stop(self, worker_errors: tuple[Optional[Exception], Optional[Exception]]) -> None:
@@ -234,7 +234,7 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
234
234
  self.__queue_per_consumer_mapper[kafka_consumer] = InMemoryMultiqueueRepository()
235
235
 
236
236
  self._logger.info(
237
- f"initializing consumer group: '{kafka_consumer.get_consumer_group()}' subscribed to the topics: '{kafka_consumer.get_topics()}'"
237
+ f"Initializing consumer group: '{kafka_consumer.get_consumer_group()}' subscribed to the topics: '{kafka_consumer.get_topics()}'"
238
238
  )
239
239
 
240
240
  await kafka_consumer.init()
@@ -306,29 +306,59 @@ class BaseBuzAIOKafkaAsyncConsumer(AsyncConsumer):
306
306
  last_consumer, _ = next(consumer_queues_cyclic_iterator)
307
307
 
308
308
  while not self.__should_stop.is_set():
309
+ if self.is_rebalancing_in_progress():
310
+ self._logger.info("Waiting for rebalancing")
311
+ await sleep(self.__SECONDS_TO_WAIT_BETWEEN_REBALANCING_IN_PROGRESS)
312
+ continue
313
+
309
314
  if await self.__all_queues_are_empty():
310
315
  await sleep(self.__seconds_between_executions_if_there_are_no_tasks_in_the_queue)
311
316
  continue
312
317
 
313
- async with self.__task_execution_mutex:
314
- consumer: Optional[AIOKafkaConsumer] = None
318
+ consumer: Optional[AIOKafkaConsumer] = None
319
+
320
+ while consumer != last_consumer:
321
+ consumer, queue = next(consumer_queues_cyclic_iterator)
322
+ kafka_poll_record = queue.pop()
323
+
324
+ if kafka_poll_record is not None:
325
+ consuming_task = ConsumingTask(consumer, kafka_poll_record)
326
+ async with self.__get_partition_mutex(
327
+ consumer_fqn=consuming_task.consumer.get_consumer_group(),
328
+ partition=kafka_poll_record.partition,
329
+ ):
330
+ yield consuming_task
315
331
 
316
- while consumer != last_consumer:
317
- consumer, queue = next(consumer_queues_cyclic_iterator)
318
- kafka_poll_record = queue.pop()
332
+ last_consumer = consumer
333
+ break
319
334
 
320
- if kafka_poll_record is not None:
321
- yield ConsumingTask(consumer, kafka_poll_record)
322
- last_consumer = consumer
323
- break
335
+ def __get_partition_mutex(self, consumer_fqn: str, partition: int) -> Lock:
336
+ mutex_key = f"consumer_{consumer_fqn}_partition_{partition}"
337
+ return self.__consumer_and_partition_mutex[mutex_key]
324
338
 
325
339
  async def __all_queues_are_empty(self) -> bool:
326
340
  return all([queue.is_totally_empty() for queue in self.__queue_per_consumer_mapper.values()])
327
341
 
328
342
  async def __on_partition_revoked(self, consumer: AIOKafkaConsumer, topics_partitions: set[TopicPartition]) -> None:
329
- async with self.__task_execution_mutex:
330
- for topic_partition in topics_partitions:
343
+ self._logger.info(f"rebalancing in progress, revoking partitions {topics_partitions}")
344
+
345
+ self.increase_number_of_rebalancing_in_progress()
346
+ for topic_partition in topics_partitions:
347
+ async with self.__get_partition_mutex(
348
+ consumer_fqn=consumer.get_consumer_group(),
349
+ partition=topic_partition.partition,
350
+ ):
331
351
  self.__queue_per_consumer_mapper[consumer].clear(topic_partition)
352
+ self.decrease_number_of_rebalancing_in_progress()
353
+
354
+ def increase_number_of_rebalancing_in_progress(self) -> None:
355
+ self.__number_of_rebalancing_processes_in_progress += 1
356
+
357
+ def decrease_number_of_rebalancing_in_progress(self) -> None:
358
+ self.__number_of_rebalancing_processes_in_progress -= 1
359
+
360
+ def is_rebalancing_in_progress(self) -> bool:
361
+ return self.__number_of_rebalancing_processes_in_progress > 0
332
362
 
333
363
  def request_stop(self) -> None:
334
364
  self.__should_stop.set()
@@ -152,7 +152,7 @@ class AIOKafkaConsumer:
152
152
  ),
153
153
  )
154
154
 
155
- self.__logger.info(f"initializing connection of consumer with group_id={self.__consumer_group}")
155
+ self.__logger.info(f"Initializing connection of consumer with group_id={self.__consumer_group}")
156
156
 
157
157
  if self.__wait_for_connection_to_cluster_ms is not None:
158
158
  await asyncio.wait_for(self.__consumer.start(), self.__wait_for_connection_to_cluster_ms / 1000)
@@ -163,10 +163,7 @@ class AIOKafkaConsumer:
163
163
  self,
164
164
  topics_partitions: set[TopicPartition],
165
165
  ) -> None:
166
- # It could happen that the new partition assigned it was a previous one processed by this consumer, so we need to seek to the last committed offset
167
- self.__consumer.pause(*topics_partitions)
168
- await self.__consumer.seek_to_committed(*topics_partitions)
169
- self.__consumer.resume(*topics_partitions)
166
+ return
170
167
 
171
168
  async def __on_partitions_revoked(
172
169
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: buz
3
- Version: 2.15.6
3
+ Version: 2.15.8rc1
4
4
  Summary: Buz is a set of light, simple and extensible implementations of event, command and query buses.
5
5
  License: MIT
6
6
  Author: Luis Pintado Lozano
@@ -47,7 +47,7 @@ buz/event/exceptions/worker_execution_exception.py,sha256=6mgztvXOCG_9VZ_Jptkk72
47
47
  buz/event/infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
48
  buz/event/infrastructure/buz_kafka/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  buz/event/infrastructure/buz_kafka/async_buz_kafka_event_bus.py,sha256=SyLblUVlwWOaNfZzK7vL6Ee4m-85vZVCH0rjOgqVAww,4913
50
- buz/event/infrastructure/buz_kafka/base_buz_aiokafka_async_consumer.py,sha256=y8ZgJ11yfd9aARnZifTBNntlbDlj5mTiTtEsA5_72Dw,18672
50
+ buz/event/infrastructure/buz_kafka/base_buz_aiokafka_async_consumer.py,sha256=13CqXqXk_1tgVJ6SpXpW4nr2SMA7ujpm8l4q3Yhtdsg,20179
51
51
  buz/event/infrastructure/buz_kafka/buz_aiokafka_async_consumer.py,sha256=lLHUnf9DpfAyB0PN7oBjn-Eyyl0zCHsSnrb6QUigruk,6345
52
52
  buz/event/infrastructure/buz_kafka/buz_aiokafka_multi_threaded_consumer.py,sha256=CiuNTns6eifR2JNGhXGNTdWURu-IQVIioe5n_pMSt9s,6288
53
53
  buz/event/infrastructure/buz_kafka/buz_kafka_event_bus.py,sha256=ymRSvcYVgbVCPgHN6rMBVBHQ5heCSwCDl6EffyqGVX8,4601
@@ -164,7 +164,7 @@ buz/kafka/domain/services/kafka_admin_test_client.py,sha256=91l_vFIo1yhJLQQCC_Om
164
164
  buz/kafka/domain/services/kafka_producer.py,sha256=8bLTV328orrPHcARzkc6no4vyJzrArVtCsjmSRXDjos,506
165
165
  buz/kafka/infrastructure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
166
166
  buz/kafka/infrastructure/aiokafka/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
- buz/kafka/infrastructure/aiokafka/aiokafka_consumer.py,sha256=QyY7f2_E92wfX3Ru2IpLXQvC5Wj814SgDrcwe8inpTA,9102
167
+ buz/kafka/infrastructure/aiokafka/aiokafka_consumer.py,sha256=9ORj5couBePn5LtIlTXUlMF2hSOuZ5vmgG0RUbw-BVM,8792
168
168
  buz/kafka/infrastructure/aiokafka/aiokafka_producer.py,sha256=LteHKIHpT6MKplwmwsPYMsd2GWNJCzus65XDHCIdoN8,3823
169
169
  buz/kafka/infrastructure/aiokafka/rebalance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
170
170
  buz/kafka/infrastructure/aiokafka/rebalance/kafka_callback_rebalancer.py,sha256=3l7NkTrCt3rBktVIS73cTmCOvv6eFguoCbGMYIUfCFc,1774
@@ -250,7 +250,7 @@ buz/serializer/message_to_json_bytes_serializer.py,sha256=RGZJ64t4t4Pz2FCASZZCv-
250
250
  buz/wrapper/__init__.py,sha256=GnRdJFcncn-qp0hzDG9dBHLmTJSbHFVjE_yr-MdW_n4,77
251
251
  buz/wrapper/async_to_sync.py,sha256=OfK-vrVUhuN-LLLvekLdMbQYtH0ue5lfbvuasj6ovMI,698
252
252
  buz/wrapper/event_loop.py,sha256=pfBJ1g-8A2a3YgW8Gf9Fg0kkewoh3-wgTy2KIFDyfHk,266
253
- buz-2.15.6.dist-info/LICENSE,sha256=Jytu2S-2SPEgsB0y6BF-_LUxIWY7402fl0JSh36TLZE,1062
254
- buz-2.15.6.dist-info/METADATA,sha256=u5zUavFByKN26H1zpAbr9Mmyo3TPvdq0_9_sGyVCMvM,1597
255
- buz-2.15.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
256
- buz-2.15.6.dist-info/RECORD,,
253
+ buz-2.15.8rc1.dist-info/LICENSE,sha256=Jytu2S-2SPEgsB0y6BF-_LUxIWY7402fl0JSh36TLZE,1062
254
+ buz-2.15.8rc1.dist-info/METADATA,sha256=EcA5CSIiqruT_bLWz0GS6LaFdRbbbJiG_Stg9zspIug,1600
255
+ buz-2.15.8rc1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
256
+ buz-2.15.8rc1.dist-info/RECORD,,
File without changes