kafka-python 3.0.0__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.
- kafka/__init__.py +34 -0
- kafka/__main__.py +5 -0
- kafka/admin/__init__.py +29 -0
- kafka/admin/__main__.py +5 -0
- kafka/admin/_acls.py +355 -0
- kafka/admin/_cluster.py +359 -0
- kafka/admin/_configs.py +479 -0
- kafka/admin/_groups.py +754 -0
- kafka/admin/_partitions.py +595 -0
- kafka/admin/_topics.py +281 -0
- kafka/admin/_transactions.py +450 -0
- kafka/admin/_users.py +194 -0
- kafka/admin/client.py +373 -0
- kafka/benchmarks/__init__.py +0 -0
- kafka/benchmarks/consumer_performance.py +138 -0
- kafka/benchmarks/load_example.py +109 -0
- kafka/benchmarks/producer_encode_path.py +201 -0
- kafka/benchmarks/producer_performance.py +161 -0
- kafka/benchmarks/profile_protocol.py +138 -0
- kafka/benchmarks/protocol_old_vs_new.py +447 -0
- kafka/benchmarks/record_batch_compose.py +77 -0
- kafka/benchmarks/record_batch_read.py +82 -0
- kafka/benchmarks/varint_speed.py +426 -0
- kafka/cli/__init__.py +36 -0
- kafka/cli/admin/__init__.py +117 -0
- kafka/cli/admin/acls/__init__.py +9 -0
- kafka/cli/admin/acls/common.py +76 -0
- kafka/cli/admin/acls/create.py +19 -0
- kafka/cli/admin/acls/delete.py +23 -0
- kafka/cli/admin/acls/describe.py +16 -0
- kafka/cli/admin/cluster/__init__.py +14 -0
- kafka/cli/admin/cluster/describe.py +11 -0
- kafka/cli/admin/cluster/describe_quorum.py +11 -0
- kafka/cli/admin/cluster/features.py +52 -0
- kafka/cli/admin/cluster/log_dirs.py +43 -0
- kafka/cli/admin/cluster/versions.py +33 -0
- kafka/cli/admin/configs/__init__.py +10 -0
- kafka/cli/admin/configs/alter.py +43 -0
- kafka/cli/admin/configs/common.py +17 -0
- kafka/cli/admin/configs/describe.py +30 -0
- kafka/cli/admin/configs/list.py +16 -0
- kafka/cli/admin/configs/reset.py +20 -0
- kafka/cli/admin/groups/__init__.py +16 -0
- kafka/cli/admin/groups/alter_offsets.py +30 -0
- kafka/cli/admin/groups/delete.py +11 -0
- kafka/cli/admin/groups/delete_offsets.py +29 -0
- kafka/cli/admin/groups/describe.py +11 -0
- kafka/cli/admin/groups/list.py +28 -0
- kafka/cli/admin/groups/list_offsets.py +29 -0
- kafka/cli/admin/groups/remove_members.py +40 -0
- kafka/cli/admin/groups/reset_offsets.py +139 -0
- kafka/cli/admin/partitions/__init__.py +21 -0
- kafka/cli/admin/partitions/alter_reassignments.py +37 -0
- kafka/cli/admin/partitions/create.py +27 -0
- kafka/cli/admin/partitions/delete_records.py +31 -0
- kafka/cli/admin/partitions/describe.py +36 -0
- kafka/cli/admin/partitions/elect_leaders.py +53 -0
- kafka/cli/admin/partitions/list_offsets.py +88 -0
- kafka/cli/admin/partitions/list_reassignments.py +35 -0
- kafka/cli/admin/topics/__init__.py +10 -0
- kafka/cli/admin/topics/create.py +13 -0
- kafka/cli/admin/topics/delete.py +19 -0
- kafka/cli/admin/topics/describe.py +18 -0
- kafka/cli/admin/topics/list.py +11 -0
- kafka/cli/admin/transactions/__init__.py +17 -0
- kafka/cli/admin/transactions/abort.py +38 -0
- kafka/cli/admin/transactions/describe.py +24 -0
- kafka/cli/admin/transactions/describe_producers.py +29 -0
- kafka/cli/admin/transactions/find_hanging.py +26 -0
- kafka/cli/admin/transactions/list.py +37 -0
- kafka/cli/admin/users/__init__.py +8 -0
- kafka/cli/admin/users/alter_user_scram_credentials.py +34 -0
- kafka/cli/admin/users/describe_user_scram_credentials.py +15 -0
- kafka/cli/common.py +95 -0
- kafka/cli/consumer/__init__.py +63 -0
- kafka/cli/producer/__init__.py +57 -0
- kafka/cluster.py +824 -0
- kafka/codec.py +325 -0
- kafka/consumer/__init__.py +5 -0
- kafka/consumer/__main__.py +5 -0
- kafka/consumer/fetcher.py +2012 -0
- kafka/consumer/group.py +1347 -0
- kafka/consumer/subscription_state.py +897 -0
- kafka/coordinator/__init__.py +0 -0
- kafka/coordinator/assignors/__init__.py +0 -0
- kafka/coordinator/assignors/abstract.py +90 -0
- kafka/coordinator/assignors/cooperative_sticky.py +167 -0
- kafka/coordinator/assignors/range.py +81 -0
- kafka/coordinator/assignors/roundrobin.py +101 -0
- kafka/coordinator/assignors/sticky/StickyAssignorUserData.json +37 -0
- kafka/coordinator/assignors/sticky/__init__.py +0 -0
- kafka/coordinator/assignors/sticky/partition_movements.py +149 -0
- kafka/coordinator/assignors/sticky/sorted_set.py +63 -0
- kafka/coordinator/assignors/sticky/sticky_assignor.py +665 -0
- kafka/coordinator/assignors/sticky/user_data.py +8 -0
- kafka/coordinator/base.py +1215 -0
- kafka/coordinator/consumer.py +1224 -0
- kafka/coordinator/heartbeat.py +82 -0
- kafka/coordinator/subscription.py +34 -0
- kafka/errors.py +1004 -0
- kafka/future.py +166 -0
- kafka/metrics/__init__.py +13 -0
- kafka/metrics/compound_stat.py +33 -0
- kafka/metrics/dict_reporter.py +81 -0
- kafka/metrics/kafka_metric.py +36 -0
- kafka/metrics/measurable.py +27 -0
- kafka/metrics/measurable_stat.py +13 -0
- kafka/metrics/metric_config.py +33 -0
- kafka/metrics/metric_name.py +105 -0
- kafka/metrics/metrics.py +261 -0
- kafka/metrics/metrics_reporter.py +53 -0
- kafka/metrics/quota.py +41 -0
- kafka/metrics/stat.py +19 -0
- kafka/metrics/stats/__init__.py +15 -0
- kafka/metrics/stats/avg.py +24 -0
- kafka/metrics/stats/count.py +17 -0
- kafka/metrics/stats/histogram.py +99 -0
- kafka/metrics/stats/max_stat.py +17 -0
- kafka/metrics/stats/min_stat.py +19 -0
- kafka/metrics/stats/percentile.py +14 -0
- kafka/metrics/stats/percentiles.py +75 -0
- kafka/metrics/stats/rate.py +118 -0
- kafka/metrics/stats/sampled_stat.py +99 -0
- kafka/metrics/stats/sensor.py +136 -0
- kafka/metrics/stats/total.py +15 -0
- kafka/net/__init__.py +19 -0
- kafka/net/compat.py +165 -0
- kafka/net/connection.py +593 -0
- kafka/net/http_connect.py +144 -0
- kafka/net/inet.py +122 -0
- kafka/net/manager.py +451 -0
- kafka/net/metrics.py +149 -0
- kafka/net/sasl/__init__.py +32 -0
- kafka/net/sasl/abc.py +28 -0
- kafka/net/sasl/gssapi.py +95 -0
- kafka/net/sasl/msk.py +245 -0
- kafka/net/sasl/oauth.py +98 -0
- kafka/net/sasl/plain.py +42 -0
- kafka/net/sasl/scram.py +135 -0
- kafka/net/sasl/sspi.py +111 -0
- kafka/net/selector.py +644 -0
- kafka/net/socks5.py +262 -0
- kafka/net/transport.py +415 -0
- kafka/net/wakeup_notifier.py +72 -0
- kafka/partitioner/__init__.py +8 -0
- kafka/partitioner/abc.py +8 -0
- kafka/partitioner/default.py +89 -0
- kafka/partitioner/sticky.py +109 -0
- kafka/producer/__init__.py +5 -0
- kafka/producer/__main__.py +5 -0
- kafka/producer/future.py +101 -0
- kafka/producer/kafka.py +1123 -0
- kafka/producer/producer_batch.py +192 -0
- kafka/producer/record_accumulator.py +647 -0
- kafka/producer/sender.py +884 -0
- kafka/producer/transaction_manager.py +1326 -0
- kafka/protocol/__init__.py +0 -0
- kafka/protocol/admin/__init__.py +29 -0
- kafka/protocol/admin/acl.py +83 -0
- kafka/protocol/admin/acl.pyi +375 -0
- kafka/protocol/admin/client_quotas.py +14 -0
- kafka/protocol/admin/client_quotas.pyi +265 -0
- kafka/protocol/admin/cluster.py +31 -0
- kafka/protocol/admin/cluster.pyi +620 -0
- kafka/protocol/admin/configs.py +22 -0
- kafka/protocol/admin/configs.pyi +437 -0
- kafka/protocol/admin/groups.py +24 -0
- kafka/protocol/admin/groups.pyi +261 -0
- kafka/protocol/admin/topics.py +53 -0
- kafka/protocol/admin/topics.pyi +982 -0
- kafka/protocol/admin/transactions.py +18 -0
- kafka/protocol/admin/transactions.pyi +311 -0
- kafka/protocol/admin/users.py +14 -0
- kafka/protocol/admin/users.pyi +223 -0
- kafka/protocol/api_data.py +125 -0
- kafka/protocol/api_header.py +55 -0
- kafka/protocol/api_key.py +97 -0
- kafka/protocol/api_message.py +277 -0
- kafka/protocol/broker_version_data.py +246 -0
- kafka/protocol/consumer/__init__.py +13 -0
- kafka/protocol/consumer/fetch.py +16 -0
- kafka/protocol/consumer/fetch.pyi +298 -0
- kafka/protocol/consumer/group.py +38 -0
- kafka/protocol/consumer/group.pyi +824 -0
- kafka/protocol/consumer/metadata.py +30 -0
- kafka/protocol/consumer/metadata.pyi +89 -0
- kafka/protocol/consumer/offsets.py +75 -0
- kafka/protocol/consumer/offsets.pyi +288 -0
- kafka/protocol/data_container.py +166 -0
- kafka/protocol/frame.py +30 -0
- kafka/protocol/generate_stubs.py +468 -0
- kafka/protocol/metadata/__init__.py +10 -0
- kafka/protocol/metadata/api_versions.py +41 -0
- kafka/protocol/metadata/api_versions.pyi +128 -0
- kafka/protocol/metadata/find_coordinator.py +19 -0
- kafka/protocol/metadata/find_coordinator.pyi +105 -0
- kafka/protocol/metadata/metadata.py +34 -0
- kafka/protocol/metadata/metadata.pyi +160 -0
- kafka/protocol/old/__init__.py +0 -0
- kafka/protocol/old/abstract.py +17 -0
- kafka/protocol/old/add_offsets_to_txn.py +54 -0
- kafka/protocol/old/add_partitions_to_txn.py +71 -0
- kafka/protocol/old/admin.py +1086 -0
- kafka/protocol/old/api.py +205 -0
- kafka/protocol/old/api_versions.py +133 -0
- kafka/protocol/old/commit.py +355 -0
- kafka/protocol/old/consumer_protocol.py +36 -0
- kafka/protocol/old/end_txn.py +53 -0
- kafka/protocol/old/fetch.py +408 -0
- kafka/protocol/old/find_coordinator.py +72 -0
- kafka/protocol/old/group.py +451 -0
- kafka/protocol/old/init_producer_id.py +42 -0
- kafka/protocol/old/list_offsets.py +186 -0
- kafka/protocol/old/metadata.py +290 -0
- kafka/protocol/old/offset_for_leader_epoch.py +133 -0
- kafka/protocol/old/produce.py +247 -0
- kafka/protocol/old/sasl_authenticate.py +38 -0
- kafka/protocol/old/sasl_handshake.py +39 -0
- kafka/protocol/old/struct.py +87 -0
- kafka/protocol/old/txn_offset_commit.py +73 -0
- kafka/protocol/old/types.py +440 -0
- kafka/protocol/parser.py +191 -0
- kafka/protocol/producer/__init__.py +7 -0
- kafka/protocol/producer/produce.py +17 -0
- kafka/protocol/producer/produce.pyi +197 -0
- kafka/protocol/producer/transaction.py +30 -0
- kafka/protocol/producer/transaction.pyi +663 -0
- kafka/protocol/sasl.py +52 -0
- kafka/protocol/sasl.pyi +126 -0
- kafka/protocol/schemas/__init__.py +7 -0
- kafka/protocol/schemas/fields/__init__.py +7 -0
- kafka/protocol/schemas/fields/array.py +127 -0
- kafka/protocol/schemas/fields/base.py +156 -0
- kafka/protocol/schemas/fields/codecs/__init__.py +12 -0
- kafka/protocol/schemas/fields/codecs/encode_buffer.py +82 -0
- kafka/protocol/schemas/fields/codecs/tagged_fields.py +109 -0
- kafka/protocol/schemas/fields/codecs/types.py +505 -0
- kafka/protocol/schemas/fields/codegen.py +40 -0
- kafka/protocol/schemas/fields/simple.py +127 -0
- kafka/protocol/schemas/fields/struct.py +357 -0
- kafka/protocol/schemas/fields/struct_array.py +142 -0
- kafka/protocol/schemas/load_json.py +42 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnRequest.json +40 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnResponse.json +35 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnRequest.json +65 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnResponse.json +60 -0
- kafka/protocol/schemas/resources/AlterClientQuotasRequest.json +47 -0
- kafka/protocol/schemas/resources/AlterClientQuotasResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterConfigsRequest.json +43 -0
- kafka/protocol/schemas/resources/AlterConfigsResponse.json +39 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsRequest.json +42 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsResponse.json +47 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsRequest.json +41 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsRequest.json +45 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsResponse.json +35 -0
- kafka/protocol/schemas/resources/ApiVersionsRequest.json +34 -0
- kafka/protocol/schemas/resources/ApiVersionsResponse.json +79 -0
- kafka/protocol/schemas/resources/ConsumerProtocolAssignment.json +42 -0
- kafka/protocol/schemas/resources/ConsumerProtocolSubscription.json +49 -0
- kafka/protocol/schemas/resources/CreateAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/CreateAclsResponse.json +37 -0
- kafka/protocol/schemas/resources/CreatePartitionsRequest.json +47 -0
- kafka/protocol/schemas/resources/CreatePartitionsResponse.json +41 -0
- kafka/protocol/schemas/resources/CreateTopicsRequest.json +65 -0
- kafka/protocol/schemas/resources/CreateTopicsResponse.json +72 -0
- kafka/protocol/schemas/resources/DeleteAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/DeleteAclsResponse.json +59 -0
- kafka/protocol/schemas/resources/DeleteGroupsRequest.json +30 -0
- kafka/protocol/schemas/resources/DeleteGroupsResponse.json +36 -0
- kafka/protocol/schemas/resources/DeleteRecordsRequest.json +42 -0
- kafka/protocol/schemas/resources/DeleteRecordsResponse.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsRequest.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeAclsRequest.json +43 -0
- kafka/protocol/schemas/resources/DescribeAclsResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasRequest.json +37 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasResponse.json +47 -0
- kafka/protocol/schemas/resources/DescribeClusterRequest.json +35 -0
- kafka/protocol/schemas/resources/DescribeClusterResponse.json +56 -0
- kafka/protocol/schemas/resources/DescribeConfigsRequest.json +42 -0
- kafka/protocol/schemas/resources/DescribeConfigsResponse.json +69 -0
- kafka/protocol/schemas/resources/DescribeGroupsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeGroupsResponse.json +74 -0
- kafka/protocol/schemas/resources/DescribeLogDirsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeLogDirsResponse.json +65 -0
- kafka/protocol/schemas/resources/DescribeProducersRequest.json +32 -0
- kafka/protocol/schemas/resources/DescribeProducersResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeQuorumRequest.json +39 -0
- kafka/protocol/schemas/resources/DescribeQuorumResponse.json +82 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsRequest.json +40 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsResponse.json +66 -0
- kafka/protocol/schemas/resources/DescribeTransactionsRequest.json +27 -0
- kafka/protocol/schemas/resources/DescribeTransactionsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsRequest.json +30 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsResponse.json +45 -0
- kafka/protocol/schemas/resources/ElectLeadersRequest.json +41 -0
- kafka/protocol/schemas/resources/ElectLeadersResponse.json +45 -0
- kafka/protocol/schemas/resources/EndTxnRequest.json +43 -0
- kafka/protocol/schemas/resources/EndTxnResponse.json +41 -0
- kafka/protocol/schemas/resources/FetchRequest.json +125 -0
- kafka/protocol/schemas/resources/FetchResponse.json +124 -0
- kafka/protocol/schemas/resources/FindCoordinatorRequest.json +43 -0
- kafka/protocol/schemas/resources/FindCoordinatorResponse.json +58 -0
- kafka/protocol/schemas/resources/HeartbeatRequest.json +39 -0
- kafka/protocol/schemas/resources/HeartbeatResponse.json +35 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsRequest.json +44 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsResponse.json +38 -0
- kafka/protocol/schemas/resources/InitProducerIdRequest.json +50 -0
- kafka/protocol/schemas/resources/InitProducerIdResponse.json +47 -0
- kafka/protocol/schemas/resources/JoinGroupRequest.json +63 -0
- kafka/protocol/schemas/resources/JoinGroupResponse.json +69 -0
- kafka/protocol/schemas/resources/LeaveGroupRequest.json +47 -0
- kafka/protocol/schemas/resources/LeaveGroupResponse.json +47 -0
- kafka/protocol/schemas/resources/ListConfigResourcesRequest.json +31 -0
- kafka/protocol/schemas/resources/ListConfigResourcesResponse.json +37 -0
- kafka/protocol/schemas/resources/ListGroupsRequest.json +36 -0
- kafka/protocol/schemas/resources/ListGroupsResponse.json +49 -0
- kafka/protocol/schemas/resources/ListOffsetsRequest.json +72 -0
- kafka/protocol/schemas/resources/ListOffsetsResponse.json +71 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsRequest.json +34 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsResponse.json +46 -0
- kafka/protocol/schemas/resources/ListTransactionsRequest.json +40 -0
- kafka/protocol/schemas/resources/ListTransactionsResponse.json +42 -0
- kafka/protocol/schemas/resources/MetadataRequest.json +56 -0
- kafka/protocol/schemas/resources/MetadataResponse.json +101 -0
- kafka/protocol/schemas/resources/OffsetCommitRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetCommitResponse.json +71 -0
- kafka/protocol/schemas/resources/OffsetDeleteRequest.json +39 -0
- kafka/protocol/schemas/resources/OffsetDeleteResponse.json +42 -0
- kafka/protocol/schemas/resources/OffsetFetchRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetFetchResponse.json +107 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochRequest.json +52 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochResponse.json +51 -0
- kafka/protocol/schemas/resources/ProduceRequest.json +73 -0
- kafka/protocol/schemas/resources/ProduceResponse.json +96 -0
- kafka/protocol/schemas/resources/RequestHeader.json +44 -0
- kafka/protocol/schemas/resources/ResponseHeader.json +26 -0
- kafka/protocol/schemas/resources/SaslAuthenticateRequest.json +29 -0
- kafka/protocol/schemas/resources/SaslAuthenticateResponse.json +34 -0
- kafka/protocol/schemas/resources/SaslHandshakeRequest.json +31 -0
- kafka/protocol/schemas/resources/SaslHandshakeResponse.json +32 -0
- kafka/protocol/schemas/resources/SyncGroupRequest.json +56 -0
- kafka/protocol/schemas/resources/SyncGroupResponse.json +46 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitRequest.json +68 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitResponse.json +47 -0
- kafka/protocol/schemas/resources/UpdateFeaturesRequest.json +43 -0
- kafka/protocol/schemas/resources/UpdateFeaturesResponse.json +39 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersRequest.json +49 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersResponse.json +45 -0
- kafka/protocol/schemas/resources/__init__.py +0 -0
- kafka/record/__init__.py +3 -0
- kafka/record/_crc32c.py +161 -0
- kafka/record/abc.py +144 -0
- kafka/record/default_records.py +782 -0
- kafka/record/legacy_records.py +587 -0
- kafka/record/memory_records.py +255 -0
- kafka/record/util.py +135 -0
- kafka/serializer/__init__.py +4 -0
- kafka/serializer/abstract.py +20 -0
- kafka/serializer/default.py +16 -0
- kafka/serializer/json.py +17 -0
- kafka/serializer/wrapper.py +21 -0
- kafka/structs.py +69 -0
- kafka/util.py +159 -0
- kafka/vendor/__init__.py +0 -0
- kafka/version.py +1 -0
- kafka_python-3.0.0.dist-info/METADATA +319 -0
- kafka_python-3.0.0.dist-info/RECORD +373 -0
- kafka_python-3.0.0.dist-info/WHEEL +5 -0
- kafka_python-3.0.0.dist-info/entry_points.txt +2 -0
- kafka_python-3.0.0.dist-info/licenses/LICENSE +202 -0
- kafka_python-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
|
|
3
|
+
from kafka.future import Future
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WakeupNotifier:
|
|
7
|
+
"""await wakeup(timeout_secs) when either ``timeout_secs`` elapses or
|
|
8
|
+
notify() is called -- whichever first. The notifier is safe to call
|
|
9
|
+
from any thread (it routes through call_soon_threadsafe).
|
|
10
|
+
|
|
11
|
+
Level-triggered: notify() arriving while no one is awaiting is latched
|
|
12
|
+
and consumed by the next ``__call__``. This closes a lost-wakeup race
|
|
13
|
+
where a caller's state mutation (e.g. ``cluster._need_update = True``)
|
|
14
|
+
and its ``notify()`` happen between another task's pre-await state
|
|
15
|
+
check and its ``await self._wakeup(...)``. Without latching, the
|
|
16
|
+
notification arrives at the IO thread before the task has registered
|
|
17
|
+
a future to signal, and the task would sleep for the full timeout
|
|
18
|
+
despite work being ready.
|
|
19
|
+
|
|
20
|
+
Used by the metadata refresh loop to sleep on its TTL while remaining
|
|
21
|
+
interruptible by external callers (e.g. KafkaProducer / KafkaConsumer
|
|
22
|
+
invoking cluster.request_update() from another thread).
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, net):
|
|
25
|
+
self._net = weakref.proxy(net)
|
|
26
|
+
self._fut = None
|
|
27
|
+
# Set by ``_wakeup`` when no awaiter is registered; consumed by the
|
|
28
|
+
# next ``__call__``. All accesses run on the IO thread (notify
|
|
29
|
+
# routes through call_soon_threadsafe), so no lock is needed.
|
|
30
|
+
self._pending = False
|
|
31
|
+
|
|
32
|
+
def _wakeup(self):
|
|
33
|
+
if self._fut is not None and not self._fut.is_done:
|
|
34
|
+
self._fut.success(None)
|
|
35
|
+
else:
|
|
36
|
+
self._pending = True
|
|
37
|
+
|
|
38
|
+
async def __call__(self, timeout_secs=None):
|
|
39
|
+
if self._fut is not None:
|
|
40
|
+
raise RuntimeError('Concurrent access to WakeupNotifier!')
|
|
41
|
+
self._fut = Future()
|
|
42
|
+
if self._pending:
|
|
43
|
+
self._pending = False
|
|
44
|
+
self._fut.success(None)
|
|
45
|
+
if timeout_secs is not None:
|
|
46
|
+
try:
|
|
47
|
+
timer = self._net.call_later(timeout_secs, self._wakeup)
|
|
48
|
+
except ReferenceError:
|
|
49
|
+
return
|
|
50
|
+
else:
|
|
51
|
+
timer = None
|
|
52
|
+
try:
|
|
53
|
+
await self._fut
|
|
54
|
+
finally:
|
|
55
|
+
self._fut = None
|
|
56
|
+
if timer is not None and not timer.is_done:
|
|
57
|
+
try:
|
|
58
|
+
self._net.unschedule(timer)
|
|
59
|
+
except (ValueError, RuntimeError, ReferenceError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def notify(self):
|
|
63
|
+
# Always queue _wakeup on the IO thread. Skipping the queue when
|
|
64
|
+
# ``self._fut is None`` would re-introduce the lost-wakeup race:
|
|
65
|
+
# the check could pass before another thread enters ``__call__``
|
|
66
|
+
# and creates the future. Routing through the IO thread is one
|
|
67
|
+
# call_soon_threadsafe (~microseconds) and lets ``_wakeup`` decide
|
|
68
|
+
# under single-threaded semantics whether to signal or latch.
|
|
69
|
+
try:
|
|
70
|
+
self._net.call_soon_threadsafe(self._wakeup)
|
|
71
|
+
except ReferenceError:
|
|
72
|
+
pass
|
kafka/partitioner/abc.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
from .abc import Partitioner
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DefaultPartitioner(Partitioner):
|
|
7
|
+
"""Default partitioner.
|
|
8
|
+
|
|
9
|
+
Hashes key to partition using murmur2 hashing (from java client)
|
|
10
|
+
If key is None, selects partition randomly from available,
|
|
11
|
+
or from all partitions if none are currently available
|
|
12
|
+
"""
|
|
13
|
+
def partition(self, topic, key, serialized_key, value, serialized_value, cluster):
|
|
14
|
+
if topic not in cluster.topics():
|
|
15
|
+
raise ValueError("Topic %s not found in ClusterMetadata" % (topic,))
|
|
16
|
+
all_partitions = sorted(cluster.partitions_for_topic(topic))
|
|
17
|
+
available = list(cluster.available_partitions_for_topic(topic))
|
|
18
|
+
if serialized_key is not None:
|
|
19
|
+
idx = murmur2(serialized_key)
|
|
20
|
+
idx &= 0x7fffffff
|
|
21
|
+
idx %= len(all_partitions)
|
|
22
|
+
return all_partitions[idx]
|
|
23
|
+
pool = available if available else all_partitions
|
|
24
|
+
return random.choice(pool)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# https://github.com/apache/kafka/blob/0.8.2/clients/src/main/java/org/apache/kafka/common/utils/Utils.java#L244
|
|
28
|
+
def murmur2(data):
|
|
29
|
+
"""Pure-python Murmur2 implementation.
|
|
30
|
+
|
|
31
|
+
Based on java client, see org.apache.kafka.common.utils.Utils.murmur2
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
data (bytes): opaque bytes
|
|
35
|
+
|
|
36
|
+
Returns: MurmurHash2 of data
|
|
37
|
+
"""
|
|
38
|
+
length = len(data)
|
|
39
|
+
seed = 0x9747b28c
|
|
40
|
+
# 'm' and 'r' are mixing constants generated offline.
|
|
41
|
+
# They're not really 'magic', they just happen to work well.
|
|
42
|
+
m = 0x5bd1e995
|
|
43
|
+
r = 24
|
|
44
|
+
|
|
45
|
+
# Initialize the hash to a random value
|
|
46
|
+
h = seed ^ length
|
|
47
|
+
length4 = length // 4
|
|
48
|
+
|
|
49
|
+
for i in range(length4):
|
|
50
|
+
i4 = i * 4
|
|
51
|
+
k = ((data[i4 + 0] & 0xff) +
|
|
52
|
+
((data[i4 + 1] & 0xff) << 8) +
|
|
53
|
+
((data[i4 + 2] & 0xff) << 16) +
|
|
54
|
+
((data[i4 + 3] & 0xff) << 24))
|
|
55
|
+
k &= 0xffffffff
|
|
56
|
+
k *= m
|
|
57
|
+
k &= 0xffffffff
|
|
58
|
+
k ^= (k % 0x100000000) >> r # k ^= k >>> r
|
|
59
|
+
k &= 0xffffffff
|
|
60
|
+
k *= m
|
|
61
|
+
k &= 0xffffffff
|
|
62
|
+
|
|
63
|
+
h *= m
|
|
64
|
+
h &= 0xffffffff
|
|
65
|
+
h ^= k
|
|
66
|
+
h &= 0xffffffff
|
|
67
|
+
|
|
68
|
+
# Handle the last few bytes of the input array
|
|
69
|
+
extra_bytes = length % 4
|
|
70
|
+
if extra_bytes >= 3:
|
|
71
|
+
h ^= (data[(length & ~3) + 2] & 0xff) << 16
|
|
72
|
+
h &= 0xffffffff
|
|
73
|
+
if extra_bytes >= 2:
|
|
74
|
+
h ^= (data[(length & ~3) + 1] & 0xff) << 8
|
|
75
|
+
h &= 0xffffffff
|
|
76
|
+
if extra_bytes >= 1:
|
|
77
|
+
h ^= (data[length & ~3] & 0xff)
|
|
78
|
+
h &= 0xffffffff
|
|
79
|
+
h *= m
|
|
80
|
+
h &= 0xffffffff
|
|
81
|
+
|
|
82
|
+
h ^= (h % 0x100000000) >> 13 # h >>> 13;
|
|
83
|
+
h &= 0xffffffff
|
|
84
|
+
h *= m
|
|
85
|
+
h &= 0xffffffff
|
|
86
|
+
h ^= (h % 0x100000000) >> 15 # h >>> 15;
|
|
87
|
+
h &= 0xffffffff
|
|
88
|
+
|
|
89
|
+
return h
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""KIP-480 sticky partitioner.
|
|
2
|
+
|
|
3
|
+
Records with a non-None key are hashed to a partition just like
|
|
4
|
+
:class:`~kafka.partitioner.default.DefaultPartitioner`. Records with a
|
|
5
|
+
None key go to a *sticky* partition - i.e. the same partition is reused
|
|
6
|
+
for every null-key record on a topic until KafkaProducer signals that a
|
|
7
|
+
batch has been completed (via :meth:`StickyPartitioner.on_new_batch`),
|
|
8
|
+
at which point a different partition is picked.
|
|
9
|
+
|
|
10
|
+
The goal is to give the RecordAccumulator larger, denser batches for
|
|
11
|
+
null-key sends so per-batch overhead (CRC, compression, broker
|
|
12
|
+
round-trip) is amortized across more records. Java's benchmark in
|
|
13
|
+
KIP-480 reported substantial throughput/latency improvements over the
|
|
14
|
+
default-random behavior, though kafka-python is unlikely to see similar
|
|
15
|
+
improvements while predominantly CPU-bound on per-record overhead.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import random
|
|
19
|
+
import threading
|
|
20
|
+
|
|
21
|
+
from .default import DefaultPartitioner
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StickyPartitioner(DefaultPartitioner):
|
|
25
|
+
"""Partitioner that sticks null-key records to one partition per
|
|
26
|
+
topic until ``on_new_batch`` rotates it.
|
|
27
|
+
|
|
28
|
+
Thread-safety: ``_sticky`` mutations are protected by ``_lock`` so
|
|
29
|
+
concurrent ``send()`` callers can't observe a torn read-modify-write.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self._sticky = {} # topic -> partition_id
|
|
34
|
+
self._lock = threading.Lock()
|
|
35
|
+
|
|
36
|
+
def partition(self, topic, key, serialized_key, value, serialized_value, cluster):
|
|
37
|
+
"""Choose a partition for the next record.
|
|
38
|
+
|
|
39
|
+
Arguments:
|
|
40
|
+
topic (str): topic to partition on.
|
|
41
|
+
key (any): Unserialized key.
|
|
42
|
+
serialized_key (bytes or None): partitioning key.
|
|
43
|
+
value (any): Unserialized value.
|
|
44
|
+
serialized_value (bytes or None): serialized value.
|
|
45
|
+
cluster (ClusterMetadata): metadata for cluster; provides
|
|
46
|
+
all and available partitions for topic.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValueError: if topic is not in ClusterMetadata
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
int: chosen partition ID.
|
|
53
|
+
"""
|
|
54
|
+
if topic not in cluster.topics():
|
|
55
|
+
raise ValueError("Topic %s not found in ClusterMetadata" % (topic,))
|
|
56
|
+
if serialized_key is not None:
|
|
57
|
+
return super().partition(topic, key, serialized_key, value, serialized_value, cluster)
|
|
58
|
+
# Null key: reuse the sticky partition if still valid.
|
|
59
|
+
with self._lock:
|
|
60
|
+
partition = self._sticky.get(topic)
|
|
61
|
+
if partition is not None:
|
|
62
|
+
all_partitions = sorted(cluster.partitions_for_topic(topic))
|
|
63
|
+
available = list(cluster.available_partitions_for_topic(topic))
|
|
64
|
+
if available:
|
|
65
|
+
if partition in available:
|
|
66
|
+
return partition
|
|
67
|
+
elif partition in all_partitions:
|
|
68
|
+
return partition
|
|
69
|
+
# Stale (leader unavailable, topic shrunk); fall through to re-pick.
|
|
70
|
+
return self._pick_sticky_locked(topic, cluster)
|
|
71
|
+
|
|
72
|
+
def on_new_batch(self, topic, cluster, prev_partition):
|
|
73
|
+
"""Hook called by ``KafkaProducer`` on the abort-for-new-batch
|
|
74
|
+
retry path: rotate the sticky for ``topic`` so the next
|
|
75
|
+
null-key record lands on a different partition.
|
|
76
|
+
|
|
77
|
+
Stale events (where another thread already rotated us off
|
|
78
|
+
``prev_partition``) are no-ops.
|
|
79
|
+
"""
|
|
80
|
+
with self._lock:
|
|
81
|
+
if self._sticky.get(topic) != prev_partition:
|
|
82
|
+
# Another caller already rotated us; don't override.
|
|
83
|
+
return
|
|
84
|
+
self._pick_sticky_locked(topic, cluster, avoid=prev_partition)
|
|
85
|
+
|
|
86
|
+
def _pick_sticky_locked(self, topic, cluster, avoid=None):
|
|
87
|
+
"""Pick a new sticky partition for ``topic``. Must be called with
|
|
88
|
+
``self._lock`` held. Returns None when the topic is no longer in
|
|
89
|
+
cluster metadata (caller is expected to no-op in that case)."""
|
|
90
|
+
all_partitions = cluster.partitions_for_topic(topic)
|
|
91
|
+
if not all_partitions:
|
|
92
|
+
return None
|
|
93
|
+
all_partitions = sorted(all_partitions)
|
|
94
|
+
available = list(cluster.available_partitions_for_topic(topic) or ())
|
|
95
|
+
if available:
|
|
96
|
+
if len(available) == 1:
|
|
97
|
+
partition = available[0]
|
|
98
|
+
else:
|
|
99
|
+
# >= 2 available: pick uniformly, avoiding ``avoid`` if set.
|
|
100
|
+
candidates = [p for p in available if p != avoid] if avoid is not None else available
|
|
101
|
+
if not candidates:
|
|
102
|
+
candidates = available
|
|
103
|
+
partition = random.choice(candidates)
|
|
104
|
+
else:
|
|
105
|
+
# No partitions are currently available - pick from the full
|
|
106
|
+
# set without enforcing ``!= avoid``
|
|
107
|
+
partition = random.choice(all_partitions)
|
|
108
|
+
self._sticky[topic] = partition
|
|
109
|
+
return partition
|
kafka/producer/future.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
from kafka import errors as Errors
|
|
5
|
+
from kafka.future import Future
|
|
6
|
+
from kafka.util import Timer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FutureProduceResult(Future):
|
|
10
|
+
__slots__ = ('topic_partition', '_latch')
|
|
11
|
+
|
|
12
|
+
def __init__(self, topic_partition):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.topic_partition = topic_partition
|
|
15
|
+
self._latch = threading.Event()
|
|
16
|
+
|
|
17
|
+
def success(self, value):
|
|
18
|
+
ret = super().success(value)
|
|
19
|
+
self._latch.set()
|
|
20
|
+
return ret
|
|
21
|
+
|
|
22
|
+
def failure(self, error):
|
|
23
|
+
ret = super().failure(error)
|
|
24
|
+
self._latch.set()
|
|
25
|
+
return ret
|
|
26
|
+
|
|
27
|
+
def wait(self, timeout=None):
|
|
28
|
+
# wait() on python2.6 returns None instead of the flag value
|
|
29
|
+
return self._latch.wait(timeout) or self._latch.is_set()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FutureRecordMetadata(Future):
|
|
33
|
+
__slots__ = ('_produce_future', 'args')
|
|
34
|
+
def __init__(self, produce_future, batch_index, timestamp_ms, checksum, serialized_key_size, serialized_value_size, serialized_header_size):
|
|
35
|
+
super().__init__()
|
|
36
|
+
self._produce_future = produce_future
|
|
37
|
+
# packing args as a tuple is a minor speed optimization
|
|
38
|
+
self.args = (batch_index, timestamp_ms, checksum, serialized_key_size, serialized_value_size, serialized_header_size)
|
|
39
|
+
produce_future._add_cb_eb(self._produce_success, self.failure)
|
|
40
|
+
|
|
41
|
+
def _produce_success(self, result):
|
|
42
|
+
offset, produce_timestamp_ms, record_exceptions_fn = result
|
|
43
|
+
|
|
44
|
+
# Unpacking from args tuple is minor speed optimization
|
|
45
|
+
(batch_index, timestamp_ms, checksum,
|
|
46
|
+
serialized_key_size, serialized_value_size, serialized_header_size) = self.args
|
|
47
|
+
|
|
48
|
+
if record_exceptions_fn is not None:
|
|
49
|
+
self.failure(record_exceptions_fn(batch_index))
|
|
50
|
+
else:
|
|
51
|
+
# None is when Broker does not support the API (<0.10) and
|
|
52
|
+
# -1 is when the broker is configured for CREATE_TIME timestamps
|
|
53
|
+
if produce_timestamp_ms is not None and produce_timestamp_ms != -1:
|
|
54
|
+
timestamp_ms = produce_timestamp_ms
|
|
55
|
+
if offset != -1 and batch_index is not None:
|
|
56
|
+
offset += batch_index
|
|
57
|
+
tp = self._produce_future.topic_partition
|
|
58
|
+
metadata = RecordMetadata(tp[0], tp[1], tp, offset, timestamp_ms,
|
|
59
|
+
checksum, serialized_key_size,
|
|
60
|
+
serialized_value_size, serialized_header_size)
|
|
61
|
+
self.success(metadata)
|
|
62
|
+
|
|
63
|
+
def rebind(self, new_produce_future, new_batch_index):
|
|
64
|
+
"""Rebind this future to a new produce future with a new batch index.
|
|
65
|
+
|
|
66
|
+
Used when a batch is split due to MESSAGE_TOO_LARGE. The original
|
|
67
|
+
FutureRecordMetadata is rebound to the new (smaller) batch's future.
|
|
68
|
+
|
|
69
|
+
This must be called from the sender thread while the old produce_future
|
|
70
|
+
has not been completed. Any user thread blocked in get() on the old
|
|
71
|
+
produce_future's latch will be woken and will re-wait on the new one.
|
|
72
|
+
"""
|
|
73
|
+
old_produce_future = self._produce_future
|
|
74
|
+
self._produce_future = new_produce_future
|
|
75
|
+
_, timestamp_ms, checksum, sk, sv, sh = self.args
|
|
76
|
+
self.args = (new_batch_index, timestamp_ms, checksum, sk, sv, sh)
|
|
77
|
+
new_produce_future._add_cb_eb(self._produce_success, self.failure)
|
|
78
|
+
# Wake any thread blocked in get() so it re-waits on the new future.
|
|
79
|
+
# The old produce_future is never completed, so its stale callbacks
|
|
80
|
+
# (registered in __init__) will never fire.
|
|
81
|
+
old_produce_future._latch.set()
|
|
82
|
+
|
|
83
|
+
def get(self, timeout=None):
|
|
84
|
+
"""Wait for up to timeout seconds for future to complete."""
|
|
85
|
+
# Loop because rebind() may wake us from the old produce_future's
|
|
86
|
+
# latch before the record is actually done. A batch may be split
|
|
87
|
+
# multiple times, so each rebind wakes us and we re-wait on the
|
|
88
|
+
# (possibly new) _produce_future.
|
|
89
|
+
timer = Timer(timeout * 1000 if timeout is not None else None)
|
|
90
|
+
while not self.is_done and not timer.expired:
|
|
91
|
+
if not self._produce_future.wait(timer.timeout_secs):
|
|
92
|
+
raise Errors.KafkaTimeoutError(
|
|
93
|
+
"Timeout after waiting for %s secs." % (timeout,))
|
|
94
|
+
if self.failed():
|
|
95
|
+
raise self.exception # pylint: disable-msg=raising-bad-type
|
|
96
|
+
return self.value
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
RecordMetadata = collections.namedtuple(
|
|
100
|
+
'RecordMetadata', ['topic', 'partition', 'topic_partition', 'offset', 'timestamp',
|
|
101
|
+
'checksum', 'serialized_key_size', 'serialized_value_size', 'serialized_header_size'])
|