@platformatic/rdkafka 4.0.0 → 4.1.0-alpha.1

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 (293) hide show
  1. package/README.md +5 -5
  2. package/bench/seed.sh +0 -0
  3. package/binding.gyp +3 -3
  4. package/ci/prepublish.js +4 -3
  5. package/ci/update-version.js +123 -0
  6. package/config.d.ts +126 -10
  7. package/configure +0 -0
  8. package/deps/librdkafka/.semaphore/run-all-tests.yml +77 -0
  9. package/deps/librdkafka/.semaphore/semaphore-integration.yml +250 -0
  10. package/deps/librdkafka/.semaphore/semaphore.yml +67 -44
  11. package/deps/librdkafka/.semaphore/verify-linux-packages.yml +41 -0
  12. package/deps/librdkafka/CHANGELOG.md +471 -1
  13. package/deps/librdkafka/CONFIGURATION.md +29 -9
  14. package/deps/librdkafka/INTRODUCTION.md +909 -601
  15. package/deps/librdkafka/Makefile +2 -1
  16. package/deps/librdkafka/configure +0 -0
  17. package/deps/librdkafka/debian/rules +0 -0
  18. package/deps/librdkafka/dev-conf.sh +0 -0
  19. package/deps/librdkafka/examples/consumer.c +18 -8
  20. package/deps/librdkafka/examples/describe_consumer_groups.c +25 -6
  21. package/deps/librdkafka/examples/incremental_alter_configs.c +4 -5
  22. package/deps/librdkafka/examples/kafkatest_verifiable_client.cpp +26 -42
  23. package/deps/librdkafka/examples/openssl_engine_example.cpp +3 -4
  24. package/deps/librdkafka/examples/producer.cpp +2 -2
  25. package/deps/librdkafka/examples/user_scram.c +4 -5
  26. package/deps/librdkafka/examples/win_ssl_cert_store.cpp +4 -3
  27. package/deps/librdkafka/lds-gen.py +0 -0
  28. package/deps/librdkafka/mklove/Makefile.base +0 -0
  29. package/deps/librdkafka/packaging/RELEASE.md +8 -0
  30. package/deps/librdkafka/packaging/alpine/build-alpine.sh +0 -0
  31. package/deps/librdkafka/packaging/cp/README.md +4 -1
  32. package/deps/librdkafka/packaging/cp/check_features.c +12 -4
  33. package/deps/librdkafka/packaging/cp/verify-deb.sh +6 -6
  34. package/deps/librdkafka/packaging/cp/verify-packages.sh +52 -31
  35. package/deps/librdkafka/packaging/cp/verify-rpm.sh +6 -13
  36. package/deps/librdkafka/packaging/debian/docs +5 -0
  37. package/deps/librdkafka/packaging/debian/rules +0 -0
  38. package/deps/librdkafka/packaging/get_version.py +0 -0
  39. package/deps/librdkafka/packaging/homebrew/brew-update-pr.sh +0 -0
  40. package/deps/librdkafka/packaging/mingw-w64/{travis-before-install.sh → export-variables.sh} +0 -7
  41. package/deps/librdkafka/packaging/mingw-w64/semaphoreci-build.sh +1 -1
  42. package/deps/librdkafka/packaging/nuget/artifact.py +0 -0
  43. package/deps/librdkafka/packaging/nuget/cleanup-s3.py +0 -0
  44. package/deps/librdkafka/packaging/nuget/nuget.sh +0 -0
  45. package/deps/librdkafka/packaging/nuget/packaging.py +0 -0
  46. package/deps/librdkafka/packaging/nuget/push-to-nuget.sh +0 -0
  47. package/deps/librdkafka/packaging/nuget/release.py +0 -0
  48. package/deps/librdkafka/packaging/nuget/requirements.txt +3 -3
  49. package/deps/librdkafka/packaging/rpm/mock-on-docker.sh +0 -0
  50. package/deps/librdkafka/packaging/rpm/tests/run-test.sh +0 -0
  51. package/deps/librdkafka/packaging/rpm/tests/test-on-docker.sh +0 -0
  52. package/deps/librdkafka/packaging/tools/build-configurations-checks.sh +3 -1
  53. package/deps/librdkafka/packaging/tools/build-deb-package.sh +0 -0
  54. package/deps/librdkafka/packaging/tools/build-debian.sh +0 -0
  55. package/deps/librdkafka/packaging/tools/build-manylinux.sh +0 -0
  56. package/deps/librdkafka/packaging/tools/build-release-artifacts.sh +0 -0
  57. package/deps/librdkafka/packaging/tools/distro-build.sh +0 -0
  58. package/deps/librdkafka/packaging/tools/gh-release-checksums.py +0 -0
  59. package/deps/librdkafka/packaging/tools/rdutcoverage.sh +0 -0
  60. package/deps/librdkafka/packaging/tools/run-in-docker.sh +28 -0
  61. package/deps/librdkafka/packaging/tools/run-integration-tests.sh +31 -0
  62. package/deps/librdkafka/packaging/tools/run-style-check.sh +4 -0
  63. package/deps/librdkafka/packaging/tools/style-format.sh +6 -5
  64. package/deps/librdkafka/packaging/tools/update_rpcs_max_versions.py +100 -0
  65. package/deps/librdkafka/service.yml +154 -0
  66. package/deps/librdkafka/src/generate_proto.sh +0 -0
  67. package/deps/librdkafka/src/rd.h +145 -2
  68. package/deps/librdkafka/src/rdaddr.h +6 -7
  69. package/deps/librdkafka/src/rdatomic.h +15 -11
  70. package/deps/librdkafka/src/rdavg.h +3 -2
  71. package/deps/librdkafka/src/rdbase64.c +33 -2
  72. package/deps/librdkafka/src/rdbase64.h +2 -0
  73. package/deps/librdkafka/src/rdendian.h +10 -9
  74. package/deps/librdkafka/src/rdhttp.c +418 -100
  75. package/deps/librdkafka/src/rdhttp.h +23 -5
  76. package/deps/librdkafka/src/rdkafka.c +299 -145
  77. package/deps/librdkafka/src/rdkafka.h +89 -6
  78. package/deps/librdkafka/src/rdkafka_admin.c +482 -105
  79. package/deps/librdkafka/src/rdkafka_admin.h +42 -0
  80. package/deps/librdkafka/src/rdkafka_broker.c +463 -312
  81. package/deps/librdkafka/src/rdkafka_broker.h +68 -10
  82. package/deps/librdkafka/src/rdkafka_buf.c +8 -5
  83. package/deps/librdkafka/src/rdkafka_buf.h +4 -3
  84. package/deps/librdkafka/src/rdkafka_cert.c +25 -1
  85. package/deps/librdkafka/src/rdkafka_cert.h +4 -3
  86. package/deps/librdkafka/src/rdkafka_cgrp.c +468 -143
  87. package/deps/librdkafka/src/rdkafka_cgrp.h +36 -2
  88. package/deps/librdkafka/src/rdkafka_conf.c +526 -102
  89. package/deps/librdkafka/src/rdkafka_conf.h +68 -4
  90. package/deps/librdkafka/src/rdkafka_feature.c +472 -35
  91. package/deps/librdkafka/src/rdkafka_feature.h +2 -0
  92. package/deps/librdkafka/src/rdkafka_fetcher.c +22 -2
  93. package/deps/librdkafka/src/rdkafka_fetcher.h +2 -2
  94. package/deps/librdkafka/src/rdkafka_int.h +84 -46
  95. package/deps/librdkafka/src/rdkafka_metadata.c +221 -136
  96. package/deps/librdkafka/src/rdkafka_metadata.h +15 -11
  97. package/deps/librdkafka/src/rdkafka_metadata_cache.c +279 -73
  98. package/deps/librdkafka/src/rdkafka_mock.c +795 -19
  99. package/deps/librdkafka/src/rdkafka_mock.h +119 -0
  100. package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1298 -132
  101. package/deps/librdkafka/src/rdkafka_mock_handlers.c +345 -49
  102. package/deps/librdkafka/src/rdkafka_mock_int.h +180 -61
  103. package/deps/librdkafka/src/rdkafka_msg.c +35 -19
  104. package/deps/librdkafka/src/rdkafka_msg.h +3 -3
  105. package/deps/librdkafka/src/rdkafka_msgset_writer.c +14 -10
  106. package/deps/librdkafka/src/rdkafka_offset.c +33 -5
  107. package/deps/librdkafka/src/rdkafka_op.c +1 -1
  108. package/deps/librdkafka/src/rdkafka_op.h +45 -21
  109. package/deps/librdkafka/src/rdkafka_partition.c +118 -23
  110. package/deps/librdkafka/src/rdkafka_partition.h +16 -7
  111. package/deps/librdkafka/src/rdkafka_proto.h +4 -1
  112. package/deps/librdkafka/src/rdkafka_protocol.h +1 -0
  113. package/deps/librdkafka/src/rdkafka_queue.c +125 -48
  114. package/deps/librdkafka/src/rdkafka_queue.h +19 -0
  115. package/deps/librdkafka/src/rdkafka_range_assignor.c +1 -1
  116. package/deps/librdkafka/src/rdkafka_request.c +336 -64
  117. package/deps/librdkafka/src/rdkafka_request.h +24 -1
  118. package/deps/librdkafka/src/rdkafka_sasl.c +5 -3
  119. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +14 -10
  120. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1166 -89
  121. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +13 -3
  122. package/deps/librdkafka/src/rdkafka_sasl_plain.c +3 -3
  123. package/deps/librdkafka/src/rdkafka_sasl_scram.c +4 -3
  124. package/deps/librdkafka/src/rdkafka_ssl.c +306 -81
  125. package/deps/librdkafka/src/rdkafka_ssl.h +22 -0
  126. package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4 -0
  127. package/deps/librdkafka/src/rdkafka_telemetry.c +80 -23
  128. package/deps/librdkafka/src/rdkafka_telemetry_encode.c +5 -1
  129. package/deps/librdkafka/src/rdkafka_telemetry_encode.h +20 -20
  130. package/deps/librdkafka/src/rdkafka_topic.c +220 -137
  131. package/deps/librdkafka/src/rdkafka_topic.h +14 -8
  132. package/deps/librdkafka/src/rdkafka_transport.c +59 -47
  133. package/deps/librdkafka/src/rdkafka_transport.h +5 -0
  134. package/deps/librdkafka/src/rdkafka_txnmgr.c +6 -1
  135. package/deps/librdkafka/src/rdmap.c +6 -1
  136. package/deps/librdkafka/src/rdrand.c +11 -1
  137. package/deps/librdkafka/src/rdtime.h +48 -17
  138. package/deps/librdkafka/src/rdunittest.c +42 -25
  139. package/deps/librdkafka/src/rdunittest.h +2 -0
  140. package/deps/librdkafka/src/rdwin32.h +1 -1
  141. package/deps/librdkafka/src/regexp.c +47 -42
  142. package/deps/librdkafka/src/tinycthread_extra.c +45 -21
  143. package/deps/librdkafka/src/tinycthread_extra.h +7 -3
  144. package/deps/librdkafka/src-cpp/rdkafkacpp.h +28 -2
  145. package/deps/librdkafka/tests/0001-multiobj.c +4 -0
  146. package/deps/librdkafka/tests/0004-conf.c +103 -36
  147. package/deps/librdkafka/tests/0009-mock_cluster.c +1 -0
  148. package/deps/librdkafka/tests/0011-produce_batch.c +2 -1
  149. package/deps/librdkafka/tests/0016-client_swname.c +18 -3
  150. package/deps/librdkafka/tests/0017-compression.c +6 -8
  151. package/deps/librdkafka/tests/0018-cgrp_term.c +8 -3
  152. package/deps/librdkafka/tests/0021-rkt_destroy.c +1 -0
  153. package/deps/librdkafka/tests/0022-consume_batch.c +4 -1
  154. package/deps/librdkafka/tests/0026-consume_pause.c +12 -14
  155. package/deps/librdkafka/tests/0028-long_topicnames.c +1 -1
  156. package/deps/librdkafka/tests/0029-assign_offset.c +6 -8
  157. package/deps/librdkafka/tests/0030-offset_commit.c +2 -2
  158. package/deps/librdkafka/tests/0033-regex_subscribe.c +33 -17
  159. package/deps/librdkafka/tests/0034-offset_reset.c +25 -4
  160. package/deps/librdkafka/tests/0036-partial_fetch.c +1 -0
  161. package/deps/librdkafka/tests/0038-performance.c +1 -0
  162. package/deps/librdkafka/tests/0040-io_event.c +11 -5
  163. package/deps/librdkafka/tests/0041-fetch_max_bytes.c +1 -0
  164. package/deps/librdkafka/tests/0044-partition_cnt.c +3 -2
  165. package/deps/librdkafka/tests/0045-subscribe_update.c +284 -20
  166. package/deps/librdkafka/tests/0047-partial_buf_tmout.c +1 -0
  167. package/deps/librdkafka/tests/0048-partitioner.c +1 -1
  168. package/deps/librdkafka/tests/0050-subscribe_adds.c +1 -0
  169. package/deps/librdkafka/tests/0051-assign_adds.c +1 -0
  170. package/deps/librdkafka/tests/0052-msg_timestamps.c +22 -4
  171. package/deps/librdkafka/tests/0053-stats_cb.cpp +12 -20
  172. package/deps/librdkafka/tests/0055-producer_latency.c +176 -3
  173. package/deps/librdkafka/tests/0056-balanced_group_mt.c +6 -2
  174. package/deps/librdkafka/tests/0058-log.cpp +3 -3
  175. package/deps/librdkafka/tests/0059-bsearch.cpp +8 -4
  176. package/deps/librdkafka/tests/0061-consumer_lag.cpp +25 -5
  177. package/deps/librdkafka/tests/0067-empty_topic.cpp +3 -0
  178. package/deps/librdkafka/tests/0068-produce_timeout.c +21 -23
  179. package/deps/librdkafka/tests/0069-consumer_add_parts.c +3 -7
  180. package/deps/librdkafka/tests/0075-retry.c +38 -1
  181. package/deps/librdkafka/tests/0076-produce_retry.c +2 -0
  182. package/deps/librdkafka/tests/0077-compaction.c +8 -2
  183. package/deps/librdkafka/tests/0080-admin_ut.c +34 -4
  184. package/deps/librdkafka/tests/0081-admin.c +455 -155
  185. package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +4 -0
  186. package/deps/librdkafka/tests/0083-cb_event.c +8 -3
  187. package/deps/librdkafka/tests/0084-destroy_flags.c +14 -18
  188. package/deps/librdkafka/tests/0085-headers.cpp +4 -0
  189. package/deps/librdkafka/tests/0086-purge.c +40 -7
  190. package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +1 -1
  191. package/deps/librdkafka/tests/0089-max_poll_interval.c +12 -7
  192. package/deps/librdkafka/tests/0090-idempotence.c +1 -2
  193. package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +5 -7
  194. package/deps/librdkafka/tests/0092-mixed_msgver.c +6 -0
  195. package/deps/librdkafka/tests/0093-holb.c +8 -5
  196. package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +1 -0
  197. package/deps/librdkafka/tests/0097-ssl_verify.cpp +262 -70
  198. package/deps/librdkafka/tests/0098-consumer-txn.cpp +16 -16
  199. package/deps/librdkafka/tests/0099-commit_metadata.c +1 -1
  200. package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +1 -1
  201. package/deps/librdkafka/tests/0102-static_group_rebalance.c +317 -16
  202. package/deps/librdkafka/tests/0103-transactions.c +7 -7
  203. package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +1 -0
  204. package/deps/librdkafka/tests/0105-transactions_mock.c +18 -11
  205. package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +35 -14
  206. package/deps/librdkafka/tests/0107-topic_recreate.c +2 -2
  207. package/deps/librdkafka/tests/0109-auto_create_topics.cpp +86 -26
  208. package/deps/librdkafka/tests/0110-batch_size.cpp +1 -2
  209. package/deps/librdkafka/tests/0112-assign_unknown_part.c +3 -14
  210. package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +248 -104
  211. package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +3 -3
  212. package/deps/librdkafka/tests/0115-producer_auth.cpp +4 -1
  213. package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +7 -5
  214. package/deps/librdkafka/tests/0117-mock_errors.c +15 -5
  215. package/deps/librdkafka/tests/0118-commit_rebalance.c +41 -8
  216. package/deps/librdkafka/tests/0119-consumer_auth.cpp +20 -1
  217. package/deps/librdkafka/tests/0120-asymmetric_subscription.c +8 -3
  218. package/deps/librdkafka/tests/0125-immediate_flush.c +1 -1
  219. package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +323 -8
  220. package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +1 -0
  221. package/deps/librdkafka/tests/0132-strategy_ordering.c +12 -4
  222. package/deps/librdkafka/tests/0133-ssl_keys.c +28 -6
  223. package/deps/librdkafka/tests/0137-barrier_batch_consume.c +24 -14
  224. package/deps/librdkafka/tests/0139-offset_validation_mock.c +510 -2
  225. package/deps/librdkafka/tests/0140-commit_metadata.cpp +1 -1
  226. package/deps/librdkafka/tests/0142-reauthentication.c +37 -17
  227. package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +39 -40
  228. package/deps/librdkafka/tests/0146-metadata_mock.c +76 -15
  229. package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +952 -0
  230. package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +563 -0
  231. package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
  232. package/deps/librdkafka/tests/0150-telemetry_mock.c +9 -6
  233. package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
  234. package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
  235. package/deps/librdkafka/tests/0153-memberid.c +128 -0
  236. package/deps/librdkafka/tests/CMakeLists.txt +6 -0
  237. package/deps/librdkafka/tests/LibrdkafkaTestApp.py +34 -9
  238. package/deps/librdkafka/tests/autotest.sh +0 -0
  239. package/deps/librdkafka/tests/broker_version_tests.py +0 -0
  240. package/deps/librdkafka/tests/buildbox.sh +0 -0
  241. package/deps/librdkafka/tests/cleanup-checker-tests.sh +0 -0
  242. package/deps/librdkafka/tests/cluster_testing.py +0 -0
  243. package/deps/librdkafka/tests/delete-test-topics.sh +0 -0
  244. package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
  245. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
  246. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
  247. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
  248. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +42 -101
  249. package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
  250. package/deps/librdkafka/tests/fixtures/ssl/client2.key +41 -29
  251. package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +112 -37
  252. package/deps/librdkafka/tests/gen-ssl-certs.sh +0 -0
  253. package/deps/librdkafka/tests/interactive_broker_version.py +0 -0
  254. package/deps/librdkafka/tests/java/Makefile +2 -1
  255. package/deps/librdkafka/tests/java/run-class.sh +0 -0
  256. package/deps/librdkafka/tests/lz4_manual_test.sh +0 -0
  257. package/deps/librdkafka/tests/multi-broker-version-test.sh +0 -0
  258. package/deps/librdkafka/tests/parse-refcnt.sh +0 -0
  259. package/deps/librdkafka/tests/performance_plot.py +0 -0
  260. package/deps/librdkafka/tests/requirements.txt +1 -1
  261. package/deps/librdkafka/tests/run-all-tests.sh +79 -0
  262. package/deps/librdkafka/tests/run-consumer-tests.sh +0 -0
  263. package/deps/librdkafka/tests/run-producer-tests.sh +0 -0
  264. package/deps/librdkafka/tests/run-test-batches.py +157 -0
  265. package/deps/librdkafka/tests/run-test.sh +0 -0
  266. package/deps/librdkafka/tests/sasl_test.py +0 -0
  267. package/deps/librdkafka/tests/test.c +478 -18
  268. package/deps/librdkafka/tests/test.h +62 -13
  269. package/deps/librdkafka/tests/testcpp.cpp +6 -1
  270. package/deps/librdkafka/tests/testcpp.h +30 -2
  271. package/deps/librdkafka/tests/testshared.h +16 -2
  272. package/deps/librdkafka/tests/tools/stats/graph.py +0 -0
  273. package/deps/librdkafka/tests/tools/stats/to_csv.py +1 -1
  274. package/deps/librdkafka/tests/trivup/trivup-0.14.0.tar.gz +0 -0
  275. package/deps/librdkafka/tests/until-fail.sh +0 -0
  276. package/deps/librdkafka/tests/xxxx-metadata.cpp +1 -1
  277. package/deps/librdkafka/vcpkg.json +1 -1
  278. package/deps/librdkafka/win32/setup-msys2.ps1 +24 -8
  279. package/deps/librdkafka/win32/setup-vcpkg.ps1 +28 -6
  280. package/deps/librdkafka/win32/tests/tests.vcxproj +6 -0
  281. package/deps/librdkafka/win32/wingetopt.h +5 -4
  282. package/errors.d.ts +27 -1
  283. package/lib/error.js +29 -3
  284. package/make_docs.sh +0 -0
  285. package/package.json +12 -13
  286. package/run_docker.sh +0 -0
  287. package/src/callbacks.cc +6 -9
  288. package/src/callbacks.h +1 -5
  289. package/test/kafka-consumer-worker.js +10 -7
  290. package/test/kafka-consumer.spec.js +32 -21
  291. package/deps/librdkafka/.semaphore/project.yml +0 -43
  292. package/deps/librdkafka/.semaphore/project_public.yml +0 -20
  293. package/deps/librdkafka/win32/install-openssl.ps1 +0 -33
