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
kafka/net/selector.py
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import copy
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
import heapq
|
|
6
|
+
import selectors
|
|
7
|
+
import socket
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
import kafka.errors as Errors
|
|
12
|
+
from kafka.future import Future
|
|
13
|
+
from kafka.version import __version__
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
def log_trace(msg, *args, **kwargs):
|
|
19
|
+
log.log(5, msg, *args, **kwargs)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
MAX_TIMEOUT = 2147483
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def yield_callback(callback):
|
|
26
|
+
yield callback()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _initialize_coro(maybe_coro):
|
|
30
|
+
if inspect.isgenerator(maybe_coro) or inspect.iscoroutine(maybe_coro):
|
|
31
|
+
return maybe_coro
|
|
32
|
+
elif inspect.isgeneratorfunction(maybe_coro) or inspect.iscoroutinefunction(maybe_coro):
|
|
33
|
+
# Defer calling until the Task actually runs to avoid
|
|
34
|
+
# "coroutine was never awaited" warnings if the Task is discarded
|
|
35
|
+
return maybe_coro
|
|
36
|
+
elif inspect.isfunction(maybe_coro) or inspect.ismethod(maybe_coro):
|
|
37
|
+
return yield_callback(maybe_coro)
|
|
38
|
+
else:
|
|
39
|
+
raise TypeError('Generator or coroutine not found: %s' % type(maybe_coro))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class KernelEvent:
|
|
43
|
+
def __init__(self, method, *args):
|
|
44
|
+
self.method = method
|
|
45
|
+
self.args = args
|
|
46
|
+
|
|
47
|
+
def __await__(self):
|
|
48
|
+
return (yield self)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Task:
|
|
52
|
+
def __init__(self, coro):
|
|
53
|
+
self._stack = (_initialize_coro(coro), None)
|
|
54
|
+
self._res = None
|
|
55
|
+
self._exc = None
|
|
56
|
+
self.scheduled_at = None
|
|
57
|
+
|
|
58
|
+
def __lt__(self, other):
|
|
59
|
+
# heapq requires the heap entries to be orderable. When two tasks
|
|
60
|
+
# share the same scheduled_at, we don't care which fires first --
|
|
61
|
+
# id() gives us a stable, unique-per-live-object tiebreaker.
|
|
62
|
+
return id(self) < id(other)
|
|
63
|
+
|
|
64
|
+
def __call__(self, arg=None):
|
|
65
|
+
if self.is_done:
|
|
66
|
+
raise RuntimeError('Task is already done!')
|
|
67
|
+
elif self._exc is not None:
|
|
68
|
+
exc, self._exc = self._exc, None
|
|
69
|
+
ret = None
|
|
70
|
+
else:
|
|
71
|
+
ret = None
|
|
72
|
+
exc = None
|
|
73
|
+
while True:
|
|
74
|
+
coro = self._stack[0]
|
|
75
|
+
if callable(coro) and not inspect.isgenerator(coro) and not inspect.iscoroutine(coro):
|
|
76
|
+
coro = coro()
|
|
77
|
+
self._stack = (coro, self._stack[1])
|
|
78
|
+
try:
|
|
79
|
+
if exc:
|
|
80
|
+
ret = coro.throw(exc)
|
|
81
|
+
else:
|
|
82
|
+
ret = coro.send(ret)
|
|
83
|
+
|
|
84
|
+
if isinstance(ret, (KernelEvent, Future)):
|
|
85
|
+
# handle in event loop
|
|
86
|
+
return ret
|
|
87
|
+
|
|
88
|
+
elif inspect.isgenerator(ret) or inspect.iscoroutine(ret) or inspect.isfunction(ret):
|
|
89
|
+
self.push_stack(ret)
|
|
90
|
+
ret = None
|
|
91
|
+
|
|
92
|
+
except StopIteration as final:
|
|
93
|
+
self._stack = self._stack[1]
|
|
94
|
+
if not self._stack:
|
|
95
|
+
# we're done, back to event loop
|
|
96
|
+
self._res = final.value
|
|
97
|
+
raise
|
|
98
|
+
else:
|
|
99
|
+
ret = final.value
|
|
100
|
+
exc = None
|
|
101
|
+
|
|
102
|
+
except BaseException as e:
|
|
103
|
+
self._stack = self._stack[1]
|
|
104
|
+
if not self._stack:
|
|
105
|
+
self._exc = e
|
|
106
|
+
raise
|
|
107
|
+
else:
|
|
108
|
+
ret = None
|
|
109
|
+
exc = e
|
|
110
|
+
else:
|
|
111
|
+
exc = None
|
|
112
|
+
|
|
113
|
+
def push_stack(self, coro):
|
|
114
|
+
self._stack = (_initialize_coro(coro), self._stack)
|
|
115
|
+
|
|
116
|
+
def inject_exc(self, exc):
|
|
117
|
+
if self.is_done:
|
|
118
|
+
raise RuntimeError('Task is already done!')
|
|
119
|
+
elif not isinstance(exc, BaseException):
|
|
120
|
+
raise TypeError('exc is not a BaseException')
|
|
121
|
+
elif self._exc is not None:
|
|
122
|
+
raise RuntimeError('Task exception is already set!')
|
|
123
|
+
self._exc = exc
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def is_done(self):
|
|
127
|
+
return self._stack is None
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def result(self):
|
|
131
|
+
if not self.is_done:
|
|
132
|
+
raise RuntimeError('Task not complete!')
|
|
133
|
+
return self._res
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def exception(self):
|
|
137
|
+
if not self.is_done:
|
|
138
|
+
raise RuntimeError('Task not complete!')
|
|
139
|
+
return self._exc
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class NetworkSelector:
|
|
143
|
+
DEFAULT_CONFIG = {
|
|
144
|
+
'client_id': 'kafka-python-' + __version__,
|
|
145
|
+
'selector': selectors.DefaultSelector,
|
|
146
|
+
# Warn (or, in debug mode, raise) when a single ready-task step takes
|
|
147
|
+
# longer than this many seconds. A coroutine that hits this threshold
|
|
148
|
+
# is blocking the event loop -- common cause is a tight sync loop
|
|
149
|
+
# over a synchronously-raising await (see cluster._refresh_loop hang
|
|
150
|
+
# where RuntimeError from a closed manager was caught and retried).
|
|
151
|
+
# Mirrors asyncio's loop.slow_callback_duration. Set to 0 to disable.
|
|
152
|
+
'slow_task_threshold_secs': 0.1,
|
|
153
|
+
# When True, raise RuntimeError on slow tasks instead of just warning.
|
|
154
|
+
# Useful in tests so livelocks fail loudly.
|
|
155
|
+
'raise_on_slow_task': False,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
def __init__(self, **configs):
|
|
159
|
+
self.config = copy.copy(self.DEFAULT_CONFIG)
|
|
160
|
+
for key in self.config:
|
|
161
|
+
if key in configs:
|
|
162
|
+
self.config[key] = configs[key]
|
|
163
|
+
|
|
164
|
+
# Used by poll() as both a mutex (cross-thread concurrent-entry guard)
|
|
165
|
+
# and the in-loop flag. acquire(blocking=False) doubles as the
|
|
166
|
+
# "is anyone in poll() right now?" check. Held only across poll()'s
|
|
167
|
+
# body; never held by anything else.
|
|
168
|
+
# _poll_owner tracks which thread holds the lock so we can produce
|
|
169
|
+
# an accurate diagnostic (recursive vs concurrent) on contention.
|
|
170
|
+
self._poll_lock = threading.Lock()
|
|
171
|
+
self._poll_owner = None
|
|
172
|
+
self._closed = False
|
|
173
|
+
self._exception = None
|
|
174
|
+
self._stop = False
|
|
175
|
+
self._selector = self.config['selector']()
|
|
176
|
+
self._scheduled = [] # managed by heapq; Task.__lt__ tiebreaks ties on scheduled_at
|
|
177
|
+
self._ready = collections.deque()
|
|
178
|
+
# Strong refs to every Task that hasn't completed yet. Without this,
|
|
179
|
+
# a Task suspended on an externally-unreachable awaitable (e.g. a
|
|
180
|
+
# Future created and awaited inside the Task's own coroutine) forms
|
|
181
|
+
# an orphan cycle and is subject to gc collection. Keeping every
|
|
182
|
+
# pending Task rooted on the selector itself prevents the cycle from
|
|
183
|
+
# ever being garbage-eligible. Tasks are removed when they raise
|
|
184
|
+
# StopIteration (normal completion) or BaseException (raised) inside
|
|
185
|
+
# _poll_once. This mirrors asyncio's loop._tasks weakset.
|
|
186
|
+
self._pending_tasks = set()
|
|
187
|
+
self._current = None
|
|
188
|
+
self._wakeup_r, self._wakeup_w = socket.socketpair()
|
|
189
|
+
self._wakeup_r.setblocking(False)
|
|
190
|
+
self._wakeup_w.setblocking(False)
|
|
191
|
+
self._selector.register(self._wakeup_r, selectors.EVENT_READ, (None, None))
|
|
192
|
+
self._io_thread = None
|
|
193
|
+
self._pending_waiters = {} # event -> state dict, for pending run() waiters
|
|
194
|
+
self._pending_waiters_lock = threading.Lock()
|
|
195
|
+
|
|
196
|
+
def __str__(self):
|
|
197
|
+
return '<NetworkSelector ready=%d scheduled=%d waiting=%d>' % (len(self._ready), len(self._scheduled), len(self._selector.get_map()))
|
|
198
|
+
|
|
199
|
+
def run_forever(self):
|
|
200
|
+
"""Run the event loop until stop() is called. Intended to be driven by
|
|
201
|
+
a dedicated IO thread. Wake-ups from other threads must go through
|
|
202
|
+
call_soon_threadsafe() so the select() loop returns promptly."""
|
|
203
|
+
self._stop = False
|
|
204
|
+
log.info('IO loop starting (client_id=%s)', self.config['client_id'])
|
|
205
|
+
try:
|
|
206
|
+
while not self._stop:
|
|
207
|
+
self._poll_once()
|
|
208
|
+
self.drain()
|
|
209
|
+
except BaseException as exc:
|
|
210
|
+
log.exception('IO loop crashed (client_id=%s)', self.config['client_id'])
|
|
211
|
+
self._exception = exc
|
|
212
|
+
self._fail_pending_waiters(exc)
|
|
213
|
+
raise
|
|
214
|
+
else:
|
|
215
|
+
log.info('IO loop exited cleanly (client_id=%s, stop=%s)',
|
|
216
|
+
self.config['client_id'], self._stop)
|
|
217
|
+
|
|
218
|
+
def start(self):
|
|
219
|
+
"""Spawn a daemon IO thread that owns the event loop. Idempotent."""
|
|
220
|
+
if self._io_thread is not None:
|
|
221
|
+
return
|
|
222
|
+
t = threading.Thread(target=self.run_forever,
|
|
223
|
+
name='kafka-io-%s' % self.config['client_id'],
|
|
224
|
+
daemon=True)
|
|
225
|
+
self._io_thread = t
|
|
226
|
+
t.start()
|
|
227
|
+
|
|
228
|
+
def stop(self, timeout_ms=None):
|
|
229
|
+
"""Signal run_forever() to exit and join the IO thread.
|
|
230
|
+
|
|
231
|
+
Blocks the caller until the IO thread terminates (or ``timeout_ms``
|
|
232
|
+
elapses). Pending cross-thread ``run()`` waiters are failed with
|
|
233
|
+
KafkaConnectionError. Idempotent; safe to call from any thread
|
|
234
|
+
other than the IO thread itself.
|
|
235
|
+
"""
|
|
236
|
+
if self._stop or self._io_thread is None:
|
|
237
|
+
return
|
|
238
|
+
self._stop = True
|
|
239
|
+
self.wakeup()
|
|
240
|
+
self._io_thread.join(timeout_ms / 1000 if timeout_ms is not None else None)
|
|
241
|
+
self._io_thread = None
|
|
242
|
+
self._fail_pending_waiters(Errors.KafkaConnectionError('Event loop stopped'))
|
|
243
|
+
|
|
244
|
+
def _fail_pending_waiters(self, exc):
|
|
245
|
+
with self._pending_waiters_lock:
|
|
246
|
+
waiters = list(self._pending_waiters.items())
|
|
247
|
+
self._pending_waiters.clear()
|
|
248
|
+
for event, state in waiters:
|
|
249
|
+
state['exception'] = exc
|
|
250
|
+
event.set()
|
|
251
|
+
|
|
252
|
+
def run(self, coro, *args):
|
|
253
|
+
"""Schedules coro on the event loop, blocks until complete, returns value or raises.
|
|
254
|
+
|
|
255
|
+
If an IO thread is running (via start()), the caller thread blocks on
|
|
256
|
+
a cross-thread Event while the coroutine runs on the IO thread. Safe
|
|
257
|
+
to call concurrently from multiple caller threads.
|
|
258
|
+
|
|
259
|
+
If no IO thread is running, falls back to driving the loop on the
|
|
260
|
+
caller thread (legacy behavior).
|
|
261
|
+
"""
|
|
262
|
+
if self._closed:
|
|
263
|
+
raise RuntimeError('NetworkSelector closed!')
|
|
264
|
+
if self._io_thread is None:
|
|
265
|
+
future = self.call_soon_with_future(coro, *args)
|
|
266
|
+
self.poll(future=future)
|
|
267
|
+
if future.exception is not None:
|
|
268
|
+
raise future.exception
|
|
269
|
+
return future.value
|
|
270
|
+
elif threading.current_thread() is self._io_thread:
|
|
271
|
+
raise RuntimeError(
|
|
272
|
+
"Cannot block on net.run() from the IO thread itself. "
|
|
273
|
+
"This typically happens when a synchronous rebalance listener "
|
|
274
|
+
"(or another IO-thread callback) calls a blocking consumer/admin API. "
|
|
275
|
+
"Use AsyncConsumerRebalanceListener and await the async variant, "
|
|
276
|
+
"or move the blocking work to a worker thread.")
|
|
277
|
+
elif self._exception:
|
|
278
|
+
raise self._exception from None
|
|
279
|
+
|
|
280
|
+
event = threading.Event()
|
|
281
|
+
state = {'value': None, 'exception': None}
|
|
282
|
+
async def waiter():
|
|
283
|
+
try:
|
|
284
|
+
state['value'] = await self._invoke(coro, *args)
|
|
285
|
+
except BaseException as exc:
|
|
286
|
+
state['exception'] = exc
|
|
287
|
+
finally:
|
|
288
|
+
with self._pending_waiters_lock:
|
|
289
|
+
self._pending_waiters.pop(event, None)
|
|
290
|
+
event.set()
|
|
291
|
+
with self._pending_waiters_lock:
|
|
292
|
+
self._pending_waiters[event] = state
|
|
293
|
+
self.call_soon_threadsafe(waiter)
|
|
294
|
+
event.wait()
|
|
295
|
+
if state['exception'] is not None:
|
|
296
|
+
raise state['exception'] # pylint: disable=E0702
|
|
297
|
+
return state['value']
|
|
298
|
+
|
|
299
|
+
def drain(self, scheduled=False):
|
|
300
|
+
while self._ready or (scheduled and self._scheduled):
|
|
301
|
+
self._poll_once()
|
|
302
|
+
|
|
303
|
+
def call_at(self, when, task):
|
|
304
|
+
if self._closed:
|
|
305
|
+
raise RuntimeError('NetworkSelector closed!')
|
|
306
|
+
if not isinstance(task, Task):
|
|
307
|
+
task = Task(task)
|
|
308
|
+
task.scheduled_at = when
|
|
309
|
+
heapq.heappush(self._scheduled, (when, task))
|
|
310
|
+
self._pending_tasks.add(task)
|
|
311
|
+
return task
|
|
312
|
+
|
|
313
|
+
def call_later(self, delay, task):
|
|
314
|
+
if not isinstance(task, Task):
|
|
315
|
+
task = Task(task)
|
|
316
|
+
self.call_at(time.monotonic() + delay, task)
|
|
317
|
+
return task
|
|
318
|
+
|
|
319
|
+
def call_soon(self, task):
|
|
320
|
+
if not isinstance(task, Task):
|
|
321
|
+
task = Task(task)
|
|
322
|
+
self._ready.append(task)
|
|
323
|
+
self._pending_tasks.add(task)
|
|
324
|
+
return task
|
|
325
|
+
|
|
326
|
+
def call_soon_threadsafe(self, callback):
|
|
327
|
+
if self._exception:
|
|
328
|
+
raise self._exception from None
|
|
329
|
+
elif self._closed:
|
|
330
|
+
raise RuntimeError('NetworkSelector closed!')
|
|
331
|
+
task = self.call_soon(callback)
|
|
332
|
+
self.wakeup()
|
|
333
|
+
return task
|
|
334
|
+
|
|
335
|
+
def call_soon_with_future(self, coro, *args):
|
|
336
|
+
if hasattr(coro, '__await__'):
|
|
337
|
+
if args:
|
|
338
|
+
raise ValueError('initiated coroutine does not accept args')
|
|
339
|
+
future = Future()
|
|
340
|
+
async def wrapper():
|
|
341
|
+
try:
|
|
342
|
+
future.success(await self._invoke(coro, *args))
|
|
343
|
+
except BaseException as exc:
|
|
344
|
+
future.failure(exc)
|
|
345
|
+
self.call_soon_threadsafe(wrapper)
|
|
346
|
+
return future
|
|
347
|
+
|
|
348
|
+
async def _invoke(self, coro, *args):
|
|
349
|
+
"""Invoke coro/awaitable/function and fully resolve the result.
|
|
350
|
+
|
|
351
|
+
If the result is itself a Future (e.g. send() returning an unresolved
|
|
352
|
+
Future), it is awaited so callers receive the resolved value.
|
|
353
|
+
"""
|
|
354
|
+
if inspect.iscoroutinefunction(coro):
|
|
355
|
+
result = await coro(*args)
|
|
356
|
+
elif hasattr(coro, '__await__'):
|
|
357
|
+
result = await coro
|
|
358
|
+
else:
|
|
359
|
+
result = coro(*args)
|
|
360
|
+
if inspect.iscoroutine(result) or hasattr(result, '__await__'):
|
|
361
|
+
result = await result
|
|
362
|
+
while isinstance(result, Future):
|
|
363
|
+
result = await result
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
def unschedule(self, task):
|
|
367
|
+
if task.scheduled_at is not None:
|
|
368
|
+
self._scheduled.remove((task.scheduled_at, task))
|
|
369
|
+
task.scheduled_at = None
|
|
370
|
+
|
|
371
|
+
def reschedule(self, when, task):
|
|
372
|
+
self.unschedule(task)
|
|
373
|
+
self.call_at(when, task)
|
|
374
|
+
return task
|
|
375
|
+
|
|
376
|
+
def sleep(self, delay):
|
|
377
|
+
return KernelEvent('_sleep', delay)
|
|
378
|
+
|
|
379
|
+
def _sleep(self, delay):
|
|
380
|
+
self.call_later(delay, self._current)
|
|
381
|
+
|
|
382
|
+
def wait_write(self, fileobj, timeout_at=None):
|
|
383
|
+
return KernelEvent('_wait_write', fileobj, timeout_at)
|
|
384
|
+
|
|
385
|
+
def _wait_write(self, fileobj, timeout_at=None):
|
|
386
|
+
self._wait_io(fileobj, selectors.EVENT_WRITE, timeout_at)
|
|
387
|
+
|
|
388
|
+
def wait_read(self, fileobj, timeout_at=None):
|
|
389
|
+
return KernelEvent('_wait_read', fileobj, timeout_at)
|
|
390
|
+
|
|
391
|
+
def _wait_read(self, fileobj, timeout_at=None):
|
|
392
|
+
self._wait_io(fileobj, selectors.EVENT_READ, timeout_at)
|
|
393
|
+
|
|
394
|
+
def _wait_io(self, fileobj, event, timeout_at):
|
|
395
|
+
suspended = self._current
|
|
396
|
+
self.register_event(fileobj, event, suspended)
|
|
397
|
+
if timeout_at is None or self._closed:
|
|
398
|
+
suspended.push_stack(lambda: self.unregister_event(fileobj, event))
|
|
399
|
+
return
|
|
400
|
+
|
|
401
|
+
state = {'fired': False, 'timer': None}
|
|
402
|
+
|
|
403
|
+
def on_resume():
|
|
404
|
+
state['fired'] = True
|
|
405
|
+
if state['timer'] is not None:
|
|
406
|
+
try:
|
|
407
|
+
self.unschedule(state['timer'])
|
|
408
|
+
except ValueError:
|
|
409
|
+
pass
|
|
410
|
+
self.unregister_event(fileobj, event)
|
|
411
|
+
|
|
412
|
+
def on_timeout():
|
|
413
|
+
if state['fired']:
|
|
414
|
+
return
|
|
415
|
+
state['fired'] = True
|
|
416
|
+
self.unregister_event(fileobj, event)
|
|
417
|
+
suspended.inject_exc(Errors.KafkaTimeoutError('I/O wait timed out'))
|
|
418
|
+
self._ready.append(suspended)
|
|
419
|
+
|
|
420
|
+
suspended.push_stack(on_resume)
|
|
421
|
+
state['timer'] = self.call_at(timeout_at, on_timeout)
|
|
422
|
+
|
|
423
|
+
def _schedule_tasks(self):
|
|
424
|
+
while self._scheduled and self._scheduled[0][0] <= time.monotonic():
|
|
425
|
+
_, task = heapq.heappop(self._scheduled)
|
|
426
|
+
task.scheduled_at = None
|
|
427
|
+
self._ready.append(task)
|
|
428
|
+
|
|
429
|
+
def _next_scheduled_timeout(self, now):
|
|
430
|
+
try:
|
|
431
|
+
return self._scheduled[0][0] - now
|
|
432
|
+
except IndexError:
|
|
433
|
+
return None
|
|
434
|
+
|
|
435
|
+
# Note: Windows select works w/ sockets only
|
|
436
|
+
def register_event(self, fileobj, event, task):
|
|
437
|
+
log_trace('net.register_event: %s, %s, %s', fileobj, event, task)
|
|
438
|
+
if not isinstance(task, Task):
|
|
439
|
+
task = Task(task)
|
|
440
|
+
try:
|
|
441
|
+
key = self._selector.get_key(fileobj)
|
|
442
|
+
reader, writer = key.data
|
|
443
|
+
if event == selectors.EVENT_READ and reader:
|
|
444
|
+
raise RuntimeError("EVENT_READ already registered for fileobj %s by %s (new: %s)" % (fileobj, reader, task))
|
|
445
|
+
if event == selectors.EVENT_WRITE and writer:
|
|
446
|
+
raise RuntimeError("EVENT_WRITE already registered for fileobj %s by %s (new: %s)" % (fileobj, writer, task))
|
|
447
|
+
self._selector.modify(fileobj, key.events | event, (task, writer) if event == selectors.EVENT_READ else (reader, task))
|
|
448
|
+
except KeyError:
|
|
449
|
+
self._selector.register(fileobj, event, (task, None) if event == selectors.EVENT_READ else (None, task))
|
|
450
|
+
|
|
451
|
+
def unregister_event(self, fileobj, event):
|
|
452
|
+
log_trace('net.unregister_event: %s, %s', fileobj, event)
|
|
453
|
+
try:
|
|
454
|
+
key = self._selector.get_key(fileobj)
|
|
455
|
+
reader, writer = key.data
|
|
456
|
+
events = key.events & ~event
|
|
457
|
+
if not events:
|
|
458
|
+
self._selector.unregister(fileobj)
|
|
459
|
+
else:
|
|
460
|
+
self._selector.modify(fileobj, events, (None, writer) if event == selectors.EVENT_READ else (reader, None))
|
|
461
|
+
except KeyError:
|
|
462
|
+
pass
|
|
463
|
+
|
|
464
|
+
def add_reader(self, fileobj, task):
|
|
465
|
+
self.register_event(fileobj, selectors.EVENT_READ, task)
|
|
466
|
+
|
|
467
|
+
def remove_reader(self, fileobj):
|
|
468
|
+
self.unregister_event(fileobj, selectors.EVENT_READ)
|
|
469
|
+
|
|
470
|
+
def add_writer(self, fileobj, task):
|
|
471
|
+
self.register_event(fileobj, selectors.EVENT_WRITE, task)
|
|
472
|
+
|
|
473
|
+
def remove_writer(self, fileobj):
|
|
474
|
+
self.unregister_event(fileobj, selectors.EVENT_WRITE)
|
|
475
|
+
|
|
476
|
+
def poll(self, timeout_ms=None, future=None):
|
|
477
|
+
log_trace('poll: enter')
|
|
478
|
+
start_at = time.monotonic()
|
|
479
|
+
inner_timeout = timeout_ms / 1000 if timeout_ms is not None else None
|
|
480
|
+
if future is not None and future.is_done:
|
|
481
|
+
inner_timeout = 0
|
|
482
|
+
while True:
|
|
483
|
+
self._poll_once(inner_timeout)
|
|
484
|
+
if future is None or future.is_done:
|
|
485
|
+
break
|
|
486
|
+
elif timeout_ms is not None:
|
|
487
|
+
inner_timeout = (timeout_ms / 1000) - (time.monotonic() - start_at)
|
|
488
|
+
if inner_timeout <= 0:
|
|
489
|
+
break
|
|
490
|
+
log_trace('poll: exit')
|
|
491
|
+
|
|
492
|
+
def _poll_once(self, timeout=None):
|
|
493
|
+
log_trace('_poll_once: enter')
|
|
494
|
+
if not self._poll_lock.acquire(blocking=False):
|
|
495
|
+
# Lock contended. Distinguish recursive (this thread is already
|
|
496
|
+
# in poll, e.g. via a task callback) from concurrent (a different
|
|
497
|
+
# thread is in poll). Same-thread reentry of a non-RLock fails
|
|
498
|
+
# the same way as cross-thread contention.
|
|
499
|
+
if self._poll_owner is threading.current_thread():
|
|
500
|
+
raise RuntimeError('Recursive access to net.poll!')
|
|
501
|
+
raise RuntimeError('Concurrent access to net.poll!')
|
|
502
|
+
self._poll_owner = threading.current_thread()
|
|
503
|
+
try:
|
|
504
|
+
if self._ready:
|
|
505
|
+
timeout = 0
|
|
506
|
+
else:
|
|
507
|
+
scheduled_timeout = self._next_scheduled_timeout(time.monotonic())
|
|
508
|
+
if scheduled_timeout is not None:
|
|
509
|
+
timeout = min(timeout, scheduled_timeout) if timeout is not None else scheduled_timeout
|
|
510
|
+
if timeout is not None:
|
|
511
|
+
if timeout > MAX_TIMEOUT:
|
|
512
|
+
timeout = MAX_TIMEOUT
|
|
513
|
+
elif timeout < 0:
|
|
514
|
+
timeout = 0
|
|
515
|
+
elif not self._selector.get_map():
|
|
516
|
+
timeout = 0
|
|
517
|
+
|
|
518
|
+
ready_events = self._selector.select(timeout)
|
|
519
|
+
log_trace('_poll_once: %d ready_events', len(ready_events))
|
|
520
|
+
self._process_events(ready_events)
|
|
521
|
+
self._schedule_tasks()
|
|
522
|
+
|
|
523
|
+
threshold = self.config['slow_task_threshold_secs']
|
|
524
|
+
n = len(self._ready)
|
|
525
|
+
for i in range(n):
|
|
526
|
+
self._current = self._ready.popleft()
|
|
527
|
+
step_start = time.monotonic() if threshold else None
|
|
528
|
+
try:
|
|
529
|
+
log_trace('Calling task %s', self._current)
|
|
530
|
+
inject = self._current._exc
|
|
531
|
+
if inject is not None:
|
|
532
|
+
self._current._exc = None
|
|
533
|
+
event = self._current(inject)
|
|
534
|
+
else:
|
|
535
|
+
event = self._current()
|
|
536
|
+
|
|
537
|
+
except StopIteration:
|
|
538
|
+
# Task ran to completion. Drop the strong ref so the Task
|
|
539
|
+
# (and its coroutine, frames, locals) is now collectable.
|
|
540
|
+
self._pending_tasks.discard(self._current)
|
|
541
|
+
|
|
542
|
+
except BaseException as e:
|
|
543
|
+
log.exception(e)
|
|
544
|
+
# Same as StopIteration -- task is done either way.
|
|
545
|
+
self._pending_tasks.discard(self._current)
|
|
546
|
+
|
|
547
|
+
else:
|
|
548
|
+
if isinstance(event, KernelEvent):
|
|
549
|
+
log_trace('kernel event %s', event.method)
|
|
550
|
+
getattr(self, event.method)(*event.args)
|
|
551
|
+
elif isinstance(event, Future):
|
|
552
|
+
event.add_both(lambda _, task=self._current: self.call_soon(task))
|
|
553
|
+
else:
|
|
554
|
+
raise RuntimeError('Unhandled event type: %s' % event)
|
|
555
|
+
|
|
556
|
+
if threshold:
|
|
557
|
+
elapsed = time.monotonic() - step_start
|
|
558
|
+
if elapsed > threshold:
|
|
559
|
+
msg = (
|
|
560
|
+
'Task %r ran for %.3fs (>%.3fs threshold). It is '
|
|
561
|
+
'blocking the event loop -- likely a tight sync loop '
|
|
562
|
+
'inside a coroutine. Other pollers will time out.'
|
|
563
|
+
% (self._current, elapsed, threshold))
|
|
564
|
+
if self.config['raise_on_slow_task']:
|
|
565
|
+
self._current = None
|
|
566
|
+
raise RuntimeError(msg)
|
|
567
|
+
log.warning(msg)
|
|
568
|
+
|
|
569
|
+
self._current = None
|
|
570
|
+
|
|
571
|
+
finally:
|
|
572
|
+
self._poll_owner = None
|
|
573
|
+
self._poll_lock.release()
|
|
574
|
+
log_trace('_poll_once: exit')
|
|
575
|
+
|
|
576
|
+
def wakeup(self):
|
|
577
|
+
try:
|
|
578
|
+
self._wakeup_w.send(b'\x00')
|
|
579
|
+
except (BlockingIOError, OSError):
|
|
580
|
+
pass
|
|
581
|
+
|
|
582
|
+
def _rebuild_wakeup_socketpair(self):
|
|
583
|
+
for s in (self._wakeup_r, self._wakeup_w):
|
|
584
|
+
try:
|
|
585
|
+
self._selector.unregister(s)
|
|
586
|
+
except Exception:
|
|
587
|
+
pass
|
|
588
|
+
try:
|
|
589
|
+
s.close()
|
|
590
|
+
except Exception:
|
|
591
|
+
pass
|
|
592
|
+
self._wakeup_r, self._wakeup_w = socket.socketpair()
|
|
593
|
+
self._wakeup_r.setblocking(False)
|
|
594
|
+
self._wakeup_w.setblocking(False)
|
|
595
|
+
self._selector.register(self._wakeup_r, selectors.EVENT_READ, (None, None))
|
|
596
|
+
|
|
597
|
+
def close(self):
|
|
598
|
+
if self._closed:
|
|
599
|
+
return
|
|
600
|
+
self._closed = True
|
|
601
|
+
if self._io_thread is not None:
|
|
602
|
+
self.stop()
|
|
603
|
+
self.drain()
|
|
604
|
+
for s in (self._wakeup_r, self._wakeup_w):
|
|
605
|
+
try:
|
|
606
|
+
self._selector.unregister(s)
|
|
607
|
+
except Exception:
|
|
608
|
+
pass
|
|
609
|
+
try:
|
|
610
|
+
s.close()
|
|
611
|
+
except Exception:
|
|
612
|
+
pass
|
|
613
|
+
self._selector.close()
|
|
614
|
+
|
|
615
|
+
def _process_events(self, event_list):
|
|
616
|
+
for key, events in event_list:
|
|
617
|
+
reader, writer = key.data
|
|
618
|
+
fileobj = key.fileobj
|
|
619
|
+
|
|
620
|
+
# Drain wakeup socketpair
|
|
621
|
+
if fileobj is self._wakeup_r:
|
|
622
|
+
try:
|
|
623
|
+
data = self._wakeup_r.recv(1024)
|
|
624
|
+
if not data:
|
|
625
|
+
log.warning('Wakeup socket returned empty. Rebuilding.')
|
|
626
|
+
self._rebuild_wakeup_socketpair()
|
|
627
|
+
except BlockingIOError:
|
|
628
|
+
pass
|
|
629
|
+
except Exception as e:
|
|
630
|
+
log.warning('Error reading wakeup socket: %s. Rebuilding.', e)
|
|
631
|
+
self._rebuild_wakeup_socketpair()
|
|
632
|
+
continue
|
|
633
|
+
|
|
634
|
+
if events & selectors.EVENT_WRITE:
|
|
635
|
+
if writer is not None:
|
|
636
|
+
self._ready.append(writer)
|
|
637
|
+
else:
|
|
638
|
+
log.warning("Selector got WRITE event without writer...")
|
|
639
|
+
|
|
640
|
+
if events & selectors.EVENT_READ:
|
|
641
|
+
if reader is not None:
|
|
642
|
+
self._ready.append(reader)
|
|
643
|
+
else:
|
|
644
|
+
log.warning("Selector got READ event without reader...")
|