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.
Files changed (373) hide show
  1. kafka/__init__.py +34 -0
  2. kafka/__main__.py +5 -0
  3. kafka/admin/__init__.py +29 -0
  4. kafka/admin/__main__.py +5 -0
  5. kafka/admin/_acls.py +355 -0
  6. kafka/admin/_cluster.py +359 -0
  7. kafka/admin/_configs.py +479 -0
  8. kafka/admin/_groups.py +754 -0
  9. kafka/admin/_partitions.py +595 -0
  10. kafka/admin/_topics.py +281 -0
  11. kafka/admin/_transactions.py +450 -0
  12. kafka/admin/_users.py +194 -0
  13. kafka/admin/client.py +373 -0
  14. kafka/benchmarks/__init__.py +0 -0
  15. kafka/benchmarks/consumer_performance.py +138 -0
  16. kafka/benchmarks/load_example.py +109 -0
  17. kafka/benchmarks/producer_encode_path.py +201 -0
  18. kafka/benchmarks/producer_performance.py +161 -0
  19. kafka/benchmarks/profile_protocol.py +138 -0
  20. kafka/benchmarks/protocol_old_vs_new.py +447 -0
  21. kafka/benchmarks/record_batch_compose.py +77 -0
  22. kafka/benchmarks/record_batch_read.py +82 -0
  23. kafka/benchmarks/varint_speed.py +426 -0
  24. kafka/cli/__init__.py +36 -0
  25. kafka/cli/admin/__init__.py +117 -0
  26. kafka/cli/admin/acls/__init__.py +9 -0
  27. kafka/cli/admin/acls/common.py +76 -0
  28. kafka/cli/admin/acls/create.py +19 -0
  29. kafka/cli/admin/acls/delete.py +23 -0
  30. kafka/cli/admin/acls/describe.py +16 -0
  31. kafka/cli/admin/cluster/__init__.py +14 -0
  32. kafka/cli/admin/cluster/describe.py +11 -0
  33. kafka/cli/admin/cluster/describe_quorum.py +11 -0
  34. kafka/cli/admin/cluster/features.py +52 -0
  35. kafka/cli/admin/cluster/log_dirs.py +43 -0
  36. kafka/cli/admin/cluster/versions.py +33 -0
  37. kafka/cli/admin/configs/__init__.py +10 -0
  38. kafka/cli/admin/configs/alter.py +43 -0
  39. kafka/cli/admin/configs/common.py +17 -0
  40. kafka/cli/admin/configs/describe.py +30 -0
  41. kafka/cli/admin/configs/list.py +16 -0
  42. kafka/cli/admin/configs/reset.py +20 -0
  43. kafka/cli/admin/groups/__init__.py +16 -0
  44. kafka/cli/admin/groups/alter_offsets.py +30 -0
  45. kafka/cli/admin/groups/delete.py +11 -0
  46. kafka/cli/admin/groups/delete_offsets.py +29 -0
  47. kafka/cli/admin/groups/describe.py +11 -0
  48. kafka/cli/admin/groups/list.py +28 -0
  49. kafka/cli/admin/groups/list_offsets.py +29 -0
  50. kafka/cli/admin/groups/remove_members.py +40 -0
  51. kafka/cli/admin/groups/reset_offsets.py +139 -0
  52. kafka/cli/admin/partitions/__init__.py +21 -0
  53. kafka/cli/admin/partitions/alter_reassignments.py +37 -0
  54. kafka/cli/admin/partitions/create.py +27 -0
  55. kafka/cli/admin/partitions/delete_records.py +31 -0
  56. kafka/cli/admin/partitions/describe.py +36 -0
  57. kafka/cli/admin/partitions/elect_leaders.py +53 -0
  58. kafka/cli/admin/partitions/list_offsets.py +88 -0
  59. kafka/cli/admin/partitions/list_reassignments.py +35 -0
  60. kafka/cli/admin/topics/__init__.py +10 -0
  61. kafka/cli/admin/topics/create.py +13 -0
  62. kafka/cli/admin/topics/delete.py +19 -0
  63. kafka/cli/admin/topics/describe.py +18 -0
  64. kafka/cli/admin/topics/list.py +11 -0
  65. kafka/cli/admin/transactions/__init__.py +17 -0
  66. kafka/cli/admin/transactions/abort.py +38 -0
  67. kafka/cli/admin/transactions/describe.py +24 -0
  68. kafka/cli/admin/transactions/describe_producers.py +29 -0
  69. kafka/cli/admin/transactions/find_hanging.py +26 -0
  70. kafka/cli/admin/transactions/list.py +37 -0
  71. kafka/cli/admin/users/__init__.py +8 -0
  72. kafka/cli/admin/users/alter_user_scram_credentials.py +34 -0
  73. kafka/cli/admin/users/describe_user_scram_credentials.py +15 -0
  74. kafka/cli/common.py +95 -0
  75. kafka/cli/consumer/__init__.py +63 -0
  76. kafka/cli/producer/__init__.py +57 -0
  77. kafka/cluster.py +824 -0
  78. kafka/codec.py +325 -0
  79. kafka/consumer/__init__.py +5 -0
  80. kafka/consumer/__main__.py +5 -0
  81. kafka/consumer/fetcher.py +2012 -0
  82. kafka/consumer/group.py +1347 -0
  83. kafka/consumer/subscription_state.py +897 -0
  84. kafka/coordinator/__init__.py +0 -0
  85. kafka/coordinator/assignors/__init__.py +0 -0
  86. kafka/coordinator/assignors/abstract.py +90 -0
  87. kafka/coordinator/assignors/cooperative_sticky.py +167 -0
  88. kafka/coordinator/assignors/range.py +81 -0
  89. kafka/coordinator/assignors/roundrobin.py +101 -0
  90. kafka/coordinator/assignors/sticky/StickyAssignorUserData.json +37 -0
  91. kafka/coordinator/assignors/sticky/__init__.py +0 -0
  92. kafka/coordinator/assignors/sticky/partition_movements.py +149 -0
  93. kafka/coordinator/assignors/sticky/sorted_set.py +63 -0
  94. kafka/coordinator/assignors/sticky/sticky_assignor.py +665 -0
  95. kafka/coordinator/assignors/sticky/user_data.py +8 -0
  96. kafka/coordinator/base.py +1215 -0
  97. kafka/coordinator/consumer.py +1224 -0
  98. kafka/coordinator/heartbeat.py +82 -0
  99. kafka/coordinator/subscription.py +34 -0
  100. kafka/errors.py +1004 -0
  101. kafka/future.py +166 -0
  102. kafka/metrics/__init__.py +13 -0
  103. kafka/metrics/compound_stat.py +33 -0
  104. kafka/metrics/dict_reporter.py +81 -0
  105. kafka/metrics/kafka_metric.py +36 -0
  106. kafka/metrics/measurable.py +27 -0
  107. kafka/metrics/measurable_stat.py +13 -0
  108. kafka/metrics/metric_config.py +33 -0
  109. kafka/metrics/metric_name.py +105 -0
  110. kafka/metrics/metrics.py +261 -0
  111. kafka/metrics/metrics_reporter.py +53 -0
  112. kafka/metrics/quota.py +41 -0
  113. kafka/metrics/stat.py +19 -0
  114. kafka/metrics/stats/__init__.py +15 -0
  115. kafka/metrics/stats/avg.py +24 -0
  116. kafka/metrics/stats/count.py +17 -0
  117. kafka/metrics/stats/histogram.py +99 -0
  118. kafka/metrics/stats/max_stat.py +17 -0
  119. kafka/metrics/stats/min_stat.py +19 -0
  120. kafka/metrics/stats/percentile.py +14 -0
  121. kafka/metrics/stats/percentiles.py +75 -0
  122. kafka/metrics/stats/rate.py +118 -0
  123. kafka/metrics/stats/sampled_stat.py +99 -0
  124. kafka/metrics/stats/sensor.py +136 -0
  125. kafka/metrics/stats/total.py +15 -0
  126. kafka/net/__init__.py +19 -0
  127. kafka/net/compat.py +165 -0
  128. kafka/net/connection.py +593 -0
  129. kafka/net/http_connect.py +144 -0
  130. kafka/net/inet.py +122 -0
  131. kafka/net/manager.py +451 -0
  132. kafka/net/metrics.py +149 -0
  133. kafka/net/sasl/__init__.py +32 -0
  134. kafka/net/sasl/abc.py +28 -0
  135. kafka/net/sasl/gssapi.py +95 -0
  136. kafka/net/sasl/msk.py +245 -0
  137. kafka/net/sasl/oauth.py +98 -0
  138. kafka/net/sasl/plain.py +42 -0
  139. kafka/net/sasl/scram.py +135 -0
  140. kafka/net/sasl/sspi.py +111 -0
  141. kafka/net/selector.py +644 -0
  142. kafka/net/socks5.py +262 -0
  143. kafka/net/transport.py +415 -0
  144. kafka/net/wakeup_notifier.py +72 -0
  145. kafka/partitioner/__init__.py +8 -0
  146. kafka/partitioner/abc.py +8 -0
  147. kafka/partitioner/default.py +89 -0
  148. kafka/partitioner/sticky.py +109 -0
  149. kafka/producer/__init__.py +5 -0
  150. kafka/producer/__main__.py +5 -0
  151. kafka/producer/future.py +101 -0
  152. kafka/producer/kafka.py +1123 -0
  153. kafka/producer/producer_batch.py +192 -0
  154. kafka/producer/record_accumulator.py +647 -0
  155. kafka/producer/sender.py +884 -0
  156. kafka/producer/transaction_manager.py +1326 -0
  157. kafka/protocol/__init__.py +0 -0
  158. kafka/protocol/admin/__init__.py +29 -0
  159. kafka/protocol/admin/acl.py +83 -0
  160. kafka/protocol/admin/acl.pyi +375 -0
  161. kafka/protocol/admin/client_quotas.py +14 -0
  162. kafka/protocol/admin/client_quotas.pyi +265 -0
  163. kafka/protocol/admin/cluster.py +31 -0
  164. kafka/protocol/admin/cluster.pyi +620 -0
  165. kafka/protocol/admin/configs.py +22 -0
  166. kafka/protocol/admin/configs.pyi +437 -0
  167. kafka/protocol/admin/groups.py +24 -0
  168. kafka/protocol/admin/groups.pyi +261 -0
  169. kafka/protocol/admin/topics.py +53 -0
  170. kafka/protocol/admin/topics.pyi +982 -0
  171. kafka/protocol/admin/transactions.py +18 -0
  172. kafka/protocol/admin/transactions.pyi +311 -0
  173. kafka/protocol/admin/users.py +14 -0
  174. kafka/protocol/admin/users.pyi +223 -0
  175. kafka/protocol/api_data.py +125 -0
  176. kafka/protocol/api_header.py +55 -0
  177. kafka/protocol/api_key.py +97 -0
  178. kafka/protocol/api_message.py +277 -0
  179. kafka/protocol/broker_version_data.py +246 -0
  180. kafka/protocol/consumer/__init__.py +13 -0
  181. kafka/protocol/consumer/fetch.py +16 -0
  182. kafka/protocol/consumer/fetch.pyi +298 -0
  183. kafka/protocol/consumer/group.py +38 -0
  184. kafka/protocol/consumer/group.pyi +824 -0
  185. kafka/protocol/consumer/metadata.py +30 -0
  186. kafka/protocol/consumer/metadata.pyi +89 -0
  187. kafka/protocol/consumer/offsets.py +75 -0
  188. kafka/protocol/consumer/offsets.pyi +288 -0
  189. kafka/protocol/data_container.py +166 -0
  190. kafka/protocol/frame.py +30 -0
  191. kafka/protocol/generate_stubs.py +468 -0
  192. kafka/protocol/metadata/__init__.py +10 -0
  193. kafka/protocol/metadata/api_versions.py +41 -0
  194. kafka/protocol/metadata/api_versions.pyi +128 -0
  195. kafka/protocol/metadata/find_coordinator.py +19 -0
  196. kafka/protocol/metadata/find_coordinator.pyi +105 -0
  197. kafka/protocol/metadata/metadata.py +34 -0
  198. kafka/protocol/metadata/metadata.pyi +160 -0
  199. kafka/protocol/old/__init__.py +0 -0
  200. kafka/protocol/old/abstract.py +17 -0
  201. kafka/protocol/old/add_offsets_to_txn.py +54 -0
  202. kafka/protocol/old/add_partitions_to_txn.py +71 -0
  203. kafka/protocol/old/admin.py +1086 -0
  204. kafka/protocol/old/api.py +205 -0
  205. kafka/protocol/old/api_versions.py +133 -0
  206. kafka/protocol/old/commit.py +355 -0
  207. kafka/protocol/old/consumer_protocol.py +36 -0
  208. kafka/protocol/old/end_txn.py +53 -0
  209. kafka/protocol/old/fetch.py +408 -0
  210. kafka/protocol/old/find_coordinator.py +72 -0
  211. kafka/protocol/old/group.py +451 -0
  212. kafka/protocol/old/init_producer_id.py +42 -0
  213. kafka/protocol/old/list_offsets.py +186 -0
  214. kafka/protocol/old/metadata.py +290 -0
  215. kafka/protocol/old/offset_for_leader_epoch.py +133 -0
  216. kafka/protocol/old/produce.py +247 -0
  217. kafka/protocol/old/sasl_authenticate.py +38 -0
  218. kafka/protocol/old/sasl_handshake.py +39 -0
  219. kafka/protocol/old/struct.py +87 -0
  220. kafka/protocol/old/txn_offset_commit.py +73 -0
  221. kafka/protocol/old/types.py +440 -0
  222. kafka/protocol/parser.py +191 -0
  223. kafka/protocol/producer/__init__.py +7 -0
  224. kafka/protocol/producer/produce.py +17 -0
  225. kafka/protocol/producer/produce.pyi +197 -0
  226. kafka/protocol/producer/transaction.py +30 -0
  227. kafka/protocol/producer/transaction.pyi +663 -0
  228. kafka/protocol/sasl.py +52 -0
  229. kafka/protocol/sasl.pyi +126 -0
  230. kafka/protocol/schemas/__init__.py +7 -0
  231. kafka/protocol/schemas/fields/__init__.py +7 -0
  232. kafka/protocol/schemas/fields/array.py +127 -0
  233. kafka/protocol/schemas/fields/base.py +156 -0
  234. kafka/protocol/schemas/fields/codecs/__init__.py +12 -0
  235. kafka/protocol/schemas/fields/codecs/encode_buffer.py +82 -0
  236. kafka/protocol/schemas/fields/codecs/tagged_fields.py +109 -0
  237. kafka/protocol/schemas/fields/codecs/types.py +505 -0
  238. kafka/protocol/schemas/fields/codegen.py +40 -0
  239. kafka/protocol/schemas/fields/simple.py +127 -0
  240. kafka/protocol/schemas/fields/struct.py +357 -0
  241. kafka/protocol/schemas/fields/struct_array.py +142 -0
  242. kafka/protocol/schemas/load_json.py +42 -0
  243. kafka/protocol/schemas/resources/AddOffsetsToTxnRequest.json +40 -0
  244. kafka/protocol/schemas/resources/AddOffsetsToTxnResponse.json +35 -0
  245. kafka/protocol/schemas/resources/AddPartitionsToTxnRequest.json +65 -0
  246. kafka/protocol/schemas/resources/AddPartitionsToTxnResponse.json +60 -0
  247. kafka/protocol/schemas/resources/AlterClientQuotasRequest.json +47 -0
  248. kafka/protocol/schemas/resources/AlterClientQuotasResponse.json +41 -0
  249. kafka/protocol/schemas/resources/AlterConfigsRequest.json +43 -0
  250. kafka/protocol/schemas/resources/AlterConfigsResponse.json +39 -0
  251. kafka/protocol/schemas/resources/AlterPartitionReassignmentsRequest.json +42 -0
  252. kafka/protocol/schemas/resources/AlterPartitionReassignmentsResponse.json +47 -0
  253. kafka/protocol/schemas/resources/AlterReplicaLogDirsRequest.json +41 -0
  254. kafka/protocol/schemas/resources/AlterReplicaLogDirsResponse.json +41 -0
  255. kafka/protocol/schemas/resources/AlterUserScramCredentialsRequest.json +45 -0
  256. kafka/protocol/schemas/resources/AlterUserScramCredentialsResponse.json +35 -0
  257. kafka/protocol/schemas/resources/ApiVersionsRequest.json +34 -0
  258. kafka/protocol/schemas/resources/ApiVersionsResponse.json +79 -0
  259. kafka/protocol/schemas/resources/ConsumerProtocolAssignment.json +42 -0
  260. kafka/protocol/schemas/resources/ConsumerProtocolSubscription.json +49 -0
  261. kafka/protocol/schemas/resources/CreateAclsRequest.json +46 -0
  262. kafka/protocol/schemas/resources/CreateAclsResponse.json +37 -0
  263. kafka/protocol/schemas/resources/CreatePartitionsRequest.json +47 -0
  264. kafka/protocol/schemas/resources/CreatePartitionsResponse.json +41 -0
  265. kafka/protocol/schemas/resources/CreateTopicsRequest.json +65 -0
  266. kafka/protocol/schemas/resources/CreateTopicsResponse.json +72 -0
  267. kafka/protocol/schemas/resources/DeleteAclsRequest.json +46 -0
  268. kafka/protocol/schemas/resources/DeleteAclsResponse.json +59 -0
  269. kafka/protocol/schemas/resources/DeleteGroupsRequest.json +30 -0
  270. kafka/protocol/schemas/resources/DeleteGroupsResponse.json +36 -0
  271. kafka/protocol/schemas/resources/DeleteRecordsRequest.json +42 -0
  272. kafka/protocol/schemas/resources/DeleteRecordsResponse.json +43 -0
  273. kafka/protocol/schemas/resources/DeleteTopicsRequest.json +43 -0
  274. kafka/protocol/schemas/resources/DeleteTopicsResponse.json +52 -0
  275. kafka/protocol/schemas/resources/DescribeAclsRequest.json +43 -0
  276. kafka/protocol/schemas/resources/DescribeAclsResponse.json +55 -0
  277. kafka/protocol/schemas/resources/DescribeClientQuotasRequest.json +37 -0
  278. kafka/protocol/schemas/resources/DescribeClientQuotasResponse.json +47 -0
  279. kafka/protocol/schemas/resources/DescribeClusterRequest.json +35 -0
  280. kafka/protocol/schemas/resources/DescribeClusterResponse.json +56 -0
  281. kafka/protocol/schemas/resources/DescribeConfigsRequest.json +42 -0
  282. kafka/protocol/schemas/resources/DescribeConfigsResponse.json +69 -0
  283. kafka/protocol/schemas/resources/DescribeGroupsRequest.json +38 -0
  284. kafka/protocol/schemas/resources/DescribeGroupsResponse.json +74 -0
  285. kafka/protocol/schemas/resources/DescribeLogDirsRequest.json +38 -0
  286. kafka/protocol/schemas/resources/DescribeLogDirsResponse.json +65 -0
  287. kafka/protocol/schemas/resources/DescribeProducersRequest.json +32 -0
  288. kafka/protocol/schemas/resources/DescribeProducersResponse.json +55 -0
  289. kafka/protocol/schemas/resources/DescribeQuorumRequest.json +39 -0
  290. kafka/protocol/schemas/resources/DescribeQuorumResponse.json +82 -0
  291. kafka/protocol/schemas/resources/DescribeTopicPartitionsRequest.json +40 -0
  292. kafka/protocol/schemas/resources/DescribeTopicPartitionsResponse.json +66 -0
  293. kafka/protocol/schemas/resources/DescribeTransactionsRequest.json +27 -0
  294. kafka/protocol/schemas/resources/DescribeTransactionsResponse.json +52 -0
  295. kafka/protocol/schemas/resources/DescribeUserScramCredentialsRequest.json +30 -0
  296. kafka/protocol/schemas/resources/DescribeUserScramCredentialsResponse.json +45 -0
  297. kafka/protocol/schemas/resources/ElectLeadersRequest.json +41 -0
  298. kafka/protocol/schemas/resources/ElectLeadersResponse.json +45 -0
  299. kafka/protocol/schemas/resources/EndTxnRequest.json +43 -0
  300. kafka/protocol/schemas/resources/EndTxnResponse.json +41 -0
  301. kafka/protocol/schemas/resources/FetchRequest.json +125 -0
  302. kafka/protocol/schemas/resources/FetchResponse.json +124 -0
  303. kafka/protocol/schemas/resources/FindCoordinatorRequest.json +43 -0
  304. kafka/protocol/schemas/resources/FindCoordinatorResponse.json +58 -0
  305. kafka/protocol/schemas/resources/HeartbeatRequest.json +39 -0
  306. kafka/protocol/schemas/resources/HeartbeatResponse.json +35 -0
  307. kafka/protocol/schemas/resources/IncrementalAlterConfigsRequest.json +44 -0
  308. kafka/protocol/schemas/resources/IncrementalAlterConfigsResponse.json +38 -0
  309. kafka/protocol/schemas/resources/InitProducerIdRequest.json +50 -0
  310. kafka/protocol/schemas/resources/InitProducerIdResponse.json +47 -0
  311. kafka/protocol/schemas/resources/JoinGroupRequest.json +63 -0
  312. kafka/protocol/schemas/resources/JoinGroupResponse.json +69 -0
  313. kafka/protocol/schemas/resources/LeaveGroupRequest.json +47 -0
  314. kafka/protocol/schemas/resources/LeaveGroupResponse.json +47 -0
  315. kafka/protocol/schemas/resources/ListConfigResourcesRequest.json +31 -0
  316. kafka/protocol/schemas/resources/ListConfigResourcesResponse.json +37 -0
  317. kafka/protocol/schemas/resources/ListGroupsRequest.json +36 -0
  318. kafka/protocol/schemas/resources/ListGroupsResponse.json +49 -0
  319. kafka/protocol/schemas/resources/ListOffsetsRequest.json +72 -0
  320. kafka/protocol/schemas/resources/ListOffsetsResponse.json +71 -0
  321. kafka/protocol/schemas/resources/ListPartitionReassignmentsRequest.json +34 -0
  322. kafka/protocol/schemas/resources/ListPartitionReassignmentsResponse.json +46 -0
  323. kafka/protocol/schemas/resources/ListTransactionsRequest.json +40 -0
  324. kafka/protocol/schemas/resources/ListTransactionsResponse.json +42 -0
  325. kafka/protocol/schemas/resources/MetadataRequest.json +56 -0
  326. kafka/protocol/schemas/resources/MetadataResponse.json +101 -0
  327. kafka/protocol/schemas/resources/OffsetCommitRequest.json +76 -0
  328. kafka/protocol/schemas/resources/OffsetCommitResponse.json +71 -0
  329. kafka/protocol/schemas/resources/OffsetDeleteRequest.json +39 -0
  330. kafka/protocol/schemas/resources/OffsetDeleteResponse.json +42 -0
  331. kafka/protocol/schemas/resources/OffsetFetchRequest.json +76 -0
  332. kafka/protocol/schemas/resources/OffsetFetchResponse.json +107 -0
  333. kafka/protocol/schemas/resources/OffsetForLeaderEpochRequest.json +52 -0
  334. kafka/protocol/schemas/resources/OffsetForLeaderEpochResponse.json +51 -0
  335. kafka/protocol/schemas/resources/ProduceRequest.json +73 -0
  336. kafka/protocol/schemas/resources/ProduceResponse.json +96 -0
  337. kafka/protocol/schemas/resources/RequestHeader.json +44 -0
  338. kafka/protocol/schemas/resources/ResponseHeader.json +26 -0
  339. kafka/protocol/schemas/resources/SaslAuthenticateRequest.json +29 -0
  340. kafka/protocol/schemas/resources/SaslAuthenticateResponse.json +34 -0
  341. kafka/protocol/schemas/resources/SaslHandshakeRequest.json +31 -0
  342. kafka/protocol/schemas/resources/SaslHandshakeResponse.json +32 -0
  343. kafka/protocol/schemas/resources/SyncGroupRequest.json +56 -0
  344. kafka/protocol/schemas/resources/SyncGroupResponse.json +46 -0
  345. kafka/protocol/schemas/resources/TxnOffsetCommitRequest.json +68 -0
  346. kafka/protocol/schemas/resources/TxnOffsetCommitResponse.json +47 -0
  347. kafka/protocol/schemas/resources/UpdateFeaturesRequest.json +43 -0
  348. kafka/protocol/schemas/resources/UpdateFeaturesResponse.json +39 -0
  349. kafka/protocol/schemas/resources/WriteTxnMarkersRequest.json +49 -0
  350. kafka/protocol/schemas/resources/WriteTxnMarkersResponse.json +45 -0
  351. kafka/protocol/schemas/resources/__init__.py +0 -0
  352. kafka/record/__init__.py +3 -0
  353. kafka/record/_crc32c.py +161 -0
  354. kafka/record/abc.py +144 -0
  355. kafka/record/default_records.py +782 -0
  356. kafka/record/legacy_records.py +587 -0
  357. kafka/record/memory_records.py +255 -0
  358. kafka/record/util.py +135 -0
  359. kafka/serializer/__init__.py +4 -0
  360. kafka/serializer/abstract.py +20 -0
  361. kafka/serializer/default.py +16 -0
  362. kafka/serializer/json.py +17 -0
  363. kafka/serializer/wrapper.py +21 -0
  364. kafka/structs.py +69 -0
  365. kafka/util.py +159 -0
  366. kafka/vendor/__init__.py +0 -0
  367. kafka/version.py +1 -0
  368. kafka_python-3.0.0.dist-info/METADATA +319 -0
  369. kafka_python-3.0.0.dist-info/RECORD +373 -0
  370. kafka_python-3.0.0.dist-info/WHEEL +5 -0
  371. kafka_python-3.0.0.dist-info/entry_points.txt +2 -0
  372. kafka_python-3.0.0.dist-info/licenses/LICENSE +202 -0
  373. 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...")