@@ -177,9 +177,7 @@ rd_kafka_cgrp_rebalance_protocol(rd_kafka_cgrp_t *rkcg) {
177
177
  RD_KAFKA_CGRP_CONSUMER_F_SUBSCRIBED_ONCE))
178
178
  return RD_KAFKA_REBALANCE_PROTOCOL_NONE;
179
179
 
180
- return rkcg->rkcg_rk->rk_conf.partition_assignors_cooperative
181
- ? RD_KAFKA_REBALANCE_PROTOCOL_COOPERATIVE
182
- : RD_KAFKA_REBALANCE_PROTOCOL_EAGER;
180
+ return RD_KAFKA_REBALANCE_PROTOCOL_COOPERATIVE;
183
181
  }
184
182
 
185
183
  if (!rkcg->rkcg_assignor)
@@ -265,11 +263,28 @@ typedef RD_MAP_TYPE(const rd_kafka_topic_partition_t *,
265
263
 
266
264
  /**
267
265
  * @returns true if consumer has joined the group and thus requires a leave.
266
+ *
267
+ * `rkcg_member_id` is sufficient to know this with "classic" group protocol.
268
268
  */
269
- #define RD_KAFKA_CGRP_HAS_JOINED(rkcg) \
270
- (rkcg->rkcg_member_id != NULL && \
269
+ #define RD_KAFKA_CGRP_HAS_JOINED_CLASSIC(rkcg) \
270
+ (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CLASSIC && \
271
+ rkcg->rkcg_member_id != NULL && \
271
272
  RD_KAFKAP_STR_LEN((rkcg)->rkcg_member_id) > 0)
272
273
 
274
+ /**
275
+ * @returns true if consumer has joined the group and thus requires a leave.
276
+ *
277
+ * With "consumer" group protocol we cannot rely on the `rkcg_member_id`
278
+ * as it's client generated.
279
+ */
280
+ #define RD_KAFKA_CGRP_HAS_JOINED_CONSUMER(rkcg) \
281
+ (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CONSUMER && \
282
+ rkcg->rkcg_generation_id > 0)
283
+
284
+ #define RD_KAFKA_CGRP_HAS_JOINED(rkcg) \
285
+ (RD_KAFKA_CGRP_HAS_JOINED_CLASSIC(rkcg) || \
286
+ RD_KAFKA_CGRP_HAS_JOINED_CONSUMER(rkcg))
287
+
273
288
 
274
289
  /**
275
290
  * @returns true if cgrp is waiting for a rebalance_cb to be handled by
@@ -355,6 +370,36 @@ static int rd_kafka_cgrp_set_state(rd_kafka_cgrp_t *rkcg, int state) {
355
370
  return 1;
356
371
  }
357
372
 
373
+ /**
374
+ * @brief Set the cgrp last error and current timestamp
375
+ * as last error timestamp.
376
+ */
377
+ static void rd_kafka_cgrp_set_last_err(rd_kafka_cgrp_t *rkcg,
378
+ rd_kafka_resp_err_t rkcg_last_err) {
379
+ rkcg->rkcg_last_err = rkcg_last_err;
380
+ rkcg->rkcg_ts_last_err = rd_clock();
381
+ }
382
+
383
+ /**
384
+ * @brief Clears cgrp last error and its timestamp.
385
+ */
386
+ static void rd_kafka_cgrp_clear_last_err(rd_kafka_cgrp_t *rkcg) {
387
+ rkcg->rkcg_last_err = RD_KAFKA_RESP_ERR_NO_ERROR;
388
+ rkcg->rkcg_ts_last_err = 0;
389
+ }
390
+
391
+ /**
392
+ * @brief Clears cgrp last error if it's an heartbeat related error like
393
+ * a topic authorization failed one.
394
+ */
395
+ static void
396
+ rd_kafka_cgrp_maybe_clear_heartbeat_failed_err(rd_kafka_cgrp_t *rkcg) {
397
+ if (rkcg->rkcg_last_err ==
398
+ RD_KAFKA_RESP_ERR_TOPIC_AUTHORIZATION_FAILED) {
399
+ rd_kafka_cgrp_clear_last_err(rkcg);
400
+ }
401
+ }
402
+
358
403
 
359
404
  void rd_kafka_cgrp_set_join_state(rd_kafka_cgrp_t *rkcg, int join_state) {
360
405
  if ((int)rkcg->rkcg_join_state == join_state)
@@ -386,6 +431,8 @@ void rd_kafka_cgrp_set_join_state(rd_kafka_cgrp_t *rkcg, int join_state) {
386
431
  void rd_kafka_cgrp_destroy_final(rd_kafka_cgrp_t *rkcg) {
387
432
  rd_kafka_assert(rkcg->rkcg_rk, !rkcg->rkcg_subscription);
388
433
  rd_kafka_assert(rkcg->rkcg_rk, !rkcg->rkcg_group_leader.members);
434
+ rd_kafka_assert(rkcg->rkcg_rk, !rkcg->rkcg_subscription_topics);
435
+ rd_kafka_assert(rkcg->rkcg_rk, !rkcg->rkcg_subscription_regex);
389
436
  rd_kafka_cgrp_set_member_id(rkcg, NULL);
390
437
  rd_kafka_topic_partition_list_destroy(rkcg->rkcg_current_assignment);
391
438
  RD_IF_FREE(rkcg->rkcg_target_assignment,
@@ -459,6 +506,7 @@ rd_kafka_cgrp_t *rd_kafka_cgrp_new(rd_kafka_t *rk,
459
506
  rd_kafkap_str_new(rk->rk_conf.group_instance_id, -1);
460
507
  rkcg->rkcg_group_remote_assignor =
461
508
  rd_kafkap_str_new(rk->rk_conf.group_remote_assignor, -1);
509
+
462
510
  if (!RD_KAFKAP_STR_LEN(rkcg->rkcg_rk->rk_conf.client_rack))
463
511
  rkcg->rkcg_client_rack = rd_kafkap_str_new(NULL, -1);
464
512
  else
@@ -467,15 +515,24 @@ rd_kafka_cgrp_t *rd_kafka_cgrp_new(rd_kafka_t *rk,
467
515
  rkcg->rkcg_next_subscription = NULL;
468
516
  TAILQ_INIT(&rkcg->rkcg_topics);
469
517
  rd_list_init(&rkcg->rkcg_toppars, 32, NULL);
470
- rd_kafka_cgrp_set_member_id(rkcg, "");
518
+
519
+ if (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CONSUMER) {
520
+ rd_kafka_Uuid_t uuid = rd_kafka_Uuid_random();
521
+ rd_kafka_cgrp_set_member_id(rkcg,
522
+ rd_kafka_Uuid_base64str(&uuid));
523
+ } else {
524
+ rd_kafka_cgrp_set_member_id(rkcg, "");
525
+ }
526
+
471
527
  rkcg->rkcg_subscribed_topics =
472
- rd_list_new(0, (void *)rd_kafka_topic_info_destroy);
528
+ rd_list_new(0, rd_kafka_topic_info_destroy_free);
473
529
  rd_interval_init(&rkcg->rkcg_coord_query_intvl);
474
530
  rd_interval_init(&rkcg->rkcg_heartbeat_intvl);
475
531
  rd_interval_init(&rkcg->rkcg_join_intvl);
476
532
  rd_interval_init(&rkcg->rkcg_timeout_scan_intvl);
477
533
  rd_atomic32_init(&rkcg->rkcg_assignment_lost, rd_false);
478
534
  rd_atomic32_init(&rkcg->rkcg_terminated, rd_false);
535
+ rd_atomic32_init(&rkcg->rkcg_subscription_version, 0);
479
536
  rkcg->rkcg_current_assignment = rd_kafka_topic_partition_list_new(0);
480
537
  rkcg->rkcg_target_assignment = NULL;
481
538
  rkcg->rkcg_next_target_assignment = NULL;
@@ -498,13 +555,6 @@ rd_kafka_cgrp_t *rd_kafka_cgrp_new(rd_kafka_t *rk,
498
555
  rk->rk_conf.auto_commit_interval_ms * 1000ll,
499
556
  rd_kafka_cgrp_offset_commit_tmr_cb, rkcg);
500
557
 
501
- if (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CONSUMER) {
502
- rd_kafka_log(
503
- rk, LOG_WARNING, "CGRP",
504
- "KIP-848 Consumer Group Protocol is in Early Access "
505
- "and MUST NOT be used in production");
506
- }
507
-
508
558
  return rkcg;
509
559
  }
510
560
 
@@ -598,6 +648,9 @@ static int rd_kafka_cgrp_coord_update(rd_kafka_cgrp_t *rkcg, int32_t coord_id) {
598
648
  /* Clear previous broker handle, if any */
599
649
  if (rkcg->rkcg_curr_coord)
600
650
  rd_kafka_cgrp_coord_clear_broker(rkcg);
651
+
652
+ rd_kafka_cgrp_consumer_expedite_next_heartbeat(
653
+ rkcg, "coordinator changed");
601
654
  }
602
655
 
603
656
 
@@ -732,6 +785,8 @@ err:
732
785
 
733
786
  RD_KAFKA_ERR_ACTION_RETRY, RD_KAFKA_RESP_ERR__TIMED_OUT_QUEUE,
734
787
 
788
+ RD_KAFKA_ERR_ACTION_RETRY, RD_KAFKA_RESP_ERR__DESTROY_BROKER,
789
+
735
790
  RD_KAFKA_ERR_ACTION_END);
736
791
 
737
792
 
@@ -748,7 +803,13 @@ err:
748
803
  "FindCoordinator response error: %s", errstr);
749
804
 
750
805
  /* Suppress repeated errors */
751
- rkcg->rkcg_last_err = ErrorCode;
806
+ rd_kafka_cgrp_set_last_err(rkcg, ErrorCode);
807
+ }
808
+
809
+ if (ErrorCode == RD_KAFKA_RESP_ERR__DESTROY_BROKER) {
810
+ /* This error is one-time and should cause
811
+ * an immediate retry. */
812
+ rd_interval_reset(&rkcg->rkcg_coord_query_intvl);
752
813
  }
753
814
 
754
815
  /* Retries are performed by the timer-intervalled
@@ -854,6 +915,44 @@ rd_kafka_broker_t *rd_kafka_cgrp_get_coord(rd_kafka_cgrp_t *rkcg) {
854
915
  return rkcg->rkcg_coord;
855
916
  }
856
917
 
918
+ #define rd_kafka_cgrp_will_leave(rkcg) \
919
+ (rkcg->rkcg_flags & (RD_KAFKA_CGRP_F_LEAVE_ON_UNASSIGN_DONE | \
920
+ RD_KAFKA_CGRP_F_WAIT_LEAVE))
921
+
922
+ #define rd_kafka_cgrp_consumer_will_rejoin(rkcg) \
923
+ (rkcg->rkcg_consumer_flags & \
924
+ (RD_KAFKA_CGRP_CONSUMER_F_WAIT_REJOIN | \
925
+ RD_KAFKA_CGRP_CONSUMER_F_WAIT_REJOIN_TO_COMPLETE))
926
+
927
+ #define rd_kafka_cgrp_consumer_subscription_preconditions_met(rkcg) \
928
+ (!RD_KAFKA_CGRP_REBALANCING(rkcg) && \
929
+ rkcg->rkcg_consumer_flags & \
930
+ RD_KAFKA_CGRP_CONSUMER_F_SEND_NEW_SUBSCRIPTION)
931
+
932
+ static int32_t
933
+ rd_kafka_cgrp_subscription_set(rd_kafka_cgrp_t *rkcg,
934
+ rd_kafka_topic_partition_list_t *rktparlist);
935
+
936
+ /**
937
+ * @brief Apply next subscription in \p rkcg , if set.
938
+ */
939
+ static void rd_kafka_cgrp_consumer_apply_next_subscribe(rd_kafka_cgrp_t *rkcg) {
940
+ if (rkcg->rkcg_next_subscription) {
941
+ if (unlikely(rkcg->rkcg_flags & RD_KAFKA_CGRP_F_TERMINATE)) {
942
+ rd_kafka_topic_partition_list_destroy(
943
+ rkcg->rkcg_next_subscription);
944
+ rkcg->rkcg_next_subscription = NULL;
945
+ return;
946
+ }
947
+
948
+ rd_kafka_cgrp_subscription_set(rkcg,
949
+ rkcg->rkcg_next_subscription);
950
+ rkcg->rkcg_next_subscription = NULL;
951
+
952
+ rd_kafka_cgrp_consumer_expedite_next_heartbeat(
953
+ rkcg, "subscription changed");
954
+ }
955
+ }
857
956
 
858
957
  /**
859
958
  * @brief cgrp handling of LeaveGroup responses
@@ -1010,7 +1109,9 @@ static void rd_kafka_cgrp_consumer_leave(rd_kafka_cgrp_t *rkcg) {
1010
1109
  rkcg->rkcg_coord, rkcg->rkcg_group_id, rkcg->rkcg_member_id,
1011
1110
  member_epoch, rkcg->rkcg_group_instance_id,
1012
1111
  NULL /* no rack */, -1 /* no rebalance_timeout_ms */,
1013
- NULL /* no subscription */, NULL /* no remote assignor */,
1112
+ NULL /* no subscription topics */,
1113
+ NULL /* no regex subscription */,
1114
+ NULL /* no remote assignor */,
1014
1115
  NULL /* no current assignment */,
1015
1116
  RD_KAFKA_REPLYQ(rkcg->rkcg_ops, 0),
1016
1117
  rd_kafka_cgrp_handle_ConsumerGroupHeartbeat_leave, rkcg);
@@ -1339,6 +1440,50 @@ done:
1339
1440
  rd_kafka_cgrp_group_assignment_set(rkcg, NULL);
1340
1441
  }
1341
1442
 
1443
+ /**
1444
+ * @brief Rejoin the group (KIP-848).
1445
+ */
1446
+ static void
1447
+ rd_kafka_cgrp_consumer_rejoin(rd_kafka_cgrp_t *rkcg, const char *fmt, ...) {
1448
+ char reason[512];
1449
+ va_list ap;
1450
+ char astr[128];
1451
+
1452
+ va_start(ap, fmt);
1453
+ rd_vsnprintf(reason, sizeof(reason), fmt, ap);
1454
+ va_end(ap);
1455
+
1456
+ if (rkcg->rkcg_group_assignment)
1457
+ rd_snprintf(astr, sizeof(astr), " with %d owned partition(s)",
1458
+ rkcg->rkcg_group_assignment->cnt);
1459
+ else
1460
+ rd_snprintf(astr, sizeof(astr), " without an assignment");
1461
+
1462
+ if (rkcg->rkcg_subscription || rkcg->rkcg_next_subscription) {
1463
+ rd_kafka_dbg(
1464
+ rkcg->rkcg_rk, CONSUMER | RD_KAFKA_DBG_CGRP, "REJOIN",
1465
+ "Group \"%s\": %s group%s: %s", rkcg->rkcg_group_id->str,
1466
+ rkcg->rkcg_join_state == RD_KAFKA_CGRP_JOIN_STATE_INIT
1467
+ ? "Joining"
1468
+ : "Rejoining",
1469
+ astr, reason);
1470
+ } else {
1471
+ rd_kafka_dbg(
1472
+ rkcg->rkcg_rk, CONSUMER | RD_KAFKA_DBG_CGRP, "NOREJOIN",
1473
+ "Group \"%s\": Not %s group%s: %s: "
1474
+ "no subscribed topics",
1475
+ rkcg->rkcg_group_id->str,
1476
+ rkcg->rkcg_join_state == RD_KAFKA_CGRP_JOIN_STATE_INIT
1477
+ ? "joining"
1478
+ : "rejoining",
1479
+ astr, reason);
1480
+ }
1481
+
1482
+ rd_kafka_cgrp_leave_maybe(rkcg);
1483
+ rd_kafka_cgrp_consumer_reset(rkcg);
1484
+ rd_kafka_cgrp_set_join_state(rkcg, RD_KAFKA_CGRP_JOIN_STATE_INIT);
1485
+ rd_kafka_cgrp_consumer_expedite_next_heartbeat(rkcg, "rejoining");
1486
+ }
1342
1487
 
1343
1488
  /**
1344
1489
  * @brief Rejoin the group.
@@ -1353,6 +1498,10 @@ static void rd_kafka_cgrp_rejoin(rd_kafka_cgrp_t *rkcg, const char *fmt, ...) {
1353
1498
  char reason[512];
1354
1499
  va_list ap;
1355
1500
  char astr[128];
1501
+ if (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CONSUMER) {
1502
+ rd_kafka_cgrp_consumer_rejoin(rkcg, fmt, ap);
1503
+ return;
1504
+ }
1356
1505
 
1357
1506
  va_start(ap, fmt);
1358
1507
  rd_vsnprintf(reason, sizeof(reason), fmt, ap);
@@ -1386,9 +1535,7 @@ static void rd_kafka_cgrp_rejoin(rd_kafka_cgrp_t *rkcg, const char *fmt, ...) {
1386
1535
  rd_kafka_cgrp_leave_maybe(rkcg);
1387
1536
  }
1388
1537
 
1389
- rd_kafka_cgrp_consumer_reset(rkcg);
1390
1538
  rd_kafka_cgrp_set_join_state(rkcg, RD_KAFKA_CGRP_JOIN_STATE_INIT);
1391
- rd_kafka_cgrp_consumer_expedite_next_heartbeat(rkcg, "rejoining");
1392
1539
  }
1393
1540
 
1394
1541
 
@@ -2232,6 +2379,8 @@ static void rd_kafka_cgrp_handle_JoinGroup(rd_kafka_t *rk,
2232
2379
  * on receiving the response since some topics
2233
2380
  * may be missing. */
2234
2381
  rd_false,
2382
+ /* cgrp_update=false: no subscription version is used */
2383
+ -1,
2235
2384
  /* force_racks is true if any memeber has a client rack set,
2236
2385
  since we will require partition to rack mapping in that
2237
2386
  case for rack-aware assignors. */
@@ -2336,7 +2485,10 @@ static rd_kafka_op_res_t rd_kafka_cgrp_handle_Metadata_op(rd_kafka_t *rk,
2336
2485
  if (rko->rko_err == RD_KAFKA_RESP_ERR__DESTROY)
2337
2486
  return RD_KAFKA_OP_RES_HANDLED; /* Terminating */
2338
2487
 
2339
- rd_kafka_cgrp_metadata_update_check(rkcg, rd_false /*dont rejoin*/);
2488
+ if (rd_atomic32_get(&rkcg->rkcg_subscription_version) ==
2489
+ rko->rko_u.metadata.subscription_version)
2490
+ rd_kafka_cgrp_metadata_update_check(rkcg,
2491
+ rd_false /*dont rejoin*/);
2340
2492
 
2341
2493
  return RD_KAFKA_OP_RES_HANDLED;
2342
2494
  }
@@ -2353,6 +2505,7 @@ static rd_kafka_op_res_t rd_kafka_cgrp_handle_Metadata_op(rd_kafka_t *rk,
2353
2505
  */
2354
2506
  static int rd_kafka_cgrp_metadata_refresh(rd_kafka_cgrp_t *rkcg,
2355
2507
  int *metadata_agep,
2508
+ int32_t cgrp_subscription_version,
2356
2509
  const char *reason) {
2357
2510
  rd_kafka_t *rk = rkcg->rkcg_rk;
2358
2511
  rd_kafka_op_t *rko;
@@ -2362,8 +2515,8 @@ static int rd_kafka_cgrp_metadata_refresh(rd_kafka_cgrp_t *rkcg,
2362
2515
  rd_list_init(&topics, 8, rd_free);
2363
2516
 
2364
2517
  /* Insert all non-wildcard topics in cache. */
2365
- rd_kafka_metadata_cache_hint_rktparlist(
2366
- rkcg->rkcg_rk, rkcg->rkcg_subscription, NULL, 0 /*dont replace*/);
2518
+ rd_kafka_metadata_cache_hint_rktparlist(rkcg->rkcg_rk,
2519
+ rkcg->rkcg_subscription, NULL);
2367
2520
 
2368
2521
  if (rkcg->rkcg_flags & RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION) {
2369
2522
  /* For wildcard subscriptions make sure the
@@ -2421,10 +2574,19 @@ static int rd_kafka_cgrp_metadata_refresh(rd_kafka_cgrp_t *rkcg,
2421
2574
  rd_kafka_cgrp_handle_Metadata_op);
2422
2575
  rd_kafka_op_set_replyq(rko, rkcg->rkcg_ops, 0);
2423
2576
 
2424
- err = rd_kafka_metadata_request(rkcg->rkcg_rk, NULL, &topics,
2425
- rd_false /*!allow auto create */,
2426
- rd_true /*cgrp_update*/, reason, rko);
2577
+ err = rd_kafka_metadata_request(
2578
+ rkcg->rkcg_rk, NULL, &topics, rd_false /*!allow auto create */,
2579
+ rd_true /*cgrp_update*/, cgrp_subscription_version, reason, rko);
2427
2580
  if (err) {
2581
+ /* Hint cache that something is interested in
2582
+ * these topics so that they will be included in
2583
+ * a future all known_topics query. */
2584
+
2585
+ rd_kafka_wrlock(rk);
2586
+ rd_kafka_metadata_cache_hint(rk, &topics, NULL,
2587
+ RD_KAFKA_RESP_ERR__NOENT);
2588
+ rd_kafka_wrunlock(rk);
2589
+
2428
2590
  rd_kafka_dbg(rk, CGRP | RD_KAFKA_DBG_METADATA, "CGRPMETADATA",
2429
2591
  "%s: need to refresh metadata (%dms old) "
2430
2592
  "but no usable brokers available: %s",
@@ -2439,8 +2601,9 @@ static int rd_kafka_cgrp_metadata_refresh(rd_kafka_cgrp_t *rkcg,
2439
2601
 
2440
2602
 
2441
2603
 
2442
- static void rd_kafka_cgrp_join(rd_kafka_cgrp_t *rkcg) {
2443
- int metadata_age;
2604
+ static void rd_kafka_cgrp_join(rd_kafka_cgrp_t *rkcg,
2605
+ int32_t cgrp_subscription_version) {
2606
+ int metadata_age, metadata_refresh_outcome;
2444
2607
 
2445
2608
  if (rkcg->rkcg_state != RD_KAFKA_CGRP_STATE_UP ||
2446
2609
  rkcg->rkcg_join_state != RD_KAFKA_CGRP_JOIN_STATE_INIT ||
@@ -2474,8 +2637,9 @@ static void rd_kafka_cgrp_join(rd_kafka_cgrp_t *rkcg) {
2474
2637
  */
2475
2638
  /* We need up-to-date full metadata to continue,
2476
2639
  * refresh metadata if necessary. */
2477
- if (rd_kafka_cgrp_metadata_refresh(rkcg, &metadata_age,
2478
- "consumer join") == 1) {
2640
+ metadata_refresh_outcome = rd_kafka_cgrp_metadata_refresh(
2641
+ rkcg, &metadata_age, cgrp_subscription_version, "consumer join");
2642
+ if (metadata_refresh_outcome == 1) {
2479
2643
  rd_kafka_dbg(rkcg->rkcg_rk, CGRP | RD_KAFKA_DBG_CONSUMER,
2480
2644
  "JOIN",
2481
2645
  "Group \"%.*s\": "
@@ -2492,6 +2656,14 @@ static void rd_kafka_cgrp_join(rd_kafka_cgrp_t *rkcg) {
2492
2656
  rkcg, RD_KAFKA_CGRP_JOIN_STATE_WAIT_METADATA);
2493
2657
 
2494
2658
  return; /* ^ async call */
2659
+ } else if (metadata_refresh_outcome == -1) {
2660
+ rd_kafka_dbg(rkcg->rkcg_rk, CGRP | RD_KAFKA_DBG_CONSUMER,
2661
+ "JOIN",
2662
+ "Group \"%.*s\": "
2663
+ "postponing join until up-to-date "
2664
+ "metadata can be requested",
2665
+ RD_KAFKAP_STR_PR(rkcg->rkcg_group_id));
2666
+ return; /* ^ async call */
2495
2667
  }
2496
2668
 
2497
2669
  if (rd_list_empty(rkcg->rkcg_subscribed_topics))
@@ -2578,7 +2750,7 @@ static rd_bool_t rd_kafka_cgrp_update_subscribed_topics(rd_kafka_cgrp_t *rkcg,
2578
2750
  "clearing subscribed topics list (%d)",
2579
2751
  RD_KAFKAP_STR_PR(rkcg->rkcg_group_id),
2580
2752
  rd_list_cnt(rkcg->rkcg_subscribed_topics));
2581
- tinfos = rd_list_new(0, (void *)rd_kafka_topic_info_destroy);
2753
+ tinfos = rd_list_new(0, rd_kafka_topic_info_destroy_free);
2582
2754
 
2583
2755
  } else {
2584
2756
  if (rd_list_cnt(tinfos) == 0)
@@ -2879,7 +3051,8 @@ void rd_kafka_cgrp_consumer_next_target_assignment_request_metadata(
2879
3051
  rd_kafka_op_set_replyq(rko, rkcg->rkcg_ops, NULL);
2880
3052
  rd_kafka_MetadataRequest(
2881
3053
  rkb, NULL, missing_topic_ids, "ConsumerGroupHeartbeat API Response",
2882
- rd_false /*!allow_auto_create*/, rd_false, rd_false, rko);
3054
+ rd_false /*!allow_auto_create*/, rd_false,
3055
+ -1 /* no subscription version is used */, rd_false, rko);
2883
3056
  rd_list_destroy(missing_topic_ids);
2884
3057
  }
2885
3058
 
@@ -2896,7 +3069,7 @@ void rd_kafka_cgrp_handle_ConsumerGroupHeartbeat(rd_kafka_t *rk,
2896
3069
  const int log_decode_errors = LOG_ERR;
2897
3070
  int16_t error_code = 0;
2898
3071
  int actions = 0;
2899
- rd_kafkap_str_t error_str;
3072
+ rd_kafkap_str_t error_str = RD_KAFKAP_STR_INITIALIZER_EMPTY;
2900
3073
  rd_kafkap_str_t member_id;
2901
3074
  int32_t member_epoch;
2902
3075
  int32_t heartbeat_interval_ms;
@@ -2920,19 +3093,30 @@ void rd_kafka_cgrp_handle_ConsumerGroupHeartbeat(rd_kafka_t *rk,
2920
3093
  }
2921
3094
 
2922
3095
  rd_kafka_buf_read_str(rkbuf, &member_id);
2923
- rd_kafka_buf_read_i32(rkbuf, &member_epoch);
2924
- rd_kafka_buf_read_i32(rkbuf, &heartbeat_interval_ms);
2925
-
2926
- int8_t are_assignments_present;
2927
- rd_kafka_buf_read_i8(rkbuf, &are_assignments_present);
2928
3096
  if (!RD_KAFKAP_STR_IS_NULL(&member_id)) {
2929
3097
  rd_kafka_cgrp_set_member_id(rkcg, member_id.str);
2930
3098
  }
3099
+
3100
+ rd_kafka_buf_read_i32(rkbuf, &member_epoch);
2931
3101
  rkcg->rkcg_generation_id = member_epoch;
3102
+
3103
+ rd_kafka_dbg(rk, CGRP, "HEARTBEAT",
3104
+ "ConsumerGroupHeartbeat response received for "
3105
+ "member id \"%.*s\" with epoch %d",
3106
+ RD_KAFKAP_STR_PR(&member_id), member_epoch);
3107
+
3108
+ if (rd_kafka_cgrp_will_leave(rkcg)) {
3109
+ err = RD_KAFKA_RESP_ERR__OUTDATED;
3110
+ goto err;
3111
+ }
3112
+
3113
+ rd_kafka_buf_read_i32(rkbuf, &heartbeat_interval_ms);
2932
3114
  if (heartbeat_interval_ms > 0) {
2933
3115
  rkcg->rkcg_heartbeat_intvl_ms = heartbeat_interval_ms;
2934
3116
  }
2935
3117
 
3118
+ int8_t are_assignments_present;
3119
+ rd_kafka_buf_read_i8(rkbuf, &are_assignments_present);
2936
3120
  if (are_assignments_present == 1) {
2937
3121
  rd_kafka_topic_partition_list_t *assigned_topic_partitions;
2938
3122
  const rd_kafka_topic_partition_field_t assignments_fields[] = {
@@ -2952,11 +3136,10 @@ void rd_kafka_cgrp_handle_ConsumerGroupHeartbeat(rd_kafka_t *rk,
2952
3136
  sizeof(assigned_topic_partitions_str), 0);
2953
3137
  }
2954
3138
 
2955
- rd_kafka_dbg(
2956
- rk, CGRP, "HEARTBEAT",
2957
- "ConsumerGroupHeartbeat response received target "
2958
- "assignment \"%s\"",
2959
- assigned_topic_partitions_str);
3139
+ rd_kafka_dbg(rk, CGRP, "HEARTBEAT",
3140
+ "ConsumerGroupHeartbeat received target "
3141
+ "assignment \"%s\"",
3142
+ assigned_topic_partitions_str);
2960
3143
  }
2961
3144
 
2962
3145
  if (assigned_topic_partitions) {
@@ -3038,11 +3221,16 @@ void rd_kafka_cgrp_handle_ConsumerGroupHeartbeat(rd_kafka_t *rk,
3038
3221
  }
3039
3222
  }
3040
3223
 
3224
+ if (rd_kafka_cgrp_consumer_subscription_preconditions_met(rkcg))
3225
+ rd_kafka_cgrp_consumer_expedite_next_heartbeat(
3226
+ rkcg, "send new subscription");
3227
+
3041
3228
  rkcg->rkcg_flags &= ~RD_KAFKA_CGRP_F_HEARTBEAT_IN_TRANSIT;
3042
3229
  rkcg->rkcg_consumer_flags &=
3043
3230
  ~RD_KAFKA_CGRP_CONSUMER_F_SENDING_NEW_SUBSCRIPTION &
3044
3231
  ~RD_KAFKA_CGRP_CONSUMER_F_SEND_FULL_REQUEST &
3045
3232
  ~RD_KAFKA_CGRP_CONSUMER_F_SENDING_ACK;
3233
+ rd_kafka_cgrp_maybe_clear_heartbeat_failed_err(rkcg);
3046
3234
  rkcg->rkcg_last_heartbeat_err = RD_KAFKA_RESP_ERR_NO_ERROR;
3047
3235
  rkcg->rkcg_expedite_heartbeat_retries = 0;
3048
3236
 
@@ -3110,8 +3298,13 @@ err:
3110
3298
  actions = RD_KAFKA_ERR_ACTION_FATAL;
3111
3299
  break;
3112
3300
  default:
3113
- actions = rd_kafka_err_action(rkb, err, request,
3114
- RD_KAFKA_ERR_ACTION_END);
3301
+ actions = rd_kafka_err_action(
3302
+ rkb, err, request,
3303
+
3304
+ RD_KAFKA_ERR_ACTION_SPECIAL,
3305
+ RD_KAFKA_RESP_ERR_TOPIC_AUTHORIZATION_FAILED,
3306
+
3307
+ RD_KAFKA_ERR_ACTION_END);
3115
3308
  break;
3116
3309
  }
3117
3310
 
@@ -3139,12 +3332,31 @@ err:
3139
3332
  rkcg->rkcg_consumer_flags |=
3140
3333
  RD_KAFKA_CGRP_CONSUMER_F_SEND_FULL_REQUEST;
3141
3334
  rd_kafka_cgrp_coord_query(rkcg, rd_kafka_err2str(err));
3142
- rd_kafka_cgrp_consumer_expedite_next_heartbeat(
3143
- rkcg, "coordinator query");
3335
+ /* If coordinator changes, HB will be expedited. */
3336
+ }
3337
+
3338
+ if (actions & RD_KAFKA_ERR_ACTION_SPECIAL) {
3339
+ rd_ts_t min_error_interval =
3340
+ RD_MAX(rkcg->rkcg_heartbeat_intvl_ms * 1000,
3341
+ /* default group.consumer.heartbeat.interval.ms */
3342
+ 5000000);
3343
+ if (rkcg->rkcg_last_err != err ||
3344
+ (rd_clock() >
3345
+ rkcg->rkcg_ts_last_err + min_error_interval)) {
3346
+ rd_kafka_cgrp_set_last_err(rkcg, err);
3347
+ rd_kafka_consumer_err(
3348
+ rkcg->rkcg_q, rd_kafka_broker_id(rkb), err, 0, NULL,
3349
+ NULL, err,
3350
+ "ConsumerGroupHeartbeat failed: %s%s%.*s",
3351
+ rd_kafka_err2str(err),
3352
+ RD_KAFKAP_STR_LEN(&error_str) ? ": " : "",
3353
+ RD_KAFKAP_STR_PR(&error_str));
3354
+ }
3144
3355
  }
3145
3356
 
3146
3357
  if (actions & RD_KAFKA_ERR_ACTION_RETRY &&
3147
3358
  rkcg->rkcg_flags & RD_KAFKA_CGRP_F_SUBSCRIPTION &&
3359
+ !rd_kafka_cgrp_will_leave(rkcg) &&
3148
3360
  rd_kafka_buf_retry(rkb, request)) {
3149
3361
  /* Retry */
3150
3362
  rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_HEARTBEAT_IN_TRANSIT;
@@ -3331,13 +3543,15 @@ static void rd_kafka_cgrp_terminated(rd_kafka_cgrp_t *rkcg) {
3331
3543
 
3332
3544
  rd_kafka_q_purge(rkcg->rkcg_wait_coord_q);
3333
3545
 
3334
- /* Disable and empty ops queue since there will be no
3546
+ /* Disable ops queue since there will be no
3335
3547
  * (broker) thread serving it anymore after the unassign_broker
3336
3548
  * below.
3337
- * This prevents hang on destroy where responses are enqueued on
3338
- * rkcg_ops without anything serving the queue. */
3549
+ * As queue is forwarded to rk_ops, it cannot be purged,
3550
+ * so consumer group operation need to be served with a no-op
3551
+ * when `rkcg_terminated` is true. */
3552
+
3553
+ rd_atomic32_set(&rkcg->rkcg_terminated, rd_true);
3339
3554
  rd_kafka_q_disable(rkcg->rkcg_ops);
3340
- rd_kafka_q_purge(rkcg->rkcg_ops);
3341
3555
 
3342
3556
  if (rkcg->rkcg_curr_coord)
3343
3557
  rd_kafka_cgrp_coord_clear_broker(rkcg);
@@ -3347,8 +3561,6 @@ static void rd_kafka_cgrp_terminated(rd_kafka_cgrp_t *rkcg) {
3347
3561
  rkcg->rkcg_coord = NULL;
3348
3562
  }
3349
3563
 
3350
- rd_atomic32_set(&rkcg->rkcg_terminated, rd_true);
3351
-
3352
3564
  rd_kafka_dbg(rkcg->rkcg_rk, CGRP, "CGRPTERM",
3353
3565
  "Consumer group sub-system terminated%s",
3354
3566
  rkcg->rkcg_reply_rko ? " (will enqueue reply)" : "");
@@ -3793,7 +4005,12 @@ static void rd_kafka_cgrp_op_handle_OffsetCommit(rd_kafka_t *rk,
3793
4005
  break;
3794
4006
 
3795
4007
  case RD_KAFKA_RESP_ERR_ILLEGAL_GENERATION:
3796
- /* Revoke assignment and rebalance on illegal generation */
4008
+ /* Revoke assignment and rebalance on illegal generation,
4009
+ * only if not rebalancing, because a new generation id
4010
+ * can be received soon after this error. */
4011
+ if (RD_KAFKA_CGRP_REBALANCING(rkcg))
4012
+ break;
4013
+
3797
4014
  rk->rk_cgrp->rkcg_generation_id = -1;
3798
4015
  rd_kafka_cgrp_revoke_all_rejoin_maybe(
3799
4016
  rkcg, rd_true /*assignment is lost*/,
@@ -5136,21 +5353,60 @@ rd_kafka_cgrp_calculate_subscribe_revoking_partitions(
5136
5353
  return revoking;
5137
5354
  }
5138
5355
 
5139
- static void
5356
+ /**
5357
+ * @brief Set the new subscription and increase subscription version.
5358
+ *
5359
+ * @return The new subscription version.
5360
+ */
5361
+ static int32_t
5140
5362
  rd_kafka_cgrp_subscription_set(rd_kafka_cgrp_t *rkcg,
5141
5363
  rd_kafka_topic_partition_list_t *rktparlist) {
5364
+
5365
+ rkcg->rkcg_flags &= ~(RD_KAFKA_CGRP_F_SUBSCRIPTION |
5366
+ RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION);
5367
+ RD_IF_FREE(rkcg->rkcg_subscription,
5368
+ rd_kafka_topic_partition_list_destroy);
5369
+ RD_IF_FREE(rkcg->rkcg_subscription_topics,
5370
+ rd_kafka_topic_partition_list_destroy);
5371
+ RD_IF_FREE(rkcg->rkcg_subscription_regex, rd_kafkap_str_destroy);
5372
+
5142
5373
  rkcg->rkcg_subscription = rktparlist;
5374
+
5143
5375
  if (rkcg->rkcg_subscription) {
5144
- /* Insert all non-wildcard topics in cache immediately.
5145
- * Otherwise a manual full metadata request could
5146
- * not cache the hinted topic and return an
5147
- * UNKNOWN_TOPIC_OR_PART error to the user. See #4589. */
5148
- rd_kafka_metadata_cache_hint_rktparlist(
5149
- rkcg->rkcg_rk, rkcg->rkcg_subscription, NULL,
5150
- 0 /*dont replace*/);
5376
+ rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_SUBSCRIPTION;
5377
+ if (rd_kafka_topic_partition_list_regex_cnt(
5378
+ rkcg->rkcg_subscription) > 0)
5379
+ rkcg->rkcg_flags |=
5380
+ RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION;
5381
+
5382
+ if (rkcg->rkcg_group_protocol ==
5383
+ RD_KAFKA_GROUP_PROTOCOL_CONSUMER) {
5384
+ rkcg->rkcg_subscription_regex =
5385
+ rd_kafka_topic_partition_list_combine_regexes(
5386
+ rkcg->rkcg_subscription);
5387
+ rkcg->rkcg_subscription_topics =
5388
+ rd_kafka_topic_partition_list_remove_regexes(
5389
+ rkcg->rkcg_subscription);
5390
+ rkcg->rkcg_consumer_flags |=
5391
+ RD_KAFKA_CGRP_CONSUMER_F_SUBSCRIBED_ONCE |
5392
+ RD_KAFKA_CGRP_CONSUMER_F_SEND_NEW_SUBSCRIPTION;
5393
+ rd_kafka_cgrp_maybe_clear_heartbeat_failed_err(rkcg);
5394
+ }
5395
+ } else {
5396
+ rkcg->rkcg_subscription_regex = NULL;
5397
+ rkcg->rkcg_subscription_topics = NULL;
5398
+ if (rkcg->rkcg_next_subscription) {
5399
+ /* When unsubscribing clear next subscription too */
5400
+ rd_kafka_topic_partition_list_destroy(
5401
+ rkcg->rkcg_next_subscription);
5402
+ rkcg->rkcg_next_subscription = NULL;
5403
+ }
5151
5404
  }
5405
+
5406
+ return rd_atomic32_add(&rkcg->rkcg_subscription_version, 1);
5152
5407
  }
5153
5408
 
5409
+
5154
5410
  /**
5155
5411
  * @brief Handle a new subscription that is modifying an existing subscription
5156
5412
  * in the COOPERATIVE case.
@@ -5166,11 +5422,7 @@ rd_kafka_cgrp_modify_subscription(rd_kafka_cgrp_t *rkcg,
5166
5422
  rd_kafka_topic_partition_list_t *errored;
5167
5423
  int metadata_age;
5168
5424
  int old_cnt = rkcg->rkcg_subscription->cnt;
5169
-
5170
- rkcg->rkcg_flags &= ~RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION;
5171
-
5172
- if (rd_kafka_topic_partition_list_regex_cnt(rktparlist) > 0)
5173
- rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION;
5425
+ int32_t cgrp_subscription_version;
5174
5426
 
5175
5427
  /* Topics in rkcg_subscribed_topics that don't match any pattern in
5176
5428
  the new subscription. */
@@ -5181,10 +5433,11 @@ rd_kafka_cgrp_modify_subscription(rd_kafka_cgrp_t *rkcg,
5181
5433
  revoking = rd_kafka_cgrp_calculate_subscribe_revoking_partitions(
5182
5434
  rkcg, unsubscribing_topics);
5183
5435
 
5184
- rd_kafka_topic_partition_list_destroy(rkcg->rkcg_subscription);
5185
- rd_kafka_cgrp_subscription_set(rkcg, rktparlist);
5436
+ cgrp_subscription_version =
5437
+ rd_kafka_cgrp_subscription_set(rkcg, rktparlist);
5186
5438
 
5187
5439
  if (rd_kafka_cgrp_metadata_refresh(rkcg, &metadata_age,
5440
+ cgrp_subscription_version,
5188
5441
  "modify subscription") == 1) {
5189
5442
  rd_kafka_dbg(rkcg->rkcg_rk, CGRP | RD_KAFKA_DBG_CONSUMER,
5190
5443
  "MODSUB",
@@ -5227,7 +5480,7 @@ rd_kafka_cgrp_modify_subscription(rd_kafka_cgrp_t *rkcg,
5227
5480
  /* Create a list of the topics in metadata that matches the new
5228
5481
  * subscription */
5229
5482
  tinfos = rd_list_new(rkcg->rkcg_subscription->cnt,
5230
- (void *)rd_kafka_topic_info_destroy);
5483
+ rd_kafka_topic_info_destroy_free);
5231
5484
 
5232
5485
  /* Unmatched topics will be added to the errored list. */
5233
5486
  errored = rd_kafka_topic_partition_list_new(0);
@@ -5267,12 +5520,55 @@ rd_kafka_cgrp_modify_subscription(rd_kafka_cgrp_t *rkcg,
5267
5520
  return RD_KAFKA_RESP_ERR_NO_ERROR;
5268
5521
  }
5269
5522
 
5523
+ /**
5524
+ * Remove existing topic subscription (KIP 848).
5525
+ */
5526
+ static rd_kafka_resp_err_t
5527
+ rd_kafka_cgrp_consumer_unsubscribe(rd_kafka_cgrp_t *rkcg,
5528
+ rd_bool_t leave_group) {
5529
+
5530
+ rd_kafka_dbg(rkcg->rkcg_rk, CGRP, "UNSUBSCRIBE",
5531
+ "Group \"%.*s\": unsubscribe from current %ssubscription "
5532
+ "of size %d (leave group=%s, has joined=%s, %s, "
5533
+ "join-state %s)",
5534
+ RD_KAFKAP_STR_PR(rkcg->rkcg_group_id),
5535
+ rkcg->rkcg_subscription ? "" : "unset ",
5536
+ rkcg->rkcg_subscription ? rkcg->rkcg_subscription->cnt : 0,
5537
+ RD_STR_ToF(leave_group),
5538
+ RD_STR_ToF(RD_KAFKA_CGRP_HAS_JOINED(rkcg)),
5539
+ rkcg->rkcg_member_id ? rkcg->rkcg_member_id->str : "n/a",
5540
+ rd_kafka_cgrp_join_state_names[rkcg->rkcg_join_state]);
5541
+
5542
+ rd_kafka_timer_stop(&rkcg->rkcg_rk->rk_timers,
5543
+ &rkcg->rkcg_max_poll_interval_tmr, 1 /*lock*/);
5544
+
5545
+ rd_kafka_cgrp_subscription_set(rkcg, NULL);
5546
+
5547
+ /* When group is rejoining the leave group call is either:
5548
+ * - been done on max.poll.interval reached
5549
+ * - not necessary because member has been fenced
5550
+ *
5551
+ * When group is already leaving we just wait that previous
5552
+ * leave request finishes.
5553
+ */
5554
+ if (leave_group && !rd_kafka_cgrp_consumer_will_rejoin(rkcg) &&
5555
+ RD_KAFKA_CGRP_HAS_JOINED(rkcg) && !rd_kafka_cgrp_will_leave(rkcg)) {
5556
+ rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_LEAVE_ON_UNASSIGN_DONE;
5557
+ rd_kafka_cgrp_revoke_all_rejoin(rkcg, rd_false /*not lost*/,
5558
+ rd_true /*initiating*/,
5559
+ "unsubscribe");
5560
+ }
5561
+
5562
+ return RD_KAFKA_RESP_ERR_NO_ERROR;
5563
+ }
5270
5564
 
5271
5565
  /**
5272
5566
  * Remove existing topic subscription.
5273
5567
  */
5274
5568
  static rd_kafka_resp_err_t rd_kafka_cgrp_unsubscribe(rd_kafka_cgrp_t *rkcg,
5275
5569
  rd_bool_t leave_group) {
5570
+ if (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CONSUMER)
5571
+ return rd_kafka_cgrp_consumer_unsubscribe(rkcg, leave_group);
5276
5572
 
5277
5573
  rd_kafka_dbg(rkcg->rkcg_rk, CGRP, "UNSUBSCRIBE",
5278
5574
  "Group \"%.*s\": unsubscribe from current %ssubscription "
@@ -5289,13 +5585,9 @@ static rd_kafka_resp_err_t rd_kafka_cgrp_unsubscribe(rd_kafka_cgrp_t *rkcg,
5289
5585
  rd_kafka_timer_stop(&rkcg->rkcg_rk->rk_timers,
5290
5586
  &rkcg->rkcg_max_poll_interval_tmr, 1 /*lock*/);
5291
5587
 
5292
- if (rkcg->rkcg_subscription) {
5293
- rd_kafka_topic_partition_list_destroy(rkcg->rkcg_subscription);
5294
- rd_kafka_cgrp_subscription_set(rkcg, NULL);
5295
- }
5588
+ rd_kafka_cgrp_subscription_set(rkcg, NULL);
5296
5589
 
5297
- if (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CLASSIC)
5298
- rd_kafka_cgrp_update_subscribed_topics(rkcg, NULL);
5590
+ rd_kafka_cgrp_update_subscribed_topics(rkcg, NULL);
5299
5591
 
5300
5592
  /*
5301
5593
  * Clean-up group leader duties, if any.
@@ -5311,9 +5603,6 @@ static rd_kafka_resp_err_t rd_kafka_cgrp_unsubscribe(rd_kafka_cgrp_t *rkcg,
5311
5603
  rd_true /*initiating*/,
5312
5604
  "unsubscribe");
5313
5605
 
5314
- rkcg->rkcg_flags &= ~(RD_KAFKA_CGRP_F_SUBSCRIPTION |
5315
- RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION);
5316
-
5317
5606
  return RD_KAFKA_RESP_ERR_NO_ERROR;
5318
5607
  }
5319
5608
 
@@ -5323,7 +5612,7 @@ static rd_kafka_resp_err_t rd_kafka_cgrp_unsubscribe(rd_kafka_cgrp_t *rkcg,
5323
5612
  static rd_kafka_resp_err_t
5324
5613
  rd_kafka_cgrp_subscribe(rd_kafka_cgrp_t *rkcg,
5325
5614
  rd_kafka_topic_partition_list_t *rktparlist) {
5326
-
5615
+ int32_t subscription_version;
5327
5616
  rd_kafka_dbg(rkcg->rkcg_rk, CGRP | RD_KAFKA_DBG_CONSUMER, "SUBSCRIBE",
5328
5617
  "Group \"%.*s\": subscribe to new %ssubscription "
5329
5618
  "of %d topics (join-state %s)",
@@ -5384,14 +5673,9 @@ rd_kafka_cgrp_subscribe(rd_kafka_cgrp_t *rkcg,
5384
5673
  if (!rktparlist)
5385
5674
  return RD_KAFKA_RESP_ERR_NO_ERROR;
5386
5675
 
5387
- rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_SUBSCRIPTION;
5388
-
5389
- if (rd_kafka_topic_partition_list_regex_cnt(rktparlist) > 0)
5390
- rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION;
5391
-
5392
- rd_kafka_cgrp_subscription_set(rkcg, rktparlist);
5676
+ subscription_version = rd_kafka_cgrp_subscription_set(rkcg, rktparlist);
5393
5677
 
5394
- rd_kafka_cgrp_join(rkcg);
5678
+ rd_kafka_cgrp_join(rkcg, subscription_version);
5395
5679
 
5396
5680
  return RD_KAFKA_RESP_ERR_NO_ERROR;
5397
5681
  }
@@ -5440,6 +5724,11 @@ void rd_kafka_cgrp_terminate0(rd_kafka_cgrp_t *rkcg, rd_kafka_op_t *rko) {
5440
5724
  /* Mark for stopping, the actual state transition
5441
5725
  * is performed when all toppars have left. */
5442
5726
  rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_TERMINATE;
5727
+ if (rkcg->rkcg_group_protocol == RD_KAFKA_GROUP_PROTOCOL_CONSUMER) {
5728
+ rkcg->rkcg_consumer_flags &=
5729
+ ~RD_KAFKA_CGRP_CONSUMER_F_WAIT_REJOIN &
5730
+ ~RD_KAFKA_CGRP_CONSUMER_F_WAIT_REJOIN_TO_COMPLETE;
5731
+ }
5443
5732
  rkcg->rkcg_ts_terminate = rd_clock();
5444
5733
  rkcg->rkcg_reply_rko = rko;
5445
5734
 
@@ -5749,7 +6038,8 @@ static void rd_kafka_cgrp_join_state_serve(rd_kafka_cgrp_t *rkcg) {
5749
6038
 
5750
6039
  if (rd_interval_immediate(&rkcg->rkcg_join_intvl, 1000 * 1000,
5751
6040
  now) > 0)
5752
- rd_kafka_cgrp_join(rkcg);
6041
+ rd_kafka_cgrp_join(
6042
+ rkcg, -1 /* current subscription version */);
5753
6043
  break;
5754
6044
 
5755
6045
  case RD_KAFKA_CGRP_JOIN_STATE_WAIT_JOIN:
@@ -5774,16 +6064,18 @@ static void rd_kafka_cgrp_join_state_serve(rd_kafka_cgrp_t *rkcg) {
5774
6064
  }
5775
6065
  }
5776
6066
 
6067
+
5777
6068
  void rd_kafka_cgrp_consumer_group_heartbeat(rd_kafka_cgrp_t *rkcg,
5778
6069
  rd_bool_t full_request,
5779
6070
  rd_bool_t send_ack) {
5780
6071
 
5781
- rd_kafkap_str_t *rkcg_group_instance_id = NULL;
5782
- rd_kafkap_str_t *rkcg_client_rack = NULL;
5783
- int max_poll_interval_ms = -1;
5784
- rd_kafka_topic_partition_list_t *rkcg_subscription = NULL;
5785
- rd_kafkap_str_t *rkcg_group_remote_assignor = NULL;
5786
- rd_kafka_topic_partition_list_t *rkcg_group_assignment = NULL;
6072
+ rd_kafkap_str_t *rkcg_group_instance_id = NULL;
6073
+ rd_kafkap_str_t *rkcg_client_rack = NULL;
6074
+ int max_poll_interval_ms = -1;
6075
+ rd_kafka_topic_partition_list_t *rkcg_subscription_topics = NULL;
6076
+ rd_kafkap_str_t *rkcg_subscription_regex = NULL;
6077
+ rd_kafkap_str_t *rkcg_group_remote_assignor = NULL;
6078
+ rd_kafka_topic_partition_list_t *rkcg_group_assignment = NULL;
5787
6079
  int32_t member_epoch = rkcg->rkcg_generation_id;
5788
6080
  if (member_epoch < 0)
5789
6081
  member_epoch = 0;
@@ -5797,7 +6089,8 @@ void rd_kafka_cgrp_consumer_group_heartbeat(rd_kafka_cgrp_t *rkcg,
5797
6089
  rkcg_client_rack = rkcg->rkcg_client_rack;
5798
6090
  max_poll_interval_ms =
5799
6091
  rkcg->rkcg_rk->rk_conf.max_poll_interval_ms;
5800
- rkcg_subscription = rkcg->rkcg_subscription;
6092
+ rkcg_subscription_topics = rkcg->rkcg_subscription_topics;
6093
+ rkcg_subscription_regex = rkcg->rkcg_subscription_regex;
5801
6094
  rkcg_group_remote_assignor = rkcg->rkcg_group_remote_assignor;
5802
6095
  }
5803
6096
 
@@ -5824,23 +6117,22 @@ void rd_kafka_cgrp_consumer_group_heartbeat(rd_kafka_cgrp_t *rkcg,
5824
6117
  rkcg_group_assignment = rkcg->rkcg_current_assignment;
5825
6118
  }
5826
6119
 
5827
- if (rkcg->rkcg_join_state == RD_KAFKA_CGRP_JOIN_STATE_STEADY &&
5828
- (rkcg->rkcg_consumer_flags &
5829
- RD_KAFKA_CGRP_CONSUMER_F_SEND_NEW_SUBSCRIPTION ||
5830
- rkcg->rkcg_consumer_flags &
5831
- RD_KAFKA_CGRP_CONSUMER_F_SENDING_NEW_SUBSCRIPTION)) {
6120
+ if (rd_kafka_cgrp_consumer_subscription_preconditions_met(rkcg) ||
6121
+ rkcg->rkcg_consumer_flags &
6122
+ RD_KAFKA_CGRP_CONSUMER_F_SENDING_NEW_SUBSCRIPTION) {
5832
6123
  rkcg->rkcg_consumer_flags =
5833
6124
  (rkcg->rkcg_consumer_flags &
5834
6125
  ~RD_KAFKA_CGRP_CONSUMER_F_SEND_NEW_SUBSCRIPTION) |
5835
6126
  RD_KAFKA_CGRP_CONSUMER_F_SENDING_NEW_SUBSCRIPTION;
5836
- rkcg_subscription = rkcg->rkcg_subscription;
6127
+ rkcg_subscription_topics = rkcg->rkcg_subscription_topics;
6128
+ rkcg_subscription_regex = rkcg->rkcg_subscription_regex;
5837
6129
 
5838
6130
  if (rd_kafka_is_dbg(rkcg->rkcg_rk, CGRP)) {
5839
6131
  char rkcg_new_subscription_str[512] = "NULL";
5840
6132
 
5841
- if (rkcg_subscription) {
6133
+ if (rkcg->rkcg_subscription) {
5842
6134
  rd_kafka_topic_partition_list_str(
5843
- rkcg_subscription,
6135
+ rkcg->rkcg_subscription,
5844
6136
  rkcg_new_subscription_str,
5845
6137
  sizeof(rkcg_new_subscription_str), 0);
5846
6138
  }
@@ -5855,13 +6147,18 @@ void rd_kafka_cgrp_consumer_group_heartbeat(rd_kafka_cgrp_t *rkcg,
5855
6147
  rd_kafka_ConsumerGroupHeartbeatRequest(
5856
6148
  rkcg->rkcg_coord, rkcg->rkcg_group_id, rkcg->rkcg_member_id,
5857
6149
  member_epoch, rkcg_group_instance_id, rkcg_client_rack,
5858
- max_poll_interval_ms, rkcg_subscription, rkcg_group_remote_assignor,
6150
+ max_poll_interval_ms, rkcg_subscription_topics,
6151
+ rkcg_subscription_regex, rkcg_group_remote_assignor,
5859
6152
  rkcg_group_assignment, RD_KAFKA_REPLYQ(rkcg->rkcg_ops, 0),
5860
6153
  rd_kafka_cgrp_handle_ConsumerGroupHeartbeat, NULL);
5861
6154
  }
5862
6155
 
5863
6156
  static rd_bool_t
5864
6157
  rd_kafka_cgrp_consumer_heartbeat_preconditions_met(rd_kafka_cgrp_t *rkcg) {
6158
+ rd_dassert(
6159
+ !(rkcg->rkcg_join_state == RD_KAFKA_CGRP_JOIN_STATE_INIT &&
6160
+ rkcg->rkcg_flags & RD_KAFKA_CGRP_F_LEAVE_ON_UNASSIGN_DONE));
6161
+
5865
6162
  if (!(rkcg->rkcg_flags & RD_KAFKA_CGRP_F_SUBSCRIPTION))
5866
6163
  return rd_false;
5867
6164
 
@@ -5876,6 +6173,9 @@ rd_kafka_cgrp_consumer_heartbeat_preconditions_met(rd_kafka_cgrp_t *rkcg) {
5876
6173
  rd_kafka_max_poll_exceeded(rkcg->rkcg_rk))
5877
6174
  return rd_false;
5878
6175
 
6176
+ if (rd_kafka_cgrp_will_leave(rkcg))
6177
+ return rd_false;
6178
+
5879
6179
  return rd_true;
5880
6180
  }
5881
6181
 
@@ -5909,6 +6209,7 @@ void rd_kafka_cgrp_consumer_serve(rd_kafka_cgrp_t *rkcg) {
5909
6209
  case RD_KAFKA_CGRP_JOIN_STATE_INIT:
5910
6210
  rkcg->rkcg_consumer_flags &=
5911
6211
  ~RD_KAFKA_CGRP_CONSUMER_F_WAIT_REJOIN_TO_COMPLETE;
6212
+ rd_kafka_cgrp_consumer_apply_next_subscribe(rkcg);
5912
6213
  full_request = rd_true;
5913
6214
  break;
5914
6215
  case RD_KAFKA_CGRP_JOIN_STATE_STEADY:
@@ -5984,27 +6285,20 @@ rd_kafka_cgrp_consumer_subscribe(rd_kafka_cgrp_t *rkcg,
5984
6285
  return RD_KAFKA_RESP_ERR__FATAL;
5985
6286
  }
5986
6287
 
5987
- rkcg->rkcg_flags &= ~RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION;
5988
6288
  if (rktparlist) {
5989
- if (rkcg->rkcg_subscription)
6289
+ if (rkcg->rkcg_next_subscription)
5990
6290
  rd_kafka_topic_partition_list_destroy(
5991
- rkcg->rkcg_subscription);
5992
-
5993
- rkcg->rkcg_flags |= RD_KAFKA_CGRP_F_SUBSCRIPTION;
5994
-
5995
- if (rd_kafka_topic_partition_list_regex_cnt(rktparlist) > 0)
5996
- rkcg->rkcg_flags |=
5997
- RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION;
5998
-
5999
- rkcg->rkcg_consumer_flags |=
6000
- RD_KAFKA_CGRP_CONSUMER_F_SUBSCRIBED_ONCE |
6001
- RD_KAFKA_CGRP_CONSUMER_F_SEND_NEW_SUBSCRIPTION;
6002
-
6003
- rd_kafka_cgrp_subscription_set(rkcg, rktparlist);
6004
- rd_kafka_cgrp_consumer_expedite_next_heartbeat(
6005
- rkcg, "subscription changed");
6291
+ rkcg->rkcg_next_subscription);
6292
+ rkcg->rkcg_next_subscription = rktparlist;
6293
+
6294
+ /* If member is leaving, new subscription
6295
+ * will be applied after the leave
6296
+ * ConsumerGroupHeartbeat */
6297
+ if (!rd_kafka_cgrp_will_leave(rkcg))
6298
+ rd_kafka_cgrp_consumer_apply_next_subscribe(rkcg);
6006
6299
  } else {
6007
- rd_kafka_cgrp_unsubscribe(rkcg, rd_true /*leave group*/);
6300
+ rd_kafka_cgrp_consumer_unsubscribe(rkcg,
6301
+ rd_true /*leave group*/);
6008
6302
  }
6009
6303
 
6010
6304
  return RD_KAFKA_RESP_ERR_NO_ERROR;
@@ -6024,6 +6318,9 @@ static void rd_kafka_cgrp_consumer_incr_unassign_done(rd_kafka_cgrp_t *rkcg) {
6024
6318
  "unassign",
6025
6319
  rkcg->rkcg_group_id->str);
6026
6320
  rd_kafka_cgrp_unassign(rkcg);
6321
+
6322
+ /* Leave group, if desired. */
6323
+ rd_kafka_cgrp_leave_maybe(rkcg);
6027
6324
  return;
6028
6325
  }
6029
6326
 
@@ -6103,24 +6400,10 @@ static void rd_kafka_cgrp_consumer_assignment_done(rd_kafka_cgrp_t *rkcg) {
6103
6400
  rd_kafka_cgrp_consumer_incr_unassign_done(rkcg);
6104
6401
  break;
6105
6402
 
6106
- case RD_KAFKA_CGRP_JOIN_STATE_STEADY:
6107
- rd_kafka_cgrp_consumer_expedite_next_heartbeat(
6108
- rkcg, "back to steady state");
6109
-
6110
- if (rkcg->rkcg_rebalance_rejoin) {
6111
- rkcg->rkcg_rebalance_rejoin = rd_false;
6112
- rd_kafka_cgrp_rejoin(
6113
- rkcg,
6114
- "rejoining group to redistribute "
6115
- "previously owned partitions to other "
6116
- "group members");
6117
- break;
6118
- }
6119
-
6120
- /* FALLTHRU */
6403
+ case RD_KAFKA_CGRP_JOIN_STATE_INIT:
6404
+ case RD_KAFKA_CGRP_JOIN_STATE_STEADY: {
6121
6405
 
6122
- case RD_KAFKA_CGRP_JOIN_STATE_INIT: {
6123
- rd_bool_t still_in_group = rd_true;
6406
+ rd_bool_t not_in_group = rd_false;
6124
6407
  /*
6125
6408
  * There maybe a case when there are no assignments are
6126
6409
  * assigned to this consumer. In this case, while terminating
@@ -6128,18 +6411,38 @@ static void rd_kafka_cgrp_consumer_assignment_done(rd_kafka_cgrp_t *rkcg) {
6128
6411
  * to intermediate state. In this scenario, last leave call is
6129
6412
  * done from here.
6130
6413
  */
6131
- still_in_group &= !rd_kafka_cgrp_leave_maybe(rkcg);
6414
+ not_in_group |= rd_kafka_cgrp_leave_maybe(rkcg);
6132
6415
 
6133
6416
  /* Check if cgrp is trying to terminate, which is safe to do
6134
6417
  * in these two states. Otherwise we'll need to wait for
6135
6418
  * the current state to decommission. */
6136
- still_in_group &= !rd_kafka_cgrp_try_terminate(rkcg);
6419
+ not_in_group |= rd_kafka_cgrp_try_terminate(rkcg);
6137
6420
 
6138
- if (still_in_group)
6421
+ if (not_in_group)
6422
+ break;
6423
+
6424
+ if (rkcg->rkcg_join_state == RD_KAFKA_CGRP_JOIN_STATE_INIT) {
6425
+ rd_kafka_cgrp_consumer_expedite_next_heartbeat(
6426
+ rkcg, "Assignment Done: in init state");
6427
+ } else if (rkcg->rkcg_rebalance_rejoin) {
6428
+ /* No need to expedite the HB here as it's being
6429
+ * expedited in the rejoin call.*/
6430
+ rkcg->rkcg_rebalance_rejoin = rd_false;
6431
+ rd_kafka_cgrp_rejoin(
6432
+ rkcg,
6433
+ "Assignment Done: rejoining group to redistribute "
6434
+ "previously owned partitions to other "
6435
+ "group members");
6436
+ } else if (rkcg->rkcg_consumer_flags &
6437
+ RD_KAFKA_CGRP_CONSUMER_F_WAIT_ACK) {
6139
6438
  rd_kafka_cgrp_consumer_expedite_next_heartbeat(
6140
- rkcg, "back to init state");
6439
+ rkcg,
6440
+ "Assignment Done: in steady state, waiting for "
6441
+ "ack");
6442
+ }
6141
6443
  break;
6142
6444
  }
6445
+
6143
6446
  default:
6144
6447
  break;
6145
6448
  }
@@ -6358,6 +6661,12 @@ static rd_kafka_op_res_t rd_kafka_cgrp_op_serve(rd_kafka_t *rk,
6358
6661
  rd_kafka_toppar_t *rktp;
6359
6662
  rd_kafka_resp_err_t err;
6360
6663
  const int silent_op = rko->rko_type == RD_KAFKA_OP_RECV_BUF;
6664
+ if (unlikely(rd_atomic32_get(&rkcg->rkcg_terminated) == rd_true)) {
6665
+ if (rko)
6666
+ rd_kafka_op_destroy(rko);
6667
+ return RD_KAFKA_OP_RES_HANDLED;
6668
+ }
6669
+
6361
6670
 
6362
6671
  rktp = rko->rko_rktp;
6363
6672
 
@@ -6615,7 +6924,7 @@ void rd_kafka_cgrp_metadata_update_check(rd_kafka_cgrp_t *rkcg,
6615
6924
  * Create a list of the topics in metadata that matches our subscription
6616
6925
  */
6617
6926
  tinfos = rd_list_new(rkcg->rkcg_subscription->cnt,
6618
- (void *)rd_kafka_topic_info_destroy);
6927
+ rd_kafka_topic_info_destroy_free);
6619
6928
 
6620
6929
  if (rkcg->rkcg_flags & RD_KAFKA_CGRP_F_WILDCARD_SUBSCRIPTION)
6621
6930
  rd_kafka_metadata_topic_match(rkcg->rkcg_rk, tinfos,
@@ -6754,11 +7063,27 @@ rd_kafka_consumer_group_metadata(rd_kafka_t *rk) {
6754
7063
  return cgmetadata;
6755
7064
  }
6756
7065
 
7066
+ const char *rd_kafka_consumer_group_metadata_group_id(
7067
+ const rd_kafka_consumer_group_metadata_t *group_metadata) {
7068
+ return group_metadata->group_id;
7069
+ }
7070
+
6757
7071
  const char *rd_kafka_consumer_group_metadata_member_id(
6758
7072
  const rd_kafka_consumer_group_metadata_t *group_metadata) {
6759
7073
  return group_metadata->member_id;
6760
7074
  }
6761
7075
 
7076
+ const char *rd_kafka_consumer_group_metadata_group_instance_id(
7077
+ const rd_kafka_consumer_group_metadata_t *group_metadata) {
7078
+ return group_metadata->group_instance_id;
7079
+ }
7080
+
7081
+ int32_t rd_kafka_consumer_group_metadata_generation_id(
7082
+ const rd_kafka_consumer_group_metadata_t *group_metadata) {
7083
+ return group_metadata->generation_id;
7084
+ }
7085
+
7086
+
6762
7087
  void rd_kafka_consumer_group_metadata_destroy(
6763
7088
  rd_kafka_consumer_group_metadata_t *cgmetadata) {
6764
7089
  rd_free(cgmetadata->group_id);
@@ -7176,7 +7501,7 @@ static int unittest_list_to_map(void) {
7176
7501
  }
7177
7502
 
7178
7503
  int unittest_member_metadata_serdes(void) {
7179
- rd_list_t *topics = rd_list_new(0, (void *)rd_kafka_topic_info_destroy);
7504
+ rd_list_t *topics = rd_list_new(0, rd_kafka_topic_info_destroy_free);
7180
7505
  rd_kafka_topic_partition_list_t *owned_partitions =
7181
7506
  rd_kafka_topic_partition_list_new(0);
7182
7507
  rd_kafkap_str_t *rack_id = rd_kafkap_str_new("myrack", -1);