@ohos-ports/confluentinc-kafka-javascript 1.10.0-beta.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 (628) hide show
  1. package/CHANGELOG.md +457 -0
  2. package/LICENSE.kafkajs +31 -0
  3. package/LICENSE.node-rdkafka +25 -0
  4. package/LICENSE.txt +20 -0
  5. package/README.md +133 -0
  6. package/binding.gyp +184 -0
  7. package/build/Release/confluent-kafka-javascript.node +0 -0
  8. package/build/deps/librdkafka++.so.1 +0 -0
  9. package/build/deps/librdkafka.Makefile +6 -0
  10. package/build/deps/librdkafka.so.1 +0 -0
  11. package/build/deps/librdkafka.target.mk +55 -0
  12. package/deps/librdkafka/.clang-format +136 -0
  13. package/deps/librdkafka/.clang-format-cpp +103 -0
  14. package/deps/librdkafka/.dir-locals.el +10 -0
  15. package/deps/librdkafka/.formatignore +35 -0
  16. package/deps/librdkafka/.gdbmacros +19 -0
  17. package/deps/librdkafka/CHANGELOG.md +2477 -0
  18. package/deps/librdkafka/CMakeLists.txt +291 -0
  19. package/deps/librdkafka/CODE_OF_CONDUCT.md +46 -0
  20. package/deps/librdkafka/CONFIGURATION.md +212 -0
  21. package/deps/librdkafka/CONTRIBUTING.md +431 -0
  22. package/deps/librdkafka/Doxyfile +2375 -0
  23. package/deps/librdkafka/INTRODUCTION.md +2752 -0
  24. package/deps/librdkafka/LICENSE +26 -0
  25. package/deps/librdkafka/LICENSE.cjson +22 -0
  26. package/deps/librdkafka/LICENSE.crc32c +28 -0
  27. package/deps/librdkafka/LICENSE.fnv1a +18 -0
  28. package/deps/librdkafka/LICENSE.hdrhistogram +27 -0
  29. package/deps/librdkafka/LICENSE.lz4 +26 -0
  30. package/deps/librdkafka/LICENSE.murmur2 +25 -0
  31. package/deps/librdkafka/LICENSE.nanopb +22 -0
  32. package/deps/librdkafka/LICENSE.opentelemetry +203 -0
  33. package/deps/librdkafka/LICENSE.pycrc +23 -0
  34. package/deps/librdkafka/LICENSE.queue +31 -0
  35. package/deps/librdkafka/LICENSE.regexp +5 -0
  36. package/deps/librdkafka/LICENSE.snappy +36 -0
  37. package/deps/librdkafka/LICENSE.tinycthread +26 -0
  38. package/deps/librdkafka/LICENSE.wingetopt +49 -0
  39. package/deps/librdkafka/LICENSES.txt +625 -0
  40. package/deps/librdkafka/Makefile +125 -0
  41. package/deps/librdkafka/README.md +201 -0
  42. package/deps/librdkafka/README.win32 +26 -0
  43. package/deps/librdkafka/STATISTICS.md +624 -0
  44. package/deps/librdkafka/configure +214 -0
  45. package/deps/librdkafka/configure.self +341 -0
  46. package/deps/librdkafka/dev-conf.sh +123 -0
  47. package/deps/librdkafka/lds-gen.py +73 -0
  48. package/deps/librdkafka/mainpage.doxy +40 -0
  49. package/deps/librdkafka/mklove/Makefile.base +329 -0
  50. package/deps/librdkafka/mklove/modules/configure.atomics +144 -0
  51. package/deps/librdkafka/mklove/modules/configure.base +2484 -0
  52. package/deps/librdkafka/mklove/modules/configure.builtin +70 -0
  53. package/deps/librdkafka/mklove/modules/configure.cc +186 -0
  54. package/deps/librdkafka/mklove/modules/configure.cxx +8 -0
  55. package/deps/librdkafka/mklove/modules/configure.fileversion +65 -0
  56. package/deps/librdkafka/mklove/modules/configure.gitversion +29 -0
  57. package/deps/librdkafka/mklove/modules/configure.good_cflags +18 -0
  58. package/deps/librdkafka/mklove/modules/configure.host +132 -0
  59. package/deps/librdkafka/mklove/modules/configure.lib +49 -0
  60. package/deps/librdkafka/mklove/modules/configure.libcurl +99 -0
  61. package/deps/librdkafka/mklove/modules/configure.libsasl2 +36 -0
  62. package/deps/librdkafka/mklove/modules/configure.libssl +147 -0
  63. package/deps/librdkafka/mklove/modules/configure.libzstd +58 -0
  64. package/deps/librdkafka/mklove/modules/configure.parseversion +95 -0
  65. package/deps/librdkafka/mklove/modules/configure.pic +16 -0
  66. package/deps/librdkafka/mklove/modules/configure.socket +20 -0
  67. package/deps/librdkafka/mklove/modules/configure.zlib +61 -0
  68. package/deps/librdkafka/mklove/modules/patches/README.md +8 -0
  69. package/deps/librdkafka/mklove/modules/patches/libcurl.0000-no-runtime-linking-check.patch +11 -0
  70. package/deps/librdkafka/mklove/modules/patches/libssl.0000-osx-rand-include-fix-OpenSSL-PR16409.patch +56 -0
  71. package/deps/librdkafka/service.yml +182 -0
  72. package/deps/librdkafka/src/CMakeLists.txt +379 -0
  73. package/deps/librdkafka/src/Makefile +105 -0
  74. package/deps/librdkafka/src/README.lz4.md +30 -0
  75. package/deps/librdkafka/src/cJSON.c +3191 -0
  76. package/deps/librdkafka/src/cJSON.h +306 -0
  77. package/deps/librdkafka/src/crc32c.c +430 -0
  78. package/deps/librdkafka/src/crc32c.h +38 -0
  79. package/deps/librdkafka/src/generate_proto.sh +66 -0
  80. package/deps/librdkafka/src/librdkafka_cgrp_synch.png +0 -0
  81. package/deps/librdkafka/src/lz4.c +2727 -0
  82. package/deps/librdkafka/src/lz4.h +842 -0
  83. package/deps/librdkafka/src/lz4frame.c +2078 -0
  84. package/deps/librdkafka/src/lz4frame.h +692 -0
  85. package/deps/librdkafka/src/lz4frame_static.h +47 -0
  86. package/deps/librdkafka/src/lz4hc.c +1631 -0
  87. package/deps/librdkafka/src/lz4hc.h +413 -0
  88. package/deps/librdkafka/src/nanopb/pb.h +917 -0
  89. package/deps/librdkafka/src/nanopb/pb_common.c +388 -0
  90. package/deps/librdkafka/src/nanopb/pb_common.h +49 -0
  91. package/deps/librdkafka/src/nanopb/pb_decode.c +1727 -0
  92. package/deps/librdkafka/src/nanopb/pb_decode.h +193 -0
  93. package/deps/librdkafka/src/nanopb/pb_encode.c +1000 -0
  94. package/deps/librdkafka/src/nanopb/pb_encode.h +185 -0
  95. package/deps/librdkafka/src/opentelemetry/common.pb.c +32 -0
  96. package/deps/librdkafka/src/opentelemetry/common.pb.h +170 -0
  97. package/deps/librdkafka/src/opentelemetry/metrics.options +2 -0
  98. package/deps/librdkafka/src/opentelemetry/metrics.pb.c +67 -0
  99. package/deps/librdkafka/src/opentelemetry/metrics.pb.h +966 -0
  100. package/deps/librdkafka/src/opentelemetry/resource.pb.c +12 -0
  101. package/deps/librdkafka/src/opentelemetry/resource.pb.h +58 -0
  102. package/deps/librdkafka/src/queue.h +850 -0
  103. package/deps/librdkafka/src/rd.h +592 -0
  104. package/deps/librdkafka/src/rdaddr.c +255 -0
  105. package/deps/librdkafka/src/rdaddr.h +202 -0
  106. package/deps/librdkafka/src/rdatomic.h +262 -0
  107. package/deps/librdkafka/src/rdavg.h +260 -0
  108. package/deps/librdkafka/src/rdavl.c +210 -0
  109. package/deps/librdkafka/src/rdavl.h +250 -0
  110. package/deps/librdkafka/src/rdbase64.c +200 -0
  111. package/deps/librdkafka/src/rdbase64.h +43 -0
  112. package/deps/librdkafka/src/rdbuf.c +1884 -0
  113. package/deps/librdkafka/src/rdbuf.h +375 -0
  114. package/deps/librdkafka/src/rdcrc32.c +114 -0
  115. package/deps/librdkafka/src/rdcrc32.h +170 -0
  116. package/deps/librdkafka/src/rddl.c +179 -0
  117. package/deps/librdkafka/src/rddl.h +43 -0
  118. package/deps/librdkafka/src/rdendian.h +175 -0
  119. package/deps/librdkafka/src/rdfloat.h +67 -0
  120. package/deps/librdkafka/src/rdfnv1a.c +113 -0
  121. package/deps/librdkafka/src/rdfnv1a.h +35 -0
  122. package/deps/librdkafka/src/rdgz.c +120 -0
  123. package/deps/librdkafka/src/rdgz.h +46 -0
  124. package/deps/librdkafka/src/rdhdrhistogram.c +721 -0
  125. package/deps/librdkafka/src/rdhdrhistogram.h +87 -0
  126. package/deps/librdkafka/src/rdhttp.c +831 -0
  127. package/deps/librdkafka/src/rdhttp.h +101 -0
  128. package/deps/librdkafka/src/rdinterval.h +177 -0
  129. package/deps/librdkafka/src/rdkafka.c +7287 -0
  130. package/deps/librdkafka/src/rdkafka.h +11795 -0
  131. package/deps/librdkafka/src/rdkafka_admin.c +10165 -0
  132. package/deps/librdkafka/src/rdkafka_admin.h +658 -0
  133. package/deps/librdkafka/src/rdkafka_assignment.c +1282 -0
  134. package/deps/librdkafka/src/rdkafka_assignment.h +73 -0
  135. package/deps/librdkafka/src/rdkafka_assignor.c +1800 -0
  136. package/deps/librdkafka/src/rdkafka_assignor.h +402 -0
  137. package/deps/librdkafka/src/rdkafka_aux.c +409 -0
  138. package/deps/librdkafka/src/rdkafka_aux.h +174 -0
  139. package/deps/librdkafka/src/rdkafka_background.c +222 -0
  140. package/deps/librdkafka/src/rdkafka_broker.c +6736 -0
  141. package/deps/librdkafka/src/rdkafka_broker.h +851 -0
  142. package/deps/librdkafka/src/rdkafka_buf.c +543 -0
  143. package/deps/librdkafka/src/rdkafka_buf.h +1525 -0
  144. package/deps/librdkafka/src/rdkafka_cert.c +576 -0
  145. package/deps/librdkafka/src/rdkafka_cert.h +62 -0
  146. package/deps/librdkafka/src/rdkafka_cgrp.c +8310 -0
  147. package/deps/librdkafka/src/rdkafka_cgrp.h +589 -0
  148. package/deps/librdkafka/src/rdkafka_conf.c +5177 -0
  149. package/deps/librdkafka/src/rdkafka_conf.h +746 -0
  150. package/deps/librdkafka/src/rdkafka_confval.h +97 -0
  151. package/deps/librdkafka/src/rdkafka_coord.c +623 -0
  152. package/deps/librdkafka/src/rdkafka_coord.h +132 -0
  153. package/deps/librdkafka/src/rdkafka_error.c +228 -0
  154. package/deps/librdkafka/src/rdkafka_error.h +80 -0
  155. package/deps/librdkafka/src/rdkafka_event.c +502 -0
  156. package/deps/librdkafka/src/rdkafka_event.h +126 -0
  157. package/deps/librdkafka/src/rdkafka_feature.c +898 -0
  158. package/deps/librdkafka/src/rdkafka_feature.h +104 -0
  159. package/deps/librdkafka/src/rdkafka_fetcher.c +3820 -0
  160. package/deps/librdkafka/src/rdkafka_fetcher.h +62 -0
  161. package/deps/librdkafka/src/rdkafka_header.c +220 -0
  162. package/deps/librdkafka/src/rdkafka_header.h +76 -0
  163. package/deps/librdkafka/src/rdkafka_idempotence.c +807 -0
  164. package/deps/librdkafka/src/rdkafka_idempotence.h +144 -0
  165. package/deps/librdkafka/src/rdkafka_int.h +1505 -0
  166. package/deps/librdkafka/src/rdkafka_interceptor.c +842 -0
  167. package/deps/librdkafka/src/rdkafka_interceptor.h +104 -0
  168. package/deps/librdkafka/src/rdkafka_lz4.c +450 -0
  169. package/deps/librdkafka/src/rdkafka_lz4.h +49 -0
  170. package/deps/librdkafka/src/rdkafka_metadata.c +2347 -0
  171. package/deps/librdkafka/src/rdkafka_metadata.h +350 -0
  172. package/deps/librdkafka/src/rdkafka_metadata_cache.c +1183 -0
  173. package/deps/librdkafka/src/rdkafka_mock.c +4514 -0
  174. package/deps/librdkafka/src/rdkafka_mock.h +867 -0
  175. package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1877 -0
  176. package/deps/librdkafka/src/rdkafka_mock_handlers.c +5255 -0
  177. package/deps/librdkafka/src/rdkafka_mock_int.h +1005 -0
  178. package/deps/librdkafka/src/rdkafka_mock_sharegrp.c +1207 -0
  179. package/deps/librdkafka/src/rdkafka_msg.c +2619 -0
  180. package/deps/librdkafka/src/rdkafka_msg.h +629 -0
  181. package/deps/librdkafka/src/rdkafka_msgbatch.h +62 -0
  182. package/deps/librdkafka/src/rdkafka_msgset.h +105 -0
  183. package/deps/librdkafka/src/rdkafka_msgset_reader.c +2099 -0
  184. package/deps/librdkafka/src/rdkafka_msgset_writer.c +1474 -0
  185. package/deps/librdkafka/src/rdkafka_offset.c +1565 -0
  186. package/deps/librdkafka/src/rdkafka_offset.h +150 -0
  187. package/deps/librdkafka/src/rdkafka_op.c +1219 -0
  188. package/deps/librdkafka/src/rdkafka_op.h +1065 -0
  189. package/deps/librdkafka/src/rdkafka_partition.c +5109 -0
  190. package/deps/librdkafka/src/rdkafka_partition.h +1215 -0
  191. package/deps/librdkafka/src/rdkafka_pattern.c +228 -0
  192. package/deps/librdkafka/src/rdkafka_pattern.h +70 -0
  193. package/deps/librdkafka/src/rdkafka_plugin.c +213 -0
  194. package/deps/librdkafka/src/rdkafka_plugin.h +41 -0
  195. package/deps/librdkafka/src/rdkafka_proto.h +739 -0
  196. package/deps/librdkafka/src/rdkafka_protocol.h +131 -0
  197. package/deps/librdkafka/src/rdkafka_queue.c +1351 -0
  198. package/deps/librdkafka/src/rdkafka_queue.h +1235 -0
  199. package/deps/librdkafka/src/rdkafka_range_assignor.c +1748 -0
  200. package/deps/librdkafka/src/rdkafka_request.c +7298 -0
  201. package/deps/librdkafka/src/rdkafka_request.h +761 -0
  202. package/deps/librdkafka/src/rdkafka_roundrobin_assignor.c +123 -0
  203. package/deps/librdkafka/src/rdkafka_sasl.c +589 -0
  204. package/deps/librdkafka/src/rdkafka_sasl.h +63 -0
  205. package/deps/librdkafka/src/rdkafka_sasl_cyrus.c +722 -0
  206. package/deps/librdkafka/src/rdkafka_sasl_int.h +89 -0
  207. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +1833 -0
  208. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.h +52 -0
  209. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1827 -0
  210. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +52 -0
  211. package/deps/librdkafka/src/rdkafka_sasl_plain.c +142 -0
  212. package/deps/librdkafka/src/rdkafka_sasl_scram.c +858 -0
  213. package/deps/librdkafka/src/rdkafka_sasl_win32.c +550 -0
  214. package/deps/librdkafka/src/rdkafka_share_acknowledgement.c +1445 -0
  215. package/deps/librdkafka/src/rdkafka_share_acknowledgement.h +373 -0
  216. package/deps/librdkafka/src/rdkafka_ssl.c +2235 -0
  217. package/deps/librdkafka/src/rdkafka_ssl.h +86 -0
  218. package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4785 -0
  219. package/deps/librdkafka/src/rdkafka_subscription.c +411 -0
  220. package/deps/librdkafka/src/rdkafka_telemetry.c +761 -0
  221. package/deps/librdkafka/src/rdkafka_telemetry.h +52 -0
  222. package/deps/librdkafka/src/rdkafka_telemetry_decode.c +1307 -0
  223. package/deps/librdkafka/src/rdkafka_telemetry_decode.h +59 -0
  224. package/deps/librdkafka/src/rdkafka_telemetry_encode.c +1309 -0
  225. package/deps/librdkafka/src/rdkafka_telemetry_encode.h +459 -0
  226. package/deps/librdkafka/src/rdkafka_timer.c +407 -0
  227. package/deps/librdkafka/src/rdkafka_timer.h +117 -0
  228. package/deps/librdkafka/src/rdkafka_topic.c +2382 -0
  229. package/deps/librdkafka/src/rdkafka_topic.h +369 -0
  230. package/deps/librdkafka/src/rdkafka_transport.c +1309 -0
  231. package/deps/librdkafka/src/rdkafka_transport.h +99 -0
  232. package/deps/librdkafka/src/rdkafka_transport_int.h +100 -0
  233. package/deps/librdkafka/src/rdkafka_txnmgr.c +3256 -0
  234. package/deps/librdkafka/src/rdkafka_txnmgr.h +171 -0
  235. package/deps/librdkafka/src/rdkafka_zstd.c +226 -0
  236. package/deps/librdkafka/src/rdkafka_zstd.h +57 -0
  237. package/deps/librdkafka/src/rdlist.c +593 -0
  238. package/deps/librdkafka/src/rdlist.h +444 -0
  239. package/deps/librdkafka/src/rdlog.c +89 -0
  240. package/deps/librdkafka/src/rdlog.h +41 -0
  241. package/deps/librdkafka/src/rdmap.c +508 -0
  242. package/deps/librdkafka/src/rdmap.h +492 -0
  243. package/deps/librdkafka/src/rdmurmur2.c +167 -0
  244. package/deps/librdkafka/src/rdmurmur2.h +35 -0
  245. package/deps/librdkafka/src/rdports.c +61 -0
  246. package/deps/librdkafka/src/rdports.h +38 -0
  247. package/deps/librdkafka/src/rdposix.h +250 -0
  248. package/deps/librdkafka/src/rdrand.c +151 -0
  249. package/deps/librdkafka/src/rdrand.h +72 -0
  250. package/deps/librdkafka/src/rdregex.c +156 -0
  251. package/deps/librdkafka/src/rdregex.h +43 -0
  252. package/deps/librdkafka/src/rdsignal.h +57 -0
  253. package/deps/librdkafka/src/rdstring.c +645 -0
  254. package/deps/librdkafka/src/rdstring.h +98 -0
  255. package/deps/librdkafka/src/rdsysqueue.h +404 -0
  256. package/deps/librdkafka/src/rdtime.h +356 -0
  257. package/deps/librdkafka/src/rdtypes.h +86 -0
  258. package/deps/librdkafka/src/rdunittest.c +564 -0
  259. package/deps/librdkafka/src/rdunittest.h +232 -0
  260. package/deps/librdkafka/src/rdunittest_acknowledge.c +1177 -0
  261. package/deps/librdkafka/src/rdunittest_fetcher.c +1211 -0
  262. package/deps/librdkafka/src/rdunittest_msgset_errors.c +1521 -0
  263. package/deps/librdkafka/src/rdvarint.c +134 -0
  264. package/deps/librdkafka/src/rdvarint.h +165 -0
  265. package/deps/librdkafka/src/rdwin32.h +382 -0
  266. package/deps/librdkafka/src/rdxxhash.c +1030 -0
  267. package/deps/librdkafka/src/rdxxhash.h +328 -0
  268. package/deps/librdkafka/src/regexp.c +1352 -0
  269. package/deps/librdkafka/src/regexp.h +41 -0
  270. package/deps/librdkafka/src/snappy.c +1866 -0
  271. package/deps/librdkafka/src/snappy.h +62 -0
  272. package/deps/librdkafka/src/snappy_compat.h +138 -0
  273. package/deps/librdkafka/src/statistics_schema.json +444 -0
  274. package/deps/librdkafka/src/tinycthread.c +932 -0
  275. package/deps/librdkafka/src/tinycthread.h +503 -0
  276. package/deps/librdkafka/src/tinycthread_extra.c +210 -0
  277. package/deps/librdkafka/src/tinycthread_extra.h +218 -0
  278. package/deps/librdkafka/src/win32_config.h +58 -0
  279. package/deps/librdkafka/src-cpp/CMakeLists.txt +90 -0
  280. package/deps/librdkafka/src-cpp/ConfImpl.cpp +84 -0
  281. package/deps/librdkafka/src-cpp/ConsumerImpl.cpp +244 -0
  282. package/deps/librdkafka/src-cpp/HandleImpl.cpp +436 -0
  283. package/deps/librdkafka/src-cpp/HeadersImpl.cpp +48 -0
  284. package/deps/librdkafka/src-cpp/KafkaConsumerImpl.cpp +296 -0
  285. package/deps/librdkafka/src-cpp/Makefile +55 -0
  286. package/deps/librdkafka/src-cpp/MessageImpl.cpp +38 -0
  287. package/deps/librdkafka/src-cpp/MetadataImpl.cpp +170 -0
  288. package/deps/librdkafka/src-cpp/ProducerImpl.cpp +197 -0
  289. package/deps/librdkafka/src-cpp/QueueImpl.cpp +70 -0
  290. package/deps/librdkafka/src-cpp/README.md +16 -0
  291. package/deps/librdkafka/src-cpp/RdKafka.cpp +59 -0
  292. package/deps/librdkafka/src-cpp/TopicImpl.cpp +124 -0
  293. package/deps/librdkafka/src-cpp/TopicPartitionImpl.cpp +57 -0
  294. package/deps/librdkafka/src-cpp/rdkafkacpp.h +3797 -0
  295. package/deps/librdkafka/src-cpp/rdkafkacpp_int.h +1641 -0
  296. package/deps/librdkafka/tests/0000-unittests.c +72 -0
  297. package/deps/librdkafka/tests/0001-multiobj.c +102 -0
  298. package/deps/librdkafka/tests/0002-unkpart.c +244 -0
  299. package/deps/librdkafka/tests/0003-msgmaxsize.c +173 -0
  300. package/deps/librdkafka/tests/0004-conf.c +934 -0
  301. package/deps/librdkafka/tests/0005-order.c +133 -0
  302. package/deps/librdkafka/tests/0006-symbols.c +163 -0
  303. package/deps/librdkafka/tests/0007-autotopic.c +136 -0
  304. package/deps/librdkafka/tests/0008-reqacks.c +179 -0
  305. package/deps/librdkafka/tests/0009-mock_cluster.c +272 -0
  306. package/deps/librdkafka/tests/0011-produce_batch.c +753 -0
  307. package/deps/librdkafka/tests/0012-produce_consume.c +537 -0
  308. package/deps/librdkafka/tests/0013-null-msgs.c +473 -0
  309. package/deps/librdkafka/tests/0014-reconsume-191.c +512 -0
  310. package/deps/librdkafka/tests/0015-offset_seeks.c +172 -0
  311. package/deps/librdkafka/tests/0016-client_swname.c +181 -0
  312. package/deps/librdkafka/tests/0017-compression.c +140 -0
  313. package/deps/librdkafka/tests/0018-cgrp_term.c +444 -0
  314. package/deps/librdkafka/tests/0019-list_groups.c +289 -0
  315. package/deps/librdkafka/tests/0020-destroy_hang.c +162 -0
  316. package/deps/librdkafka/tests/0021-rkt_destroy.c +72 -0
  317. package/deps/librdkafka/tests/0022-consume_batch.c +331 -0
  318. package/deps/librdkafka/tests/0025-timers.c +147 -0
  319. package/deps/librdkafka/tests/0026-consume_pause.c +547 -0
  320. package/deps/librdkafka/tests/0028-long_topicnames.c +79 -0
  321. package/deps/librdkafka/tests/0029-assign_offset.c +202 -0
  322. package/deps/librdkafka/tests/0030-offset_commit.c +589 -0
  323. package/deps/librdkafka/tests/0031-get_offsets.c +235 -0
  324. package/deps/librdkafka/tests/0033-regex_subscribe.c +536 -0
  325. package/deps/librdkafka/tests/0034-offset_reset.c +398 -0
  326. package/deps/librdkafka/tests/0035-api_version.c +73 -0
  327. package/deps/librdkafka/tests/0036-partial_fetch.c +87 -0
  328. package/deps/librdkafka/tests/0037-destroy_hang_local.c +85 -0
  329. package/deps/librdkafka/tests/0038-performance.c +121 -0
  330. package/deps/librdkafka/tests/0039-event.c +284 -0
  331. package/deps/librdkafka/tests/0040-io_event.c +257 -0
  332. package/deps/librdkafka/tests/0041-fetch_max_bytes.c +97 -0
  333. package/deps/librdkafka/tests/0042-many_topics.c +252 -0
  334. package/deps/librdkafka/tests/0043-no_connection.c +77 -0
  335. package/deps/librdkafka/tests/0044-partition_cnt.c +94 -0
  336. package/deps/librdkafka/tests/0045-subscribe_update.c +1010 -0
  337. package/deps/librdkafka/tests/0046-rkt_cache.c +65 -0
  338. package/deps/librdkafka/tests/0047-partial_buf_tmout.c +98 -0
  339. package/deps/librdkafka/tests/0048-partitioner.c +283 -0
  340. package/deps/librdkafka/tests/0049-consume_conn_close.c +162 -0
  341. package/deps/librdkafka/tests/0050-subscribe_adds.c +145 -0
  342. package/deps/librdkafka/tests/0051-assign_adds.c +126 -0
  343. package/deps/librdkafka/tests/0052-msg_timestamps.c +238 -0
  344. package/deps/librdkafka/tests/0053-stats_cb.cpp +527 -0
  345. package/deps/librdkafka/tests/0054-offset_time.cpp +236 -0
  346. package/deps/librdkafka/tests/0055-producer_latency.c +539 -0
  347. package/deps/librdkafka/tests/0056-balanced_group_mt.c +315 -0
  348. package/deps/librdkafka/tests/0057-invalid_topic.cpp +112 -0
  349. package/deps/librdkafka/tests/0058-log.cpp +123 -0
  350. package/deps/librdkafka/tests/0059-bsearch.cpp +241 -0
  351. package/deps/librdkafka/tests/0060-op_prio.cpp +163 -0
  352. package/deps/librdkafka/tests/0061-consumer_lag.cpp +295 -0
  353. package/deps/librdkafka/tests/0062-stats_event.c +126 -0
  354. package/deps/librdkafka/tests/0063-clusterid.cpp +180 -0
  355. package/deps/librdkafka/tests/0064-interceptors.c +529 -0
  356. package/deps/librdkafka/tests/0065-yield.cpp +140 -0
  357. package/deps/librdkafka/tests/0066-plugins.cpp +143 -0
  358. package/deps/librdkafka/tests/0067-empty_topic.cpp +151 -0
  359. package/deps/librdkafka/tests/0068-produce_timeout.c +136 -0
  360. package/deps/librdkafka/tests/0069-consumer_add_parts.c +119 -0
  361. package/deps/librdkafka/tests/0070-null_empty.cpp +197 -0
  362. package/deps/librdkafka/tests/0072-headers_ut.c +448 -0
  363. package/deps/librdkafka/tests/0073-headers.c +381 -0
  364. package/deps/librdkafka/tests/0074-producev.c +87 -0
  365. package/deps/librdkafka/tests/0075-retry.c +290 -0
  366. package/deps/librdkafka/tests/0076-produce_retry.c +452 -0
  367. package/deps/librdkafka/tests/0077-compaction.c +366 -0
  368. package/deps/librdkafka/tests/0078-c_from_cpp.cpp +96 -0
  369. package/deps/librdkafka/tests/0079-fork.c +93 -0
  370. package/deps/librdkafka/tests/0080-admin_ut.c +3095 -0
  371. package/deps/librdkafka/tests/0081-admin.c +5633 -0
  372. package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +137 -0
  373. package/deps/librdkafka/tests/0083-cb_event.c +233 -0
  374. package/deps/librdkafka/tests/0084-destroy_flags.c +208 -0
  375. package/deps/librdkafka/tests/0085-headers.cpp +392 -0
  376. package/deps/librdkafka/tests/0086-purge.c +368 -0
  377. package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +162 -0
  378. package/deps/librdkafka/tests/0089-max_poll_interval.c +511 -0
  379. package/deps/librdkafka/tests/0090-idempotence.c +171 -0
  380. package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +295 -0
  381. package/deps/librdkafka/tests/0092-mixed_msgver.c +103 -0
  382. package/deps/librdkafka/tests/0093-holb.c +200 -0
  383. package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +231 -0
  384. package/deps/librdkafka/tests/0095-all_brokers_down.cpp +122 -0
  385. package/deps/librdkafka/tests/0097-ssl_verify.cpp +674 -0
  386. package/deps/librdkafka/tests/0098-consumer-txn.cpp +1218 -0
  387. package/deps/librdkafka/tests/0099-commit_metadata.c +194 -0
  388. package/deps/librdkafka/tests/0100-thread_interceptors.cpp +195 -0
  389. package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +446 -0
  390. package/deps/librdkafka/tests/0102-static_group_rebalance.c +836 -0
  391. package/deps/librdkafka/tests/0103-transactions.c +1383 -0
  392. package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +625 -0
  393. package/deps/librdkafka/tests/0105-transactions_mock.c +3930 -0
  394. package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +318 -0
  395. package/deps/librdkafka/tests/0107-topic_recreate.c +259 -0
  396. package/deps/librdkafka/tests/0109-auto_create_topics.cpp +278 -0
  397. package/deps/librdkafka/tests/0110-batch_size.cpp +182 -0
  398. package/deps/librdkafka/tests/0111-delay_create_topics.cpp +127 -0
  399. package/deps/librdkafka/tests/0112-assign_unknown_part.c +87 -0
  400. package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +3473 -0
  401. package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +176 -0
  402. package/deps/librdkafka/tests/0115-producer_auth.cpp +182 -0
  403. package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +216 -0
  404. package/deps/librdkafka/tests/0117-mock_errors.c +331 -0
  405. package/deps/librdkafka/tests/0118-commit_rebalance.c +154 -0
  406. package/deps/librdkafka/tests/0119-consumer_auth.cpp +167 -0
  407. package/deps/librdkafka/tests/0120-asymmetric_subscription.c +185 -0
  408. package/deps/librdkafka/tests/0121-clusterid.c +115 -0
  409. package/deps/librdkafka/tests/0122-buffer_cleaning_after_rebalance.c +227 -0
  410. package/deps/librdkafka/tests/0123-connections_max_idle.c +98 -0
  411. package/deps/librdkafka/tests/0124-openssl_invalid_engine.c +69 -0
  412. package/deps/librdkafka/tests/0125-immediate_flush.c +144 -0
  413. package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +1164 -0
  414. package/deps/librdkafka/tests/0127-fetch_queue_backoff.cpp +165 -0
  415. package/deps/librdkafka/tests/0128-sasl_callback_queue.cpp +217 -0
  416. package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +79 -0
  417. package/deps/librdkafka/tests/0130-store_offsets.c +178 -0
  418. package/deps/librdkafka/tests/0131-connect_timeout.c +81 -0
  419. package/deps/librdkafka/tests/0132-strategy_ordering.c +179 -0
  420. package/deps/librdkafka/tests/0133-ssl_keys.c +150 -0
  421. package/deps/librdkafka/tests/0134-ssl_provider.c +92 -0
  422. package/deps/librdkafka/tests/0135-sasl_credentials.cpp +279 -0
  423. package/deps/librdkafka/tests/0136-resolve_cb.c +181 -0
  424. package/deps/librdkafka/tests/0137-barrier_batch_consume.c +752 -0
  425. package/deps/librdkafka/tests/0138-admin_mock.c +281 -0
  426. package/deps/librdkafka/tests/0139-offset_validation_mock.c +950 -0
  427. package/deps/librdkafka/tests/0140-commit_metadata.cpp +108 -0
  428. package/deps/librdkafka/tests/0142-reauthentication.c +931 -0
  429. package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +552 -0
  430. package/deps/librdkafka/tests/0144-idempotence_mock.c +373 -0
  431. package/deps/librdkafka/tests/0145-pause_resume_mock.c +119 -0
  432. package/deps/librdkafka/tests/0146-metadata_mock.c +505 -0
  433. package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +1210 -0
  434. package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +569 -0
  435. package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
  436. package/deps/librdkafka/tests/0150-telemetry_mock.c +651 -0
  437. package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
  438. package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
  439. package/deps/librdkafka/tests/0153-memberid.c +128 -0
  440. package/deps/librdkafka/tests/0155-share_group_heartbeat_mock.c +2695 -0
  441. package/deps/librdkafka/tests/0156-share_consumer_fetch_mock.c +1551 -0
  442. package/deps/librdkafka/tests/0157-share_consumer_ack_mock.c +1788 -0
  443. package/deps/librdkafka/tests/0158-share_consumer_transactions_mock.c +467 -0
  444. package/deps/librdkafka/tests/0170-share_consumer_subscription.c +2192 -0
  445. package/deps/librdkafka/tests/0171-share_consumer_consume.c +2150 -0
  446. package/deps/librdkafka/tests/0172-share_consumer_acknowledge.c +1919 -0
  447. package/deps/librdkafka/tests/0173-share_consumer_commit_async.c +2735 -0
  448. package/deps/librdkafka/tests/0174-share_consumer_concurrency.c +1434 -0
  449. package/deps/librdkafka/tests/0175-share_consumer_groups.c +574 -0
  450. package/deps/librdkafka/tests/0176-share_consumer_commit_sync.c +2986 -0
  451. package/deps/librdkafka/tests/0177-share_consumer_transactions.c +1815 -0
  452. package/deps/librdkafka/tests/0178-share_consumer_close.c +2102 -0
  453. package/deps/librdkafka/tests/0179-share_consumer_destroy.c +2482 -0
  454. package/deps/librdkafka/tests/0180-share_consumer_config.c +1677 -0
  455. package/deps/librdkafka/tests/0181-share_consumer_topic_delete.c +354 -0
  456. package/deps/librdkafka/tests/0182-share_consumer_error_handling_mock.c +3358 -0
  457. package/deps/librdkafka/tests/0183-share_consumer_leader_change_mock.c +1234 -0
  458. package/deps/librdkafka/tests/0184-share_consumer_topic_recreate.c +1356 -0
  459. package/deps/librdkafka/tests/0185-share_consumer_max_poll_interval.c +557 -0
  460. package/deps/librdkafka/tests/0186-share_consumer_fatal_error.c +360 -0
  461. package/deps/librdkafka/tests/0190-share_consumer_telemetry.c +387 -0
  462. package/deps/librdkafka/tests/1000-unktopic.c +164 -0
  463. package/deps/librdkafka/tests/8000-idle.cpp +60 -0
  464. package/deps/librdkafka/tests/8001-fetch_from_follower_mock_manual.c +113 -0
  465. package/deps/librdkafka/tests/CMakeLists.txt +193 -0
  466. package/deps/librdkafka/tests/LibrdkafkaTestApp.py +291 -0
  467. package/deps/librdkafka/tests/Makefile +182 -0
  468. package/deps/librdkafka/tests/README.md +514 -0
  469. package/deps/librdkafka/tests/autotest.sh +33 -0
  470. package/deps/librdkafka/tests/backtrace.gdb +30 -0
  471. package/deps/librdkafka/tests/broker_version_tests.py +315 -0
  472. package/deps/librdkafka/tests/buildbox.sh +17 -0
  473. package/deps/librdkafka/tests/cleanup-checker-tests.sh +20 -0
  474. package/deps/librdkafka/tests/cluster_testing.py +203 -0
  475. package/deps/librdkafka/tests/delete-test-topics.sh +56 -0
  476. package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
  477. package/deps/librdkafka/tests/fixtures/ssl/Makefile +8 -0
  478. package/deps/librdkafka/tests/fixtures/ssl/README.md +13 -0
  479. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
  480. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
  481. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
  482. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +50 -0
  483. package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
  484. package/deps/librdkafka/tests/fixtures/ssl/client2.key +46 -0
  485. package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +168 -0
  486. package/deps/librdkafka/tests/fuzzers/Makefile +12 -0
  487. package/deps/librdkafka/tests/fuzzers/README.md +31 -0
  488. package/deps/librdkafka/tests/fuzzers/fuzz_regex.c +74 -0
  489. package/deps/librdkafka/tests/fuzzers/helpers.h +90 -0
  490. package/deps/librdkafka/tests/gen-ssl-certs.sh +165 -0
  491. package/deps/librdkafka/tests/interactive_broker_version.py +170 -0
  492. package/deps/librdkafka/tests/interceptor_test/CMakeLists.txt +16 -0
  493. package/deps/librdkafka/tests/interceptor_test/Makefile +22 -0
  494. package/deps/librdkafka/tests/interceptor_test/interceptor_test.c +314 -0
  495. package/deps/librdkafka/tests/interceptor_test/interceptor_test.h +54 -0
  496. package/deps/librdkafka/tests/java/IncrementalRebalanceCli.java +97 -0
  497. package/deps/librdkafka/tests/java/Makefile +13 -0
  498. package/deps/librdkafka/tests/java/Murmur2Cli.java +46 -0
  499. package/deps/librdkafka/tests/java/README.md +14 -0
  500. package/deps/librdkafka/tests/java/TransactionProducerCli.java +162 -0
  501. package/deps/librdkafka/tests/java/run-class.sh +11 -0
  502. package/deps/librdkafka/tests/librdkafka.suppressions +483 -0
  503. package/deps/librdkafka/tests/lz4_manual_test.sh +59 -0
  504. package/deps/librdkafka/tests/multi-broker-version-test.sh +50 -0
  505. package/deps/librdkafka/tests/parse-refcnt.sh +43 -0
  506. package/deps/librdkafka/tests/performance_plot.py +115 -0
  507. package/deps/librdkafka/tests/plugin_test/Makefile +19 -0
  508. package/deps/librdkafka/tests/plugin_test/plugin_test.c +58 -0
  509. package/deps/librdkafka/tests/requirements.txt +2 -0
  510. package/deps/librdkafka/tests/run-all-tests.sh +81 -0
  511. package/deps/librdkafka/tests/run-consumer-tests.sh +16 -0
  512. package/deps/librdkafka/tests/run-producer-tests.sh +16 -0
  513. package/deps/librdkafka/tests/run-test-batches.py +157 -0
  514. package/deps/librdkafka/tests/run-test.sh +141 -0
  515. package/deps/librdkafka/tests/rusage.c +249 -0
  516. package/deps/librdkafka/tests/sasl_test.py +289 -0
  517. package/deps/librdkafka/tests/scenarios/README.md +6 -0
  518. package/deps/librdkafka/tests/scenarios/ak23.json +6 -0
  519. package/deps/librdkafka/tests/scenarios/default.json +5 -0
  520. package/deps/librdkafka/tests/scenarios/noautocreate.json +5 -0
  521. package/deps/librdkafka/tests/share_telemetry_verify/share-otel-collector-config.yaml +28 -0
  522. package/deps/librdkafka/tests/sockem.c +801 -0
  523. package/deps/librdkafka/tests/sockem.h +85 -0
  524. package/deps/librdkafka/tests/sockem_ctrl.c +145 -0
  525. package/deps/librdkafka/tests/sockem_ctrl.h +61 -0
  526. package/deps/librdkafka/tests/test.c +8376 -0
  527. package/deps/librdkafka/tests/test.conf.example +27 -0
  528. package/deps/librdkafka/tests/test.h +1150 -0
  529. package/deps/librdkafka/tests/testcpp.cpp +131 -0
  530. package/deps/librdkafka/tests/testcpp.h +388 -0
  531. package/deps/librdkafka/tests/testshared.h +416 -0
  532. package/deps/librdkafka/tests/tools/README.md +4 -0
  533. package/deps/librdkafka/tests/tools/stats/README.md +21 -0
  534. package/deps/librdkafka/tests/tools/stats/filter.jq +42 -0
  535. package/deps/librdkafka/tests/tools/stats/graph.py +150 -0
  536. package/deps/librdkafka/tests/tools/stats/requirements.txt +3 -0
  537. package/deps/librdkafka/tests/tools/stats/to_csv.py +124 -0
  538. package/deps/librdkafka/tests/trivup/trivup-0.15.0.tar.gz +0 -0
  539. package/deps/librdkafka/tests/until-fail.sh +87 -0
  540. package/deps/librdkafka/tests/xxxx-assign_partition.c +122 -0
  541. package/deps/librdkafka/tests/xxxx-metadata.cpp +159 -0
  542. package/deps/librdkafka/vcpkg.json +23 -0
  543. package/deps/librdkafka/win32/README.md +5 -0
  544. package/deps/librdkafka/win32/build-package.bat +3 -0
  545. package/deps/librdkafka/win32/build.bat +19 -0
  546. package/deps/librdkafka/win32/common.vcxproj +84 -0
  547. package/deps/librdkafka/win32/interceptor_test/interceptor_test.vcxproj +87 -0
  548. package/deps/librdkafka/win32/librdkafka.autopkg.template +54 -0
  549. package/deps/librdkafka/win32/librdkafka.master.testing.targets +13 -0
  550. package/deps/librdkafka/win32/librdkafka.sln +226 -0
  551. package/deps/librdkafka/win32/librdkafka.vcxproj +282 -0
  552. package/deps/librdkafka/win32/librdkafkacpp/librdkafkacpp.vcxproj +104 -0
  553. package/deps/librdkafka/win32/msbuild.ps1 +58 -0
  554. package/deps/librdkafka/win32/openssl_engine_example/openssl_engine_example.vcxproj +132 -0
  555. package/deps/librdkafka/win32/package-zip.ps1 +46 -0
  556. package/deps/librdkafka/win32/packages/repositories.config +4 -0
  557. package/deps/librdkafka/win32/push-package.bat +4 -0
  558. package/deps/librdkafka/win32/rdkafka_complex_consumer_example_cpp/rdkafka_complex_consumer_example_cpp.vcxproj +67 -0
  559. package/deps/librdkafka/win32/rdkafka_example/rdkafka_example.vcxproj +97 -0
  560. package/deps/librdkafka/win32/rdkafka_performance/rdkafka_performance.vcxproj +97 -0
  561. package/deps/librdkafka/win32/setup-msys2.ps1 +47 -0
  562. package/deps/librdkafka/win32/setup-vcpkg.ps1 +34 -0
  563. package/deps/librdkafka/win32/tests/test.conf.example +25 -0
  564. package/deps/librdkafka/win32/tests/tests.vcxproj +278 -0
  565. package/deps/librdkafka/win32/win_ssl_cert_store/win_ssl_cert_store.vcxproj +132 -0
  566. package/deps/librdkafka/win32/wingetopt.c +564 -0
  567. package/deps/librdkafka/win32/wingetopt.h +101 -0
  568. package/deps/librdkafka/win32/wintime.h +33 -0
  569. package/deps/librdkafka.gyp +83 -0
  570. package/deps/windows-install.py +80 -0
  571. package/index.d.ts +3 -0
  572. package/lib/admin.js +732 -0
  573. package/lib/client.js +679 -0
  574. package/lib/error.js +501 -0
  575. package/lib/index.js +8 -0
  576. package/lib/kafka-consumer-stream.js +399 -0
  577. package/lib/kafka-consumer.js +798 -0
  578. package/lib/kafkajs/_admin.js +1052 -0
  579. package/lib/kafkajs/_common.js +887 -0
  580. package/lib/kafkajs/_consumer.js +2156 -0
  581. package/lib/kafkajs/_consumer_cache.js +310 -0
  582. package/lib/kafkajs/_error.js +298 -0
  583. package/lib/kafkajs/_kafka.js +125 -0
  584. package/lib/kafkajs/_linked-list.js +220 -0
  585. package/lib/kafkajs/_producer.js +863 -0
  586. package/lib/kafkajs/index.js +5 -0
  587. package/lib/producer/high-level-producer.js +400 -0
  588. package/lib/producer-stream.js +337 -0
  589. package/lib/producer.js +405 -0
  590. package/lib/rdkafka.js +44 -0
  591. package/lib/tools/ref-counter.js +53 -0
  592. package/lib/topic-partition.js +91 -0
  593. package/lib/topic.js +43 -0
  594. package/lib/util.js +55 -0
  595. package/librdkafka.js +12 -0
  596. package/package.json +85 -0
  597. package/shim/source_location +54 -0
  598. package/src/admin.cc +1597 -0
  599. package/src/admin.h +112 -0
  600. package/src/binding.cc +76 -0
  601. package/src/binding.h +25 -0
  602. package/src/callbacks.cc +676 -0
  603. package/src/callbacks.h +302 -0
  604. package/src/common.cc +1628 -0
  605. package/src/common.h +192 -0
  606. package/src/config.cc +219 -0
  607. package/src/config.h +61 -0
  608. package/src/connection.cc +703 -0
  609. package/src/connection.h +117 -0
  610. package/src/errors.cc +119 -0
  611. package/src/errors.h +60 -0
  612. package/src/kafka-consumer.cc +1539 -0
  613. package/src/kafka-consumer.h +147 -0
  614. package/src/producer.cc +917 -0
  615. package/src/producer.h +124 -0
  616. package/src/topic.cc +173 -0
  617. package/src/topic.h +54 -0
  618. package/src/workers.cc +1714 -0
  619. package/src/workers.h +690 -0
  620. package/types/config.d.ts +1269 -0
  621. package/types/errors.d.ts +373 -0
  622. package/types/kafkajs.d.ts +568 -0
  623. package/types/rdkafka.d.ts +571 -0
  624. package/util/configure.js +32 -0
  625. package/util/generate-docs.sh +18 -0
  626. package/util/get-env.js +6 -0
  627. package/util/test-compile.js +11 -0
  628. package/util/test-producer-delivery.js +100 -0
@@ -0,0 +1,2735 @@
1
+ /*
2
+ * librdkafka - Apache Kafka C library
3
+ *
4
+ * Copyright (c) 2026, Confluent Inc.
5
+ * All rights reserved.
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without
8
+ * modification, are permitted provided that the following conditions are met:
9
+ *
10
+ * 1. Redistributions of source code must retain the above copyright notice,
11
+ * this list of conditions and the following disclaimer.
12
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ * this list of conditions and the following disclaimer in the documentation
14
+ * and/or other materials provided with the distribution.
15
+ *
16
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ * POSSIBILITY OF SUCH DAMAGE.
27
+ */
28
+
29
+ #include "test.h"
30
+
31
+ #include "../src/rdkafka_proto.h"
32
+
33
+ /**
34
+ * @brief Share consumer rd_kafka_share_commit_async() API tests.
35
+ *
36
+ * Tests the commit_async API in both implicit and explicit ack modes.
37
+ * Verifies that commit_async commits acknowledged records so they are
38
+ * not redelivered, and that it properly extracts acks from the inflight
39
+ * map (preventing consume_batch from re-sending the same acks).
40
+ *
41
+ * Mixed ack types: ACCEPT (~50%), RELEASE (~40%), REJECT (~10%)
42
+ */
43
+
44
+ #define MAX_MSGS 500
45
+ #define CONSUME_ARRAY 10001
46
+
47
+ /** Common producer reused across all non-mock subtests. */
48
+ static rd_kafka_t *common_producer;
49
+
50
+ /** Common admin client reused across all non-mock subtests. */
51
+ static rd_kafka_t *common_admin;
52
+
53
+
54
+ /**
55
+ * @brief Set share.record.lock.duration.ms for a share group.
56
+ */
57
+ static void set_group_lock_duration(const char *group_name,
58
+ const char *duration_ms) {
59
+ const char *cfg[] = {"share.record.lock.duration.ms", "SET",
60
+ duration_ms};
61
+
62
+ test_IncrementalAlterConfigs_simple(
63
+ common_admin, RD_KAFKA_RESOURCE_GROUP, group_name, cfg, 1);
64
+ }
65
+
66
+
67
+ /**
68
+ * @brief Subscribe a share consumer to topics.
69
+ */
70
+ static void subscribe_consumer(rd_kafka_share_t *rkshare,
71
+ const char **topics,
72
+ int topic_cnt) {
73
+ rd_kafka_topic_partition_list_t *subs;
74
+ rd_kafka_resp_err_t err;
75
+ int i;
76
+
77
+ subs = rd_kafka_topic_partition_list_new(topic_cnt);
78
+ for (i = 0; i < topic_cnt; i++)
79
+ rd_kafka_topic_partition_list_add(subs, topics[i],
80
+ RD_KAFKA_PARTITION_UA);
81
+
82
+ err = rd_kafka_share_subscribe(rkshare, subs);
83
+ TEST_ASSERT(!err, "Subscribe failed: %s", rd_kafka_err2str(err));
84
+
85
+ rd_kafka_topic_partition_list_destroy(subs);
86
+ }
87
+
88
+
89
+
90
+ /**
91
+ * @brief Determine random ack type with distribution:
92
+ * ACCEPT ~50%, RELEASE ~40%, REJECT ~10%
93
+ */
94
+ static rd_kafka_share_AcknowledgeType_t get_random_ack_type(void) {
95
+ int r = jitter(0, 99);
96
+ if (r < 50)
97
+ return RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT;
98
+ else if (r < 90)
99
+ return RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE;
100
+ else
101
+ return RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_REJECT;
102
+ }
103
+
104
+
105
+ /* ===================================================================
106
+ * Implicit ack mode — commit_async commits acks independently of
107
+ * consume_batch piggybacking.
108
+ *
109
+ * Consumer 1 (implicit) consumes first batch, calls commit_async,
110
+ * waits, closes. Consumer 2 consumes remaining and verifies no
111
+ * offset overlap with consumer 1.
112
+ * =================================================================== */
113
+ static void do_test_implicit_second_consumer(void) {
114
+ const char *topic;
115
+ const char *group = "commit-async-implicit-second";
116
+ rd_kafka_share_t *rkshare;
117
+ rd_kafka_error_t *error;
118
+ rd_kafka_messages_t *batch = NULL;
119
+ size_t rcvd;
120
+ size_t j;
121
+ int consumed1 = 0, consumed2 = 0;
122
+ int attempts = 0;
123
+ int64_t *c1_offsets;
124
+
125
+ SUB_TEST();
126
+
127
+ topic = test_mk_topic_name("0173-ca-impl-2nd", 1);
128
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
129
+ test_produce_msgs_simple(common_producer, topic, 0, MAX_MSGS);
130
+
131
+ rkshare = test_create_share_consumer(group, "implicit");
132
+ test_share_set_auto_offset_reset(group, "earliest");
133
+ set_group_lock_duration(group, "3000");
134
+ subscribe_consumer(rkshare, &topic, 1);
135
+
136
+ c1_offsets = rd_calloc(MAX_MSGS, sizeof(*c1_offsets));
137
+
138
+ /* Wait for first batch of records */
139
+ while (consumed1 == 0 && attempts++ < 30) {
140
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
141
+ if (error) {
142
+ rd_kafka_error_destroy(error);
143
+ continue;
144
+ }
145
+ rcvd = rd_kafka_messages_count(batch);
146
+
147
+ for (j = 0; j < rcvd; j++) {
148
+ rd_kafka_message_t *rkm =
149
+ rd_kafka_messages_get(batch, j);
150
+ if (!rkm->err)
151
+ c1_offsets[consumed1++] = rkm->offset;
152
+ }
153
+ rd_kafka_messages_destroy(batch);
154
+ batch = NULL;
155
+ }
156
+
157
+ TEST_SAY("Consumer 1 consumed %d messages in first batch\n", consumed1);
158
+ TEST_ASSERT(consumed1 > 0, "Consumer 1 got no records");
159
+
160
+ /* commit_async without any subsequent consume_batch */
161
+ error = rd_kafka_share_commit_async(rkshare);
162
+ TEST_ASSERT(!error, "commit_async failed: %s",
163
+ error ? rd_kafka_error_string(error) : "");
164
+
165
+ rd_sleep(3);
166
+
167
+ test_share_consumer_close(rkshare);
168
+ test_share_destroy(rkshare);
169
+
170
+ /* Produce 5 verification records */
171
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
172
+
173
+ /* Second consumer: should only get the 5 verification records.
174
+ * No lock wait needed — implicit mode close tears down the
175
+ * connection and broker releases records immediately. */
176
+ rkshare = test_create_share_consumer(group, "implicit");
177
+ subscribe_consumer(rkshare, &topic, 1);
178
+
179
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
180
+ rcvd = rd_kafka_messages_count(batch);
181
+ TEST_SAY("Consumer 2 consume_batch returned: rcvd=%zu, error=%s\n",
182
+ rcvd, error ? rd_kafka_error_string(error) : "none");
183
+ if (error) {
184
+ rd_kafka_error_destroy(error);
185
+ }
186
+
187
+ for (j = 0; j < rcvd; j++) {
188
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
189
+ if (!rkm->err) {
190
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
191
+ "Consumer 2 got redelivered record at "
192
+ "offset %" PRId64 " (delivery_count=%d)",
193
+ rkm->offset,
194
+ rd_kafka_message_delivery_count(rkm));
195
+ consumed2++;
196
+ }
197
+ }
198
+ rd_kafka_messages_destroy(batch);
199
+ batch = NULL;
200
+
201
+ TEST_SAY("Consumer 2 got %d messages (expected 5)\n", consumed2);
202
+ TEST_ASSERT(consumed2 == 5, "Expected 5 verification records, got %d",
203
+ consumed2);
204
+
205
+ rd_free(c1_offsets);
206
+
207
+ test_share_consumer_close(rkshare);
208
+ test_share_destroy(rkshare);
209
+
210
+ SUB_TEST_PASS();
211
+ }
212
+
213
+
214
+ /* ===================================================================
215
+ * Explicit ack mode — commit_async commits explicit ACCEPT acks.
216
+ *
217
+ * Consumer 1 (explicit) consumes first batch, ACCEPTs each record,
218
+ * calls commit_async, waits, closes. Consumer 2 consumes remaining
219
+ * and verifies no offset overlap with consumer 1.
220
+ * =================================================================== */
221
+ static void do_test_explicit_second_consumer(void) {
222
+ const char *topic;
223
+ const char *group = "commit-async-explicit-second";
224
+ rd_kafka_share_t *rkshare;
225
+ rd_kafka_error_t *error;
226
+ rd_kafka_messages_t *batch = NULL;
227
+ size_t rcvd;
228
+ size_t j;
229
+ int consumed1 = 0, consumed2 = 0;
230
+ int attempts = 0;
231
+ int64_t *c1_offsets;
232
+
233
+ SUB_TEST();
234
+
235
+ topic = test_mk_topic_name("0173-ca-expl-2nd", 1);
236
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
237
+ test_produce_msgs_simple(common_producer, topic, 0, MAX_MSGS);
238
+
239
+ rkshare = test_create_share_consumer(group, "explicit");
240
+ test_share_set_auto_offset_reset(group, "earliest");
241
+ set_group_lock_duration(group, "3000");
242
+ subscribe_consumer(rkshare, &topic, 1);
243
+
244
+ c1_offsets = rd_calloc(MAX_MSGS, sizeof(*c1_offsets));
245
+
246
+ /* Wait for first batch of records */
247
+ while (consumed1 == 0 && attempts++ < 30) {
248
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
249
+ if (error) {
250
+ rd_kafka_error_destroy(error);
251
+ continue;
252
+ }
253
+ rcvd = rd_kafka_messages_count(batch);
254
+
255
+ for (j = 0; j < rcvd; j++) {
256
+ rd_kafka_message_t *rkm =
257
+ rd_kafka_messages_get(batch, j);
258
+ if (!rkm->err) {
259
+ c1_offsets[consumed1++] = rkm->offset;
260
+ rd_kafka_share_acknowledge(rkshare, rkm);
261
+ }
262
+ }
263
+ rd_kafka_messages_destroy(batch);
264
+ batch = NULL;
265
+ }
266
+
267
+ TEST_SAY("Consumer 1 consumed %d messages in first batch\n", consumed1);
268
+ TEST_ASSERT(consumed1 > 0, "Consumer 1 got no records");
269
+
270
+ /* commit_async without any subsequent consume_batch */
271
+ error = rd_kafka_share_commit_async(rkshare);
272
+ TEST_ASSERT(!error, "commit_async failed: %s",
273
+ error ? rd_kafka_error_string(error) : "");
274
+
275
+ rd_sleep(3);
276
+
277
+ test_share_consumer_close(rkshare);
278
+ test_share_destroy(rkshare);
279
+
280
+ /* Produce 5 verification records */
281
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
282
+
283
+ /* Second consumer: should only get the 5 verification records.
284
+ * Records are either committed by the last commit_async or
285
+ * released on the broker side when the connection is closed. */
286
+ rkshare = test_create_share_consumer(group, "implicit");
287
+ subscribe_consumer(rkshare, &topic, 1);
288
+
289
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
290
+ rcvd = rd_kafka_messages_count(batch);
291
+ TEST_SAY("Consumer 2 consume_batch returned: rcvd=%zu, error=%s\n",
292
+ rcvd, error ? rd_kafka_error_string(error) : "none");
293
+ if (error) {
294
+ rd_kafka_error_destroy(error);
295
+ }
296
+
297
+ for (j = 0; j < rcvd; j++) {
298
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
299
+ if (!rkm->err) {
300
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
301
+ "Consumer 2 got redelivered record at "
302
+ "offset %" PRId64 " (delivery_count=%d)",
303
+ rkm->offset,
304
+ rd_kafka_message_delivery_count(rkm));
305
+ consumed2++;
306
+ }
307
+ }
308
+ rd_kafka_messages_destroy(batch);
309
+ batch = NULL;
310
+
311
+ TEST_SAY("Consumer 2 got %d messages (expected 5)\n", consumed2);
312
+ TEST_ASSERT(consumed2 == 5, "Expected 5 verification records, got %d",
313
+ consumed2);
314
+
315
+ rd_free(c1_offsets);
316
+
317
+ test_share_consumer_close(rkshare);
318
+ test_share_destroy(rkshare);
319
+
320
+ SUB_TEST_PASS();
321
+ }
322
+
323
+
324
+ /* ===================================================================
325
+ * Mixed ack types — commit_async with ACCEPT/RELEASE/REJECT.
326
+ *
327
+ * Consumer 1 (explicit) acks each record randomly (ACCEPT ~50%,
328
+ * RELEASE ~40%, REJECT ~10%), calls commit_async after each batch.
329
+ * Redeliveries (delivery_count > 1) are ACCEPT'd. Drain loop
330
+ * flushes remaining redeliveries. Consumer 2 gets unredelivered
331
+ * RELEASE'd records.
332
+ * =================================================================== */
333
+ static void do_test_mixed_acks_second_consumer(void) {
334
+ const char *topic;
335
+ const char *group = "commit-async-mixed-second";
336
+ rd_kafka_share_t *rkshare;
337
+ rd_kafka_error_t *error;
338
+ rd_kafka_messages_t *batch = NULL;
339
+ size_t rcvd;
340
+ size_t j;
341
+ int consumed = 0, redelivered = 0;
342
+ int released_cnt = 0;
343
+ int attempts = 0;
344
+ int64_t *released_offsets;
345
+
346
+ SUB_TEST();
347
+
348
+ topic = test_mk_topic_name("0173-ca-mixed-2nd", 1);
349
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
350
+ test_produce_msgs_simple(common_producer, topic, 0, MAX_MSGS);
351
+
352
+ rkshare = test_create_share_consumer(group, "explicit");
353
+ test_share_set_auto_offset_reset(group, "earliest");
354
+ subscribe_consumer(rkshare, &topic, 1);
355
+
356
+ released_offsets = rd_calloc(MAX_MSGS, sizeof(*released_offsets));
357
+
358
+ /* Consume all records and handle redeliveries in the same loop */
359
+ while ((consumed < MAX_MSGS || redelivered < released_cnt) &&
360
+ attempts++ < 100) {
361
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
362
+ if (error) {
363
+ rd_kafka_error_destroy(error);
364
+ continue;
365
+ }
366
+ rcvd = rd_kafka_messages_count(batch);
367
+
368
+ for (j = 0; j < rcvd; j++) {
369
+ rd_kafka_message_t *rkm =
370
+ rd_kafka_messages_get(batch, j);
371
+ if (!rkm->err) {
372
+ if (rd_kafka_message_delivery_count(rkm) > 1) {
373
+ /* Redelivered — verify it was
374
+ * RELEASE'd and ACCEPT it */
375
+ int k;
376
+ rd_bool_t found = rd_false;
377
+
378
+ for (k = 0; k < released_cnt; k++) {
379
+ if (rkm->offset ==
380
+ released_offsets[k]) {
381
+ found = rd_true;
382
+ released_offsets[k] =
383
+ -1;
384
+ break;
385
+ }
386
+ }
387
+ TEST_ASSERT(
388
+ found,
389
+ "Redelivered offset %" PRId64
390
+ " was not RELEASE'd",
391
+ rkm->offset);
392
+
393
+ rd_kafka_share_acknowledge(rkshare,
394
+ rkm);
395
+ redelivered++;
396
+ } else {
397
+ /* New record — ack with random type */
398
+ rd_kafka_share_AcknowledgeType_t
399
+ ack_type = get_random_ack_type();
400
+
401
+ if (ack_type ==
402
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT) {
403
+ rd_kafka_share_acknowledge(
404
+ rkshare, rkm);
405
+ } else {
406
+ if (ack_type ==
407
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE)
408
+ released_offsets
409
+ [released_cnt++] =
410
+ rkm->offset;
411
+ rd_kafka_share_acknowledge_type(
412
+ rkshare, rkm, ack_type);
413
+ }
414
+ consumed++;
415
+ }
416
+ }
417
+ }
418
+ rd_kafka_messages_destroy(batch);
419
+ batch = NULL;
420
+
421
+ error = rd_kafka_share_commit_async(rkshare);
422
+ TEST_ASSERT(!error, "commit_async failed: %s",
423
+ error ? rd_kafka_error_string(error) : "");
424
+ }
425
+
426
+ TEST_SAY("Consumed %d/%d, released %d, redelivered %d/%d\n", consumed,
427
+ MAX_MSGS, released_cnt, redelivered, released_cnt);
428
+ TEST_ASSERT(consumed == MAX_MSGS, "Expected %d consumed, got %d",
429
+ MAX_MSGS, consumed);
430
+ TEST_ASSERT(redelivered == released_cnt,
431
+ "Expected %d redelivered, got %d", released_cnt,
432
+ redelivered);
433
+
434
+ rd_free(released_offsets);
435
+
436
+ test_share_consumer_close(rkshare);
437
+ test_share_destroy(rkshare);
438
+
439
+ SUB_TEST_PASS();
440
+ }
441
+
442
+
443
+
444
+ /* ===================================================================
445
+ * Multiple topics x partitions — commit_async across many
446
+ * topic-partitions.
447
+ *
448
+ * Creates 10 topics x 5 partitions. Runs 20 rounds: each round
449
+ * produces messages, consumes them, and calls commit_async.
450
+ * =================================================================== */
451
+ static void do_test_multi_topic_partition(void) {
452
+ const int topic_cnt = 10;
453
+ const int part_cnt = 5;
454
+ const int total_parts = topic_cnt * part_cnt;
455
+ const int rounds = 20;
456
+ const int msgs_per_part = 10;
457
+ const int msgs_per_round = msgs_per_part * total_parts;
458
+ const char *group = "commit-async-multi-tp";
459
+ const char *topics[10];
460
+ rd_kafka_share_t *rkshare;
461
+ rd_kafka_error_t *error;
462
+ int t, p, round;
463
+ int total_consumed = 0;
464
+
465
+ SUB_TEST();
466
+
467
+ for (t = 0; t < topic_cnt; t++) {
468
+ topics[t] =
469
+ rd_strdup(test_mk_topic_name("0173-ca-multi-tp", 1));
470
+ test_create_topic_wait_exists(NULL, topics[t], part_cnt, -1,
471
+ 60 * 1000);
472
+ }
473
+
474
+ rkshare = test_create_share_consumer(group, "implicit");
475
+ test_share_set_auto_offset_reset(group, "earliest");
476
+ subscribe_consumer(rkshare, topics, topic_cnt);
477
+
478
+ for (round = 0; round < rounds; round++) {
479
+ int consumed = 0;
480
+ int attempts = 0;
481
+
482
+ for (t = 0; t < topic_cnt; t++) {
483
+ for (p = 0; p < part_cnt; p++)
484
+ test_produce_msgs_simple(common_producer,
485
+ topics[t], p,
486
+ msgs_per_part);
487
+ }
488
+
489
+ while (consumed < msgs_per_round && attempts++ < 100) {
490
+ rd_kafka_messages_t *batch = NULL;
491
+ size_t rcvd;
492
+ size_t j;
493
+
494
+ error = rd_kafka_share_poll(rkshare, 5000, &batch);
495
+ if (error) {
496
+ rd_kafka_error_destroy(error);
497
+ continue;
498
+ }
499
+ rcvd = rd_kafka_messages_count(batch);
500
+
501
+ for (j = 0; j < rcvd; j++) {
502
+ rd_kafka_message_t *rkm =
503
+ rd_kafka_messages_get(batch, j);
504
+ if (!rkm->err)
505
+ consumed++;
506
+ }
507
+ rd_kafka_messages_destroy(batch);
508
+ }
509
+
510
+ TEST_SAY("Round %d: consumed %d/%d messages\n", round, consumed,
511
+ msgs_per_round);
512
+ TEST_ASSERT(consumed == msgs_per_round,
513
+ "Round %d: Expected %d, got %d", round,
514
+ msgs_per_round, consumed);
515
+
516
+ error = rd_kafka_share_commit_async(rkshare);
517
+ TEST_ASSERT(!error, "Round %d: commit_async failed: %s", round,
518
+ error ? rd_kafka_error_string(error) : "");
519
+
520
+ total_consumed += consumed;
521
+ }
522
+
523
+ TEST_SAY("Total consumed across %d rounds: %d\n", rounds,
524
+ total_consumed);
525
+
526
+ test_share_consumer_close(rkshare);
527
+ test_share_destroy(rkshare);
528
+
529
+ for (t = 0; t < topic_cnt; t++)
530
+ rd_free((void *)topics[t]);
531
+
532
+ SUB_TEST_PASS();
533
+ }
534
+
535
+
536
+ /* ===================================================================
537
+ * Produce-consume loop — mixing commit_async and consume_batch
538
+ * piggybacking.
539
+ *
540
+ * 5 rounds: each round produces N, consumes N. Even rounds use
541
+ * commit_async, odd rounds let consume_batch handle acks on the
542
+ * next poll.
543
+ * =================================================================== */
544
+ static void do_test_produce_consume_loop(void) {
545
+ const char *topic;
546
+ const char *group = "commit-async-loop";
547
+ rd_kafka_share_t *rkshare;
548
+ rd_kafka_error_t *error;
549
+ int round;
550
+ const int rounds = 5;
551
+ const int msgs_per_round = MAX_MSGS / rounds;
552
+ int total_consumed = 0;
553
+
554
+ SUB_TEST();
555
+
556
+ topic = test_mk_topic_name("0173-ca-loop", 1);
557
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
558
+
559
+ rkshare = test_create_share_consumer(group, "implicit");
560
+ test_share_set_auto_offset_reset(group, "earliest");
561
+ subscribe_consumer(rkshare, &topic, 1);
562
+
563
+ for (round = 0; round < rounds; round++) {
564
+ int consumed = 0;
565
+ int attempts = 0;
566
+
567
+ test_produce_msgs_simple(common_producer, topic, 0,
568
+ msgs_per_round);
569
+ TEST_SAY("Round %d: produced %d messages\n", round,
570
+ msgs_per_round);
571
+
572
+ while (consumed < msgs_per_round && attempts++ < 100) {
573
+ rd_kafka_messages_t *batch = NULL;
574
+ size_t rcvd;
575
+ size_t j;
576
+
577
+ error = rd_kafka_share_poll(rkshare, 5000, &batch);
578
+ if (error) {
579
+ rd_kafka_error_destroy(error);
580
+ continue;
581
+ }
582
+ rcvd = rd_kafka_messages_count(batch);
583
+
584
+ for (j = 0; j < rcvd; j++) {
585
+ rd_kafka_message_t *rkm =
586
+ rd_kafka_messages_get(batch, j);
587
+ if (!rkm->err)
588
+ consumed++;
589
+ }
590
+ rd_kafka_messages_destroy(batch);
591
+ }
592
+
593
+ TEST_SAY("Round %d: consumed %d/%d messages\n", round, consumed,
594
+ msgs_per_round);
595
+ TEST_ASSERT(consumed == msgs_per_round,
596
+ "Round %d: Expected %d, got %d", round,
597
+ msgs_per_round, consumed);
598
+
599
+ if (round % 2 == 0) {
600
+ error = rd_kafka_share_commit_async(rkshare);
601
+ TEST_ASSERT(!error, "Round %d: commit_async failed: %s",
602
+ round,
603
+ error ? rd_kafka_error_string(error) : "");
604
+ TEST_SAY("Round %d: used commit_async\n", round);
605
+ } else {
606
+ TEST_SAY(
607
+ "Round %d: letting consume_batch handle acks\n",
608
+ round);
609
+ }
610
+
611
+ total_consumed += consumed;
612
+ }
613
+
614
+ TEST_SAY("Total consumed across %d rounds: %d\n", rounds,
615
+ total_consumed);
616
+
617
+ test_share_consumer_close(rkshare);
618
+ test_share_destroy(rkshare);
619
+
620
+ SUB_TEST_PASS();
621
+ }
622
+
623
+
624
+ /* ===================================================================
625
+ * Multi-round mixed acks — per-round produce, consume, and
626
+ * commit_async with same-consumer redelivery handling.
627
+ *
628
+ * 3 rounds: each round produces msgs_per_round, consumes them,
629
+ * acks randomly (ACCEPT/RELEASE/REJECT), calls commit_async.
630
+ * Redeliveries (delivery_count > 1) are ACCEPT'd inline within
631
+ * the same round. Verifies consumed + redelivered counts per round.
632
+ * =================================================================== */
633
+ static void do_test_multi_round_mixed_second_consumer(void) {
634
+ const char *topic;
635
+ const char *group = "commit-async-multi-round-second";
636
+ rd_kafka_share_t *rkshare;
637
+ rd_kafka_error_t *error;
638
+ int round;
639
+ const int rounds = 3;
640
+ const int msgs_per_round = MAX_MSGS / rounds;
641
+ int total_consumed = 0;
642
+ int total_released = 0;
643
+ int total_redelivered = 0;
644
+
645
+ SUB_TEST();
646
+
647
+ topic = test_mk_topic_name("0173-ca-mr-2nd", 1);
648
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
649
+
650
+ rkshare = test_create_share_consumer(group, "explicit");
651
+ test_share_set_auto_offset_reset(group, "earliest");
652
+ subscribe_consumer(rkshare, &topic, 1);
653
+
654
+ for (round = 0; round < rounds; round++) {
655
+ test_produce_msgs_simple(common_producer, topic, 0,
656
+ msgs_per_round);
657
+ int consumed = 0;
658
+ int released_cnt = 0;
659
+ int redelivered = 0;
660
+ int attempts = 0;
661
+
662
+ /* Consume msgs_per_round new records, handling redeliveries
663
+ * from previous rounds/batches inline */
664
+ while (
665
+ (consumed < msgs_per_round || redelivered < released_cnt) &&
666
+ attempts++ < 100) {
667
+ rd_kafka_messages_t *batch = NULL;
668
+ size_t rcvd;
669
+ size_t j;
670
+
671
+ error = rd_kafka_share_poll(rkshare, 5000, &batch);
672
+ if (error) {
673
+ rd_kafka_error_destroy(error);
674
+ continue;
675
+ }
676
+ rcvd = rd_kafka_messages_count(batch);
677
+
678
+ for (j = 0; j < rcvd; j++) {
679
+ rd_kafka_message_t *rkm =
680
+ rd_kafka_messages_get(batch, j);
681
+ if (!rkm->err) {
682
+ if (rd_kafka_message_delivery_count(
683
+ rkm) > 1) {
684
+ rd_kafka_share_acknowledge(
685
+ rkshare, rkm);
686
+ redelivered++;
687
+ } else {
688
+ rd_kafka_share_AcknowledgeType_t
689
+ ack_type =
690
+ get_random_ack_type();
691
+
692
+ if (ack_type ==
693
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT) {
694
+ rd_kafka_share_acknowledge(
695
+ rkshare, rkm);
696
+ } else {
697
+ if (ack_type ==
698
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE)
699
+ released_cnt++;
700
+ rd_kafka_share_acknowledge_type(
701
+ rkshare, rkm,
702
+ ack_type);
703
+ }
704
+ consumed++;
705
+ }
706
+ }
707
+ }
708
+ rd_kafka_messages_destroy(batch);
709
+
710
+ error = rd_kafka_share_commit_async(rkshare);
711
+ TEST_ASSERT(!error, "Round %d: commit_async failed: %s",
712
+ round,
713
+ error ? rd_kafka_error_string(error) : "");
714
+ }
715
+
716
+ TEST_SAY(
717
+ "Round %d: consumed %d/%d, released %d, "
718
+ "redelivered %d/%d\n",
719
+ round, consumed, msgs_per_round, released_cnt, redelivered,
720
+ released_cnt);
721
+ TEST_ASSERT(consumed == msgs_per_round,
722
+ "Round %d: expected %d consumed, got %d", round,
723
+ msgs_per_round, consumed);
724
+ TEST_ASSERT(redelivered == released_cnt,
725
+ "Round %d: expected %d redelivered, got %d", round,
726
+ released_cnt, redelivered);
727
+
728
+ total_consumed += consumed;
729
+ total_released += released_cnt;
730
+ total_redelivered += redelivered;
731
+ }
732
+
733
+ TEST_SAY("Total: consumed %d, released %d, redelivered %d\n",
734
+ total_consumed, total_released, total_redelivered);
735
+
736
+ test_share_consumer_close(rkshare);
737
+ test_share_destroy(rkshare);
738
+
739
+ SUB_TEST_PASS();
740
+ }
741
+
742
+
743
+ /* ===================================================================
744
+ * commit_async with no pending acks — returns NULL (no error).
745
+ * =================================================================== */
746
+ static void do_test_no_pending_acks(void) {
747
+ const char *group = "commit-async-no-pending";
748
+ rd_kafka_share_t *rkshare;
749
+ rd_kafka_error_t *error;
750
+
751
+ SUB_TEST();
752
+
753
+ rkshare = test_create_share_consumer(group, "implicit");
754
+
755
+ error = rd_kafka_share_commit_async(rkshare);
756
+ TEST_SAY("commit_async with no acks: error=%s\n",
757
+ error ? rd_kafka_error_string(error) : "NULL");
758
+ TEST_ASSERT(!error, "Expected NULL when no pending acks, got error: %s",
759
+ error ? rd_kafka_error_string(error) : "");
760
+
761
+ test_share_consumer_close(rkshare);
762
+ test_share_destroy(rkshare);
763
+
764
+ SUB_TEST_PASS();
765
+ }
766
+
767
+
768
+ /* ===================================================================
769
+ * Multiple consecutive commit_async calls — verifies no ack
770
+ * duplication or loss.
771
+ *
772
+ * Produce first set, consume, produce second set, call commit_async
773
+ * 3 times, then consume and verify count equals second produce amount.
774
+ * =================================================================== */
775
+ static void do_test_multiple_commit_async_calls(void) {
776
+ const char *topic;
777
+ const char *group = "commit-async-multiple-calls";
778
+ rd_kafka_share_t *rkshare;
779
+ rd_kafka_error_t *error;
780
+ const int first_produce = MAX_MSGS / 2;
781
+ const int second_produce = MAX_MSGS / 2;
782
+ int consumed = 0, consumed2 = 0, call;
783
+ int attempts = 0;
784
+
785
+ SUB_TEST();
786
+
787
+ topic = test_mk_topic_name("0173-ca-multi-call", 1);
788
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
789
+
790
+ rkshare = test_create_share_consumer(group, "implicit");
791
+ test_share_set_auto_offset_reset(group, "earliest");
792
+ subscribe_consumer(rkshare, &topic, 1);
793
+
794
+ test_produce_msgs_simple(common_producer, topic, 0, first_produce);
795
+
796
+ while (consumed < first_produce && attempts++ < 100) {
797
+ rd_kafka_messages_t *batch = NULL;
798
+ size_t rcvd;
799
+ size_t j;
800
+
801
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
802
+ if (error) {
803
+ rd_kafka_error_destroy(error);
804
+ continue;
805
+ }
806
+ rcvd = rd_kafka_messages_count(batch);
807
+
808
+ for (j = 0; j < rcvd; j++) {
809
+ rd_kafka_message_t *rkm =
810
+ rd_kafka_messages_get(batch, j);
811
+ if (!rkm->err)
812
+ consumed++;
813
+ }
814
+ rd_kafka_messages_destroy(batch);
815
+ }
816
+
817
+ TEST_SAY("First set: consumed %d/%d messages\n", consumed,
818
+ first_produce);
819
+ TEST_ASSERT(consumed == first_produce, "Expected %d, got %d",
820
+ first_produce, consumed);
821
+
822
+ test_produce_msgs_simple(common_producer, topic, 0, second_produce);
823
+
824
+ for (call = 0; call < 10; call++) {
825
+ error = rd_kafka_share_commit_async(rkshare);
826
+ TEST_SAY("commit_async call %d: error=%s\n", call,
827
+ error ? rd_kafka_error_string(error) : "NULL");
828
+ TEST_ASSERT(!error, "commit_async call %d failed: %s", call,
829
+ error ? rd_kafka_error_string(error) : "");
830
+ }
831
+
832
+ attempts = 0;
833
+ while (consumed2 < second_produce && attempts++ < 100) {
834
+ rd_kafka_messages_t *batch = NULL;
835
+ size_t rcvd;
836
+ size_t j;
837
+
838
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
839
+ if (error) {
840
+ rd_kafka_error_destroy(error);
841
+ continue;
842
+ }
843
+ rcvd = rd_kafka_messages_count(batch);
844
+
845
+ for (j = 0; j < rcvd; j++) {
846
+ rd_kafka_message_t *rkm =
847
+ rd_kafka_messages_get(batch, j);
848
+ if (!rkm->err)
849
+ consumed2++;
850
+ }
851
+ rd_kafka_messages_destroy(batch);
852
+ }
853
+
854
+ TEST_SAY("After multiple commits: consumed %d/%d messages\n", consumed2,
855
+ second_produce);
856
+ TEST_ASSERT(consumed2 == second_produce,
857
+ "Expected %d (second produce), got %d", second_produce,
858
+ consumed2);
859
+
860
+ test_share_consumer_close(rkshare);
861
+ test_share_destroy(rkshare);
862
+
863
+ SUB_TEST_PASS();
864
+ }
865
+
866
+
867
+ /* ===================================================================
868
+ * commit_async between produce rounds — correctly separates acks.
869
+ *
870
+ * Produce N, consume N, commit_async, wait for lock timeout,
871
+ * produce N more, consume N, commit_async. Second consumer verifies
872
+ * nothing left.
873
+ * =================================================================== */
874
+ static void do_test_commit_between_produces(void) {
875
+ const char *topic;
876
+ const char *group = "commit-async-between-produces";
877
+ rd_kafka_share_t *rkshare;
878
+ rd_kafka_error_t *error;
879
+ rd_kafka_messages_t *batch = NULL;
880
+ size_t rcvd;
881
+ size_t j;
882
+ const int half = MAX_MSGS / 2;
883
+ int consumed1 = 0, consumed2 = 0, received = 0;
884
+ int attempts = 0;
885
+
886
+ SUB_TEST();
887
+
888
+ topic = test_mk_topic_name("0173-ca-between", 1);
889
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
890
+
891
+ rkshare = test_create_share_consumer(group, "implicit");
892
+ test_share_set_auto_offset_reset(group, "earliest");
893
+ set_group_lock_duration(group, "3000");
894
+ subscribe_consumer(rkshare, &topic, 1);
895
+
896
+ /* First half: produce, wait for records, commit_async */
897
+ test_produce_msgs_simple(common_producer, topic, 0, half);
898
+
899
+ while (consumed1 == 0 && attempts++ < 30) {
900
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
901
+ if (error) {
902
+ rd_kafka_error_destroy(error);
903
+ continue;
904
+ }
905
+ rcvd = rd_kafka_messages_count(batch);
906
+
907
+ for (j = 0; j < rcvd; j++) {
908
+ rd_kafka_message_t *rkm =
909
+ rd_kafka_messages_get(batch, j);
910
+ if (!rkm->err) {
911
+ TEST_ASSERT(
912
+ rd_kafka_message_delivery_count(rkm) == 1,
913
+ "First half: redelivered record at "
914
+ "offset %" PRId64 " (delivery_count=%d)",
915
+ rkm->offset,
916
+ rd_kafka_message_delivery_count(rkm));
917
+ consumed1++;
918
+ }
919
+ }
920
+ rd_kafka_messages_destroy(batch);
921
+ batch = NULL;
922
+ }
923
+
924
+ TEST_SAY("First half: consumed %d messages\n", consumed1);
925
+ TEST_ASSERT(consumed1 == half, "First half: expected %d, got %d", half,
926
+ consumed1);
927
+
928
+ error = rd_kafka_share_commit_async(rkshare);
929
+ TEST_ASSERT(!error, "commit_async failed: %s",
930
+ error ? rd_kafka_error_string(error) : "");
931
+
932
+ /* Wait for acquisition lock timeout (3 s + buffer) so first half's acks
933
+ * are fully committed or released before producing the second
934
+ * half */
935
+ rd_sleep(4);
936
+
937
+ /* Second half: produce more, wait for records, commit_async */
938
+ test_produce_msgs_simple(common_producer, topic, 0, half);
939
+
940
+ attempts = 0;
941
+ while (consumed2 == 0 && attempts++ < 30) {
942
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
943
+ if (error) {
944
+ rd_kafka_error_destroy(error);
945
+ continue;
946
+ }
947
+ rcvd = rd_kafka_messages_count(batch);
948
+
949
+ for (j = 0; j < rcvd; j++) {
950
+ rd_kafka_message_t *rkm =
951
+ rd_kafka_messages_get(batch, j);
952
+ if (!rkm->err) {
953
+ TEST_ASSERT(
954
+ rd_kafka_message_delivery_count(rkm) == 1,
955
+ "Second half: redelivered record at "
956
+ "offset %" PRId64 " (delivery_count=%d)",
957
+ rkm->offset,
958
+ rd_kafka_message_delivery_count(rkm));
959
+ consumed2++;
960
+ }
961
+ }
962
+ rd_kafka_messages_destroy(batch);
963
+ batch = NULL;
964
+ }
965
+
966
+ TEST_SAY("Second half: consumed %d messages\n", consumed2);
967
+ TEST_ASSERT(consumed2 == half, "Second half: expected %d, got %d", half,
968
+ consumed2);
969
+
970
+ error = rd_kafka_share_commit_async(rkshare);
971
+ TEST_ASSERT(!error, "commit_async failed: %s",
972
+ error ? rd_kafka_error_string(error) : "");
973
+
974
+ rd_sleep(3);
975
+
976
+ test_share_consumer_close(rkshare);
977
+ test_share_destroy(rkshare);
978
+
979
+ /* Produce 5 verification records */
980
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
981
+
982
+ /* Second consumer: should only get the 5 verification records.
983
+ * No lock wait needed — implicit mode close tears down the
984
+ * connection and broker releases records immediately. */
985
+ rkshare = test_create_share_consumer(group, "implicit");
986
+ subscribe_consumer(rkshare, &topic, 1);
987
+
988
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
989
+ rcvd = rd_kafka_messages_count(batch);
990
+ TEST_SAY("Consumer 2 consume_batch returned: rcvd=%zu, error=%s\n",
991
+ rcvd, error ? rd_kafka_error_string(error) : "none");
992
+ if (error) {
993
+ rd_kafka_error_destroy(error);
994
+ }
995
+
996
+ for (j = 0; j < rcvd; j++) {
997
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
998
+ if (!rkm->err) {
999
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
1000
+ "Consumer 2 got redelivered record at "
1001
+ "offset %" PRId64 " (delivery_count=%d)",
1002
+ rkm->offset,
1003
+ rd_kafka_message_delivery_count(rkm));
1004
+ received++;
1005
+ }
1006
+ }
1007
+ rd_kafka_messages_destroy(batch);
1008
+ batch = NULL;
1009
+
1010
+ TEST_SAY("Consumer 2 got %d messages (expected 5)\n", received);
1011
+ TEST_ASSERT(received == 5, "Expected 5 verification records, got %d",
1012
+ received);
1013
+
1014
+ test_share_consumer_close(rkshare);
1015
+ test_share_destroy(rkshare);
1016
+
1017
+ SUB_TEST_PASS();
1018
+ }
1019
+
1020
+
1021
+ /* ===================================================================
1022
+ * All RELEASE — commit_async with all-RELEASE acks causes full
1023
+ * redelivery within the same consumer.
1024
+ *
1025
+ * Consumer (explicit) RELEASEs every new record and ACCEPT's
1026
+ * redeliveries (delivery_count > 1). Verifies all original records
1027
+ * are consumed and all redeliveries are received.
1028
+ * =================================================================== */
1029
+ static void do_test_all_release_second_consumer(void) {
1030
+ const char *topic;
1031
+ const char *group = "commit-async-all-release-second";
1032
+ rd_kafka_share_t *rkshare;
1033
+ rd_kafka_error_t *error;
1034
+ int consumed = 0, redelivered = 0;
1035
+ int attempts = 0;
1036
+
1037
+ SUB_TEST();
1038
+
1039
+ topic = test_mk_topic_name("0173-ca-allrel-2nd", 1);
1040
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1041
+ test_produce_msgs_simple(common_producer, topic, 0, MAX_MSGS);
1042
+
1043
+ rkshare = test_create_share_consumer(group, "explicit");
1044
+ test_share_set_auto_offset_reset(group, "earliest");
1045
+ subscribe_consumer(rkshare, &topic, 1);
1046
+
1047
+ /* Consume all records: RELEASE new records, ACCEPT redeliveries.
1048
+ * Loop until all original records are consumed AND all
1049
+ * redeliveries are received. */
1050
+ while ((consumed < MAX_MSGS || redelivered < consumed) &&
1051
+ attempts++ < 200) {
1052
+ rd_kafka_messages_t *batch = NULL;
1053
+ size_t rcvd;
1054
+ size_t j;
1055
+
1056
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1057
+ if (error) {
1058
+ rd_kafka_error_destroy(error);
1059
+ continue;
1060
+ }
1061
+ rcvd = rd_kafka_messages_count(batch);
1062
+
1063
+ for (j = 0; j < rcvd; j++) {
1064
+ rd_kafka_message_t *rkm =
1065
+ rd_kafka_messages_get(batch, j);
1066
+ if (!rkm->err) {
1067
+ if (rd_kafka_message_delivery_count(rkm) > 1) {
1068
+ rd_kafka_share_acknowledge(rkshare,
1069
+ rkm);
1070
+ redelivered++;
1071
+ } else {
1072
+ rd_kafka_share_acknowledge_type(
1073
+ rkshare, rkm,
1074
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE);
1075
+ consumed++;
1076
+ }
1077
+ }
1078
+ }
1079
+ rd_kafka_messages_destroy(batch);
1080
+
1081
+ error = rd_kafka_share_commit_async(rkshare);
1082
+ TEST_ASSERT(!error, "commit_async failed: %s",
1083
+ error ? rd_kafka_error_string(error) : "");
1084
+ }
1085
+
1086
+ TEST_SAY("Consumed %d/%d, redelivered %d/%d\n", consumed, MAX_MSGS,
1087
+ redelivered, consumed);
1088
+ TEST_ASSERT(consumed == MAX_MSGS, "Expected %d consumed, got %d",
1089
+ MAX_MSGS, consumed);
1090
+ TEST_ASSERT(redelivered == consumed, "Expected %d redelivered, got %d",
1091
+ consumed, redelivered);
1092
+
1093
+ test_share_consumer_close(rkshare);
1094
+ test_share_destroy(rkshare);
1095
+
1096
+ SUB_TEST_PASS();
1097
+ }
1098
+
1099
+
1100
+ /* ===================================================================
1101
+ * All REJECT — commit_async with all-REJECT acks. Records are not
1102
+ * redelivered.
1103
+ *
1104
+ * Consumer 1 (explicit) REJECTs every record, calls commit_async.
1105
+ * Consumer 2 verifies 0 messages (REJECT'd records are archived).
1106
+ * =================================================================== */
1107
+ static void do_test_all_reject_second_consumer(void) {
1108
+ const char *topic;
1109
+ const char *group = "commit-async-all-reject-second";
1110
+ rd_kafka_share_t *rkshare;
1111
+ rd_kafka_error_t *error;
1112
+ rd_kafka_messages_t *batch = NULL;
1113
+ size_t rcvd;
1114
+ size_t j;
1115
+ int consumed = 0, received = 0;
1116
+ int attempts = 0;
1117
+
1118
+ SUB_TEST();
1119
+
1120
+ topic = test_mk_topic_name("0173-ca-allrej-2nd", 1);
1121
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1122
+ test_produce_msgs_simple(common_producer, topic, 0, MAX_MSGS);
1123
+
1124
+ rkshare = test_create_share_consumer(group, "explicit");
1125
+ test_share_set_auto_offset_reset(group, "earliest");
1126
+ set_group_lock_duration(group, "3000");
1127
+ subscribe_consumer(rkshare, &topic, 1);
1128
+
1129
+ while (consumed < MAX_MSGS && attempts++ < 100) {
1130
+ size_t rcvd_inner;
1131
+
1132
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1133
+ if (error) {
1134
+ rd_kafka_error_destroy(error);
1135
+ continue;
1136
+ }
1137
+ rcvd_inner = rd_kafka_messages_count(batch);
1138
+
1139
+ for (j = 0; j < rcvd_inner; j++) {
1140
+ rd_kafka_message_t *rkm =
1141
+ rd_kafka_messages_get(batch, j);
1142
+ if (!rkm->err) {
1143
+ rd_kafka_share_acknowledge_type(
1144
+ rkshare, rkm,
1145
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_REJECT);
1146
+ consumed++;
1147
+ }
1148
+ }
1149
+ rd_kafka_messages_destroy(batch);
1150
+ batch = NULL;
1151
+
1152
+ error = rd_kafka_share_commit_async(rkshare);
1153
+ TEST_ASSERT(!error, "commit_async failed: %s",
1154
+ error ? rd_kafka_error_string(error) : "");
1155
+ }
1156
+
1157
+ TEST_SAY("Consumer 1 consumed %d/%d messages\n", consumed, MAX_MSGS);
1158
+ TEST_ASSERT(consumed == MAX_MSGS, "Expected %d consumed, got %d",
1159
+ MAX_MSGS, consumed);
1160
+
1161
+ /* Records are either committed by the last commit_async or
1162
+ * released on the broker side when the connection is closed.
1163
+ * No lock wait needed.
1164
+ * TODO KIP-932: When share consumer close is fully implemented,
1165
+ * these tests may need to wait for the acquisition lock
1166
+ * timeout before closing as close will commit acknowledged
1167
+ * records in explicit acknowledgement mode. */
1168
+
1169
+ rd_sleep(3);
1170
+
1171
+ test_share_consumer_close(rkshare);
1172
+ test_share_destroy(rkshare);
1173
+
1174
+ /* Produce 5 verification records */
1175
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
1176
+
1177
+ /* Second consumer: should only get the 5 verification records.
1178
+ * REJECT'd records are archived and not redelivered. */
1179
+ rkshare = test_create_share_consumer(group, "implicit");
1180
+ subscribe_consumer(rkshare, &topic, 1);
1181
+
1182
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
1183
+ rcvd = rd_kafka_messages_count(batch);
1184
+ TEST_SAY("Consumer 2 consume_batch returned: rcvd=%zu, error=%s\n",
1185
+ rcvd, error ? rd_kafka_error_string(error) : "none");
1186
+ if (error) {
1187
+ rd_kafka_error_destroy(error);
1188
+ }
1189
+
1190
+ for (j = 0; j < rcvd; j++) {
1191
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
1192
+ if (!rkm->err) {
1193
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
1194
+ "Consumer 2 got redelivered record at "
1195
+ "offset %" PRId64 " (delivery_count=%d)",
1196
+ rkm->offset,
1197
+ rd_kafka_message_delivery_count(rkm));
1198
+ received++;
1199
+ }
1200
+ }
1201
+ rd_kafka_messages_destroy(batch);
1202
+ batch = NULL;
1203
+
1204
+ TEST_SAY("Consumer 2 got %d messages (expected 5)\n", received);
1205
+ TEST_ASSERT(received == 5, "Expected 5 verification records, got %d",
1206
+ received);
1207
+
1208
+ test_share_consumer_close(rkshare);
1209
+ test_share_destroy(rkshare);
1210
+
1211
+ SUB_TEST_PASS();
1212
+ }
1213
+
1214
+
1215
+ /* ===================================================================
1216
+ * Per-record commit_async — acknowledges each record individually
1217
+ * and calls commit_async after each acknowledgement.
1218
+ *
1219
+ * Waits for acquisition lock timeout, then consumer 2 verifies all
1220
+ * records were committed (0 redelivered).
1221
+ * =================================================================== */
1222
+ static void do_test_per_record_commit_async(void) {
1223
+ const char *topic;
1224
+ const char *group = "commit-async-per-record";
1225
+ rd_kafka_share_t *rkshare;
1226
+ rd_kafka_error_t *error;
1227
+ rd_kafka_messages_t *batch = NULL;
1228
+ size_t rcvd;
1229
+ size_t j;
1230
+ int consumed = 0, received = 0;
1231
+ int attempts = 0;
1232
+
1233
+ SUB_TEST();
1234
+
1235
+ topic = test_mk_topic_name("0173-ca-per-rec", 1);
1236
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1237
+ test_produce_msgs_simple(common_producer, topic, 0, MAX_MSGS);
1238
+
1239
+ rkshare = test_create_share_consumer(group, "explicit");
1240
+ test_share_set_auto_offset_reset(group, "earliest");
1241
+ set_group_lock_duration(group, "3000");
1242
+ subscribe_consumer(rkshare, &topic, 1);
1243
+
1244
+ /* Consume all records, ACCEPT each individually with
1245
+ * commit_async after every record */
1246
+ while (consumed < MAX_MSGS && attempts++ < 100) {
1247
+ size_t rcvd_inner;
1248
+
1249
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1250
+ if (error) {
1251
+ rd_kafka_error_destroy(error);
1252
+ continue;
1253
+ }
1254
+ rcvd_inner = rd_kafka_messages_count(batch);
1255
+
1256
+ for (j = 0; j < rcvd_inner; j++) {
1257
+ rd_kafka_message_t *rkm =
1258
+ rd_kafka_messages_get(batch, j);
1259
+ if (!rkm->err) {
1260
+ rd_kafka_share_acknowledge(rkshare, rkm);
1261
+ consumed++;
1262
+
1263
+ error = rd_kafka_share_commit_async(rkshare);
1264
+ TEST_ASSERT(
1265
+ !error, "commit_async at msg %d failed: %s",
1266
+ consumed,
1267
+ error ? rd_kafka_error_string(error) : "");
1268
+ }
1269
+ }
1270
+ rd_kafka_messages_destroy(batch);
1271
+ batch = NULL;
1272
+ }
1273
+
1274
+ TEST_SAY("Consumed %d/%d messages with per-record commit_async\n",
1275
+ consumed, MAX_MSGS);
1276
+ TEST_ASSERT(consumed == MAX_MSGS, "Expected %d, got %d", MAX_MSGS,
1277
+ consumed);
1278
+
1279
+ /* Records are either committed by the last commit_async or
1280
+ * released on the broker side when the connection is closed.
1281
+ * No lock wait needed.
1282
+ * TODO KIP-932: When share consumer close is fully implemented,
1283
+ * these tests may need to wait for the acquisition lock
1284
+ * timeout before closing as close will commit acknowledged
1285
+ * records in explicit acknowledgement mode. */
1286
+
1287
+ /* Wait for async commits to propagate */
1288
+ rd_sleep(3);
1289
+
1290
+ test_share_consumer_close(rkshare);
1291
+ test_share_destroy(rkshare);
1292
+
1293
+ /* Produce 5 verification records */
1294
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
1295
+
1296
+ /* Second consumer: should only get the 5 verification records.
1297
+ * All previous records were ACCEPT'd via per-record commit_async. */
1298
+ rkshare = test_create_share_consumer(group, "implicit");
1299
+ subscribe_consumer(rkshare, &topic, 1);
1300
+
1301
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
1302
+ rcvd = rd_kafka_messages_count(batch);
1303
+ TEST_SAY("Consumer 2 consume_batch returned: rcvd=%zu, error=%s\n",
1304
+ rcvd, error ? rd_kafka_error_string(error) : "none");
1305
+ if (error) {
1306
+ rd_kafka_error_destroy(error);
1307
+ }
1308
+
1309
+ for (j = 0; j < rcvd; j++) {
1310
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
1311
+ if (!rkm->err) {
1312
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
1313
+ "Consumer 2 got redelivered record at "
1314
+ "offset %" PRId64 " (delivery_count=%d)",
1315
+ rkm->offset,
1316
+ rd_kafka_message_delivery_count(rkm));
1317
+ received++;
1318
+ }
1319
+ }
1320
+ rd_kafka_messages_destroy(batch);
1321
+ batch = NULL;
1322
+
1323
+ TEST_SAY("Consumer 2 got %d messages (expected 5)\n", received);
1324
+ TEST_ASSERT(received == 5, "Expected 5 verification records, got %d",
1325
+ received);
1326
+
1327
+ test_share_consumer_close(rkshare);
1328
+ test_share_destroy(rkshare);
1329
+
1330
+ SUB_TEST_PASS();
1331
+ }
1332
+
1333
+
1334
+ /* ===================================================================
1335
+ * Mock broker helpers.
1336
+ * =================================================================== */
1337
+
1338
+ typedef struct test_ctx_s {
1339
+ rd_kafka_t *producer;
1340
+ rd_kafka_mock_cluster_t *mcluster;
1341
+ const char *bootstraps;
1342
+ } test_ctx_t;
1343
+
1344
+ static test_ctx_t test_ctx_new(void) {
1345
+ test_ctx_t ctx;
1346
+ rd_kafka_conf_t *conf;
1347
+ char errstr[512];
1348
+
1349
+ memset(&ctx, 0, sizeof(ctx));
1350
+
1351
+ ctx.mcluster = test_mock_cluster_new(3, &ctx.bootstraps);
1352
+
1353
+ TEST_ASSERT(rd_kafka_mock_set_apiversion(
1354
+ ctx.mcluster, RD_KAFKAP_ShareGroupHeartbeat, 1, 1) ==
1355
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1356
+ "Failed to enable ShareGroupHeartbeat");
1357
+ TEST_ASSERT(rd_kafka_mock_set_apiversion(ctx.mcluster,
1358
+ RD_KAFKAP_ShareFetch, 1, 1) ==
1359
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1360
+ "Failed to enable ShareFetch");
1361
+
1362
+ /* Set auto.offset.reset=earliest so tests that produce
1363
+ * before consuming see all records. */
1364
+ rd_kafka_mock_sharegroup_set_auto_offset_reset(ctx.mcluster, 1);
1365
+
1366
+ test_conf_init(&conf, NULL, 0);
1367
+ test_conf_set(conf, "bootstrap.servers", ctx.bootstraps);
1368
+ rd_kafka_conf_set_dr_msg_cb(conf, test_dr_msg_cb);
1369
+
1370
+ ctx.producer =
1371
+ rd_kafka_new(RD_KAFKA_PRODUCER, conf, errstr, sizeof(errstr));
1372
+ TEST_ASSERT(ctx.producer != NULL, "Failed to create producer: %s",
1373
+ errstr);
1374
+
1375
+ return ctx;
1376
+ }
1377
+
1378
+ static void test_ctx_destroy(test_ctx_t *ctx) {
1379
+ if (ctx->producer)
1380
+ rd_kafka_destroy(ctx->producer);
1381
+ if (ctx->mcluster)
1382
+ test_mock_cluster_destroy(ctx->mcluster);
1383
+ memset(ctx, 0, sizeof(*ctx));
1384
+ }
1385
+
1386
+ static rd_kafka_share_t *new_share_consumer(const char *bootstraps,
1387
+ const char *group_id,
1388
+ const char *ack_mode) {
1389
+ rd_kafka_conf_t *conf;
1390
+ rd_kafka_share_t *rkshare;
1391
+
1392
+ test_conf_init(&conf, NULL, 0);
1393
+ test_conf_set(conf, "bootstrap.servers", bootstraps);
1394
+ test_conf_set(conf, "group.id", group_id);
1395
+ test_conf_set(conf, "share.acknowledgement.mode", ack_mode);
1396
+
1397
+ rkshare = rd_kafka_share_consumer_new(conf, NULL, 0);
1398
+ TEST_ASSERT(rkshare != NULL, "Failed to create share consumer");
1399
+ return rkshare;
1400
+ }
1401
+
1402
+ static void count_share_requests(rd_kafka_mock_cluster_t *mcluster,
1403
+ int *share_fetch_cntp,
1404
+ int *share_ack_cntp) {
1405
+ size_t cnt;
1406
+ size_t i;
1407
+ rd_kafka_mock_request_t **requests;
1408
+
1409
+ *share_fetch_cntp = 0;
1410
+ *share_ack_cntp = 0;
1411
+
1412
+ requests = rd_kafka_mock_get_requests(mcluster, &cnt);
1413
+
1414
+ for (i = 0; i < cnt; i++) {
1415
+ int16_t api_key = rd_kafka_mock_request_api_key(requests[i]);
1416
+ if (api_key == RD_KAFKAP_ShareFetch)
1417
+ (*share_fetch_cntp)++;
1418
+ else if (api_key == RD_KAFKAP_ShareAcknowledge)
1419
+ (*share_ack_cntp)++;
1420
+ }
1421
+
1422
+ rd_kafka_mock_request_destroy_array(requests, cnt);
1423
+ }
1424
+
1425
+
1426
+ /* ===================================================================
1427
+ * Mock broker — inflight request caching.
1428
+ *
1429
+ * Consumes records, acknowledges each individually and calls
1430
+ * commit_async after each acknowledgement. Verifies ShareFetch
1431
+ * request count < commit_async call count, proving acks are cached
1432
+ * when a request is already inflight (rkb_share_fetch_enqueued=true).
1433
+ * =================================================================== */
1434
+ static void do_test_mock_inflight_caching(void) {
1435
+ test_ctx_t ctx;
1436
+ rd_kafka_share_t *rkshare;
1437
+ rd_kafka_error_t *error;
1438
+ const char *topic = "mock-inflight-cache";
1439
+ const char *t = topic;
1440
+ const int msgcnt = 100;
1441
+ int consumed = 0, i = 0;
1442
+ int share_fetch_cnt, share_ack_cnt;
1443
+ int commit_cnt = 0;
1444
+
1445
+ SUB_TEST_QUICK();
1446
+
1447
+ ctx = test_ctx_new();
1448
+
1449
+ TEST_ASSERT(rd_kafka_mock_topic_create(ctx.mcluster, topic, 1, 1) ==
1450
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1451
+ "Failed to create mock topic");
1452
+
1453
+ test_produce_msgs_simple(ctx.producer, topic, RD_KAFKA_PARTITION_UA,
1454
+ msgcnt);
1455
+
1456
+ rkshare =
1457
+ new_share_consumer(ctx.bootstraps, "sg-mock-inflight", "explicit");
1458
+
1459
+ subscribe_consumer(rkshare, &t, 1);
1460
+
1461
+ /* Clear and start tracking requests before the consume+ack+commit
1462
+ * loop */
1463
+ rd_kafka_mock_start_request_tracking(ctx.mcluster);
1464
+ rd_kafka_mock_clear_requests(ctx.mcluster);
1465
+
1466
+ /* Consume records batch-by-batch, acknowledging each record
1467
+ * individually and calling commit_async after each acknowledgement.
1468
+ * Most commit_async calls will find an inflight request and
1469
+ * cache the acks in rkb_share_async_ack_details instead of
1470
+ * sending a new ShareFetch request. */
1471
+ while (consumed < msgcnt && i < 30) {
1472
+ rd_kafka_messages_t *batch = NULL;
1473
+ size_t rcvd;
1474
+ size_t j;
1475
+
1476
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1477
+ i++;
1478
+ if (error) {
1479
+ rd_kafka_error_destroy(error);
1480
+ continue;
1481
+ }
1482
+ rcvd = rd_kafka_messages_count(batch);
1483
+
1484
+ for (j = 0; j < rcvd; j++) {
1485
+ rd_kafka_message_t *rkm =
1486
+ rd_kafka_messages_get(batch, j);
1487
+ if (!rkm->err) {
1488
+ rd_kafka_share_acknowledge(rkshare, rkm);
1489
+ consumed++;
1490
+
1491
+ error = rd_kafka_share_commit_async(rkshare);
1492
+ TEST_ASSERT(
1493
+ !error, "commit_async at msg %d failed: %s",
1494
+ consumed,
1495
+ error ? rd_kafka_error_string(error) : "");
1496
+ commit_cnt++;
1497
+ }
1498
+ }
1499
+ rd_kafka_messages_destroy(batch);
1500
+ }
1501
+ TEST_SAY("Mock: consumed %d/%d\n", consumed, msgcnt);
1502
+ TEST_ASSERT(consumed == msgcnt, "Expected %d, got %d", msgcnt,
1503
+ consumed);
1504
+
1505
+ /* Wait for async ops to complete before counting requests */
1506
+ rd_sleep(3);
1507
+
1508
+ count_share_requests(ctx.mcluster, &share_fetch_cnt, &share_ack_cnt);
1509
+ rd_kafka_mock_stop_request_tracking(ctx.mcluster);
1510
+
1511
+ /* We called commit_async 100 times but the total number of
1512
+ * ShareFetch + ShareAcknowledge requests should be much fewer
1513
+ * due to inflight caching. Ack-only requests use ShareAcknowledge
1514
+ * RPC instead of ShareFetch. */
1515
+ TEST_SAY(
1516
+ "Mock: commit_async calls=%d, ShareFetch requests=%d, "
1517
+ "ShareAcknowledge requests=%d\n",
1518
+ commit_cnt, share_fetch_cnt, share_ack_cnt);
1519
+ TEST_ASSERT(share_ack_cnt > 0,
1520
+ "Expected at least one ShareAcknowledge request, got %d",
1521
+ share_ack_cnt);
1522
+ TEST_ASSERT(share_fetch_cnt + share_ack_cnt < commit_cnt,
1523
+ "Expected fewer total share requests (%d + %d = %d) than "
1524
+ "commit_async calls (%d) due to inflight caching",
1525
+ share_fetch_cnt, share_ack_cnt,
1526
+ share_fetch_cnt + share_ack_cnt, commit_cnt);
1527
+
1528
+ test_share_consumer_close(rkshare);
1529
+ test_share_destroy(rkshare);
1530
+ test_ctx_destroy(&ctx);
1531
+
1532
+ SUB_TEST_PASS();
1533
+ }
1534
+
1535
+
1536
+ /* ===================================================================
1537
+ * Acquisition lock timeout — verify that records are redelivered
1538
+ * to the SAME consumer after lock expiry when not acknowledged.
1539
+ *
1540
+ * TODO KIP-932: Move this to 0171 file maybe when admin client
1541
+ * related things are finalized in that test case.
1542
+ *
1543
+ * Steps:
1544
+ * 1. Produce 10 records.
1545
+ * 2. Consume all 10 — verify delivery_count == 1.
1546
+ * 3. Do NOT acknowledge or commit.
1547
+ * 4. Wait for the 3 s acquisition lock to expire.
1548
+ * 5. Consume again — verify the same 10 records arrive with
1549
+ * delivery_count == 2.
1550
+ * =================================================================== */
1551
+ static void do_test_lock_timeout_redelivery(void) {
1552
+ const char *topic;
1553
+ const char *group = "commit-async-lock-timeout";
1554
+ rd_kafka_share_t *rkshare;
1555
+ rd_kafka_error_t *error;
1556
+ rd_kafka_messages_t *batch = NULL;
1557
+ size_t rcvd;
1558
+ size_t j;
1559
+ int consumed1 = 0, consumed2 = 0;
1560
+ int attempts;
1561
+ const int msg_cnt = 10;
1562
+
1563
+ SUB_TEST("Lock timeout redelivery to same consumer");
1564
+
1565
+ topic = test_mk_topic_name("0173-ca-lock-to", 1);
1566
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1567
+ test_produce_msgs_simple(common_producer, topic, 0, msg_cnt);
1568
+
1569
+ rkshare = test_create_share_consumer(group, "implicit");
1570
+ test_share_set_auto_offset_reset(group, "earliest");
1571
+ set_group_lock_duration(group, "3000");
1572
+ subscribe_consumer(rkshare, &topic, 1);
1573
+
1574
+ /* First consume: get all records, verify delivery_count == 1.
1575
+ * Do NOT call commit_async — records stay ACQUIRED. */
1576
+ attempts = 0;
1577
+ while (consumed1 == 0 && attempts++ < 30) {
1578
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1579
+ if (error) {
1580
+ rd_kafka_error_destroy(error);
1581
+ continue;
1582
+ }
1583
+ rcvd = rd_kafka_messages_count(batch);
1584
+
1585
+ for (j = 0; j < rcvd; j++) {
1586
+ rd_kafka_message_t *rkm =
1587
+ rd_kafka_messages_get(batch, j);
1588
+ if (!rkm->err) {
1589
+ TEST_ASSERT(
1590
+ rd_kafka_message_delivery_count(rkm) == 1,
1591
+ "First consume: expected delivery_count=1, "
1592
+ "got %d at offset %" PRId64,
1593
+ rd_kafka_message_delivery_count(rkm),
1594
+ rkm->offset);
1595
+ consumed1++;
1596
+ }
1597
+ }
1598
+ rd_kafka_messages_destroy(batch);
1599
+ batch = NULL;
1600
+ }
1601
+
1602
+ TEST_SAY("First consume: got %d/%d records (not acknowledged)\n",
1603
+ consumed1, msg_cnt);
1604
+ TEST_ASSERT(consumed1 > 0, "Expected records on first consume, got 0");
1605
+
1606
+ /* Wait for acquisition lock to expire (3 s + buffer) */
1607
+ TEST_SAY("Waiting 4 s for acquisition lock to expire...\n");
1608
+ rd_sleep(4);
1609
+
1610
+ /* Second consume on the SAME consumer: records should be
1611
+ * redelivered with delivery_count == 2. */
1612
+ attempts = 0;
1613
+ while (consumed2 == 0 && attempts++ < 30) {
1614
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1615
+ if (error) {
1616
+ rd_kafka_error_destroy(error);
1617
+ continue;
1618
+ }
1619
+ rcvd = rd_kafka_messages_count(batch);
1620
+
1621
+ for (j = 0; j < rcvd; j++) {
1622
+ rd_kafka_message_t *rkm =
1623
+ rd_kafka_messages_get(batch, j);
1624
+ if (!rkm->err) {
1625
+ TEST_ASSERT(
1626
+ rd_kafka_message_delivery_count(rkm) == 2,
1627
+ "Second consume: expected "
1628
+ "delivery_count=2, got %d at "
1629
+ "offset %" PRId64,
1630
+ rd_kafka_message_delivery_count(rkm),
1631
+ rkm->offset);
1632
+ consumed2++;
1633
+ }
1634
+ }
1635
+ rd_kafka_messages_destroy(batch);
1636
+ batch = NULL;
1637
+ }
1638
+
1639
+ TEST_SAY("Second consume: got %d/%d redelivered records\n", consumed2,
1640
+ msg_cnt);
1641
+ TEST_ASSERT(consumed2 > 0, "Expected redelivered records, got 0");
1642
+
1643
+ test_share_consumer_close(rkshare);
1644
+ test_share_destroy(rkshare);
1645
+
1646
+ SUB_TEST_PASS();
1647
+ }
1648
+
1649
+
1650
+ /* ===================================================================
1651
+ * Test: commit_async callback invocation.
1652
+ *
1653
+ * Verifies that share_acknowledgement_commit_cb is invoked after
1654
+ * commit_async when acks are piggybacked on ShareFetch.
1655
+ * =================================================================== */
1656
+ static void do_test_commit_async_callback(void) {
1657
+ const char *topic;
1658
+ const char *group = "commit-async-callback";
1659
+ rd_kafka_share_t *rkshare;
1660
+ rd_kafka_error_t *error;
1661
+ rd_kafka_messages_t *batch = NULL;
1662
+ size_t rcvd;
1663
+ size_t j;
1664
+ int consumed = 0;
1665
+ int attempts = 0;
1666
+ test_ack_cb_state_t state = {0};
1667
+
1668
+ SUB_TEST();
1669
+
1670
+ topic = test_mk_topic_name("0173-ca-callback", 1);
1671
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1672
+ test_produce_msgs_simple(common_producer, topic, 0, 50);
1673
+
1674
+ rkshare =
1675
+ test_create_share_consumer_with_cb(group, "implicit", &state, NULL);
1676
+ const char *grp_conf[] = {"share.auto.offset.reset", "SET", "earliest"};
1677
+ test_alter_group_configurations(group, grp_conf, 1);
1678
+ subscribe_consumer(rkshare, &topic, 1);
1679
+
1680
+ /* Consume some messages */
1681
+ while (consumed < 20 && attempts++ < 30) {
1682
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1683
+ if (error) {
1684
+ rd_kafka_error_destroy(error);
1685
+ continue;
1686
+ }
1687
+ rcvd = rd_kafka_messages_count(batch);
1688
+ for (j = 0; j < rcvd; j++) {
1689
+ rd_kafka_message_t *rkm =
1690
+ rd_kafka_messages_get(batch, j);
1691
+ if (!rkm->err)
1692
+ consumed++;
1693
+ }
1694
+ rd_kafka_messages_destroy(batch);
1695
+ batch = NULL;
1696
+ }
1697
+
1698
+ TEST_SAY("Consumed %d messages\n", consumed);
1699
+ TEST_ASSERT(consumed > 0, "Expected to consume some messages");
1700
+
1701
+ /* Call commit_async to trigger callback */
1702
+ error = rd_kafka_share_commit_async(rkshare);
1703
+ TEST_ASSERT(!error, "commit_async failed: %s",
1704
+ error ? rd_kafka_error_string(error) : "");
1705
+
1706
+ /* Wait for callback */
1707
+ test_wait_for_cb_with_poll(&state, rkshare, 1, 10000);
1708
+
1709
+ TEST_SAY("Callback count=%d, total_offsets=%zu, last_err=%s\n",
1710
+ state.callback_cnt, state.total_offsets,
1711
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state)));
1712
+
1713
+ TEST_ASSERT(state.callback_cnt >= 1,
1714
+ "Expected at least 1 callback, got %d", state.callback_cnt);
1715
+ TEST_ASSERT(state.total_offsets > 0,
1716
+ "Expected offsets in callback, got %zu",
1717
+ state.total_offsets);
1718
+
1719
+ test_share_consumer_close(rkshare);
1720
+ test_share_destroy(rkshare);
1721
+ test_ack_cb_state_destroy(&state);
1722
+
1723
+ SUB_TEST_PASS();
1724
+ }
1725
+
1726
+
1727
+ /* ===================================================================
1728
+ * Partial-batch commit_async semantics.
1729
+ *
1730
+ * In explicit mode, the next consume_batch must error while any record
1731
+ * from the previous batch is un-acked. After the remaining records are
1732
+ * acked and a second commit_async is issued, consume_batch can drive
1733
+ * the piggybacked acks and the commit callback fires for all offsets.
1734
+ * =================================================================== */
1735
+ static void do_test_partial_batch_commit_async(void) {
1736
+ const char *topic;
1737
+ const char *group = "commit-async-partial-batch";
1738
+ rd_kafka_share_t *rkshare;
1739
+ rd_kafka_error_t *error;
1740
+ rd_kafka_resp_err_t ack_err;
1741
+ rd_kafka_messages_t *batches[CONSUME_ARRAY] = {NULL};
1742
+ size_t batch_cnt = 0;
1743
+ size_t total = 0;
1744
+ size_t j;
1745
+ test_ack_cb_state_t state = {0};
1746
+ const int msg_cnt = 5;
1747
+ int attempts = 0;
1748
+ size_t to_ack_first;
1749
+ size_t acked = 0;
1750
+ size_t seen = 0;
1751
+ int wait_elapsed = 0;
1752
+ rd_kafka_messages_t *wait_batch = NULL;
1753
+ rd_kafka_error_t *wait_err = NULL;
1754
+
1755
+ SUB_TEST();
1756
+
1757
+ topic = test_mk_topic_name("0173-ca-partial", 1);
1758
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1759
+ test_produce_msgs_simple(common_producer, topic, 0, msg_cnt);
1760
+
1761
+ rkshare =
1762
+ test_create_share_consumer_with_cb(group, "explicit", &state, NULL);
1763
+ test_share_set_auto_offset_reset(group, "earliest");
1764
+ subscribe_consumer(rkshare, &topic, 1);
1765
+
1766
+ /* Consume all msg_cnt records (may take multiple polls). Keep each
1767
+ * returned batch alive so the message pointers remain valid for
1768
+ * later acknowledgement. */
1769
+ while (total < (size_t)msg_cnt && attempts++ < 30) {
1770
+ rd_kafka_messages_t *poll_batch = NULL;
1771
+
1772
+ error = rd_kafka_share_poll(rkshare, 3000, &poll_batch);
1773
+ if (error) {
1774
+ rd_kafka_error_destroy(error);
1775
+ continue;
1776
+ }
1777
+ batches[batch_cnt++] = poll_batch;
1778
+ total += rd_kafka_messages_count(poll_batch);
1779
+ }
1780
+ TEST_ASSERT(total == (size_t)msg_cnt,
1781
+ "Expected exactly %d records, got %zu", msg_cnt, total);
1782
+
1783
+ /* Acknowledge first 3 records across the accumulated batches; leave
1784
+ * the remainder un-acked. */
1785
+ to_ack_first = 3;
1786
+ acked = 0;
1787
+ for (j = 0; j < batch_cnt && acked < to_ack_first; j++) {
1788
+ size_t k, n = rd_kafka_messages_count(batches[j]);
1789
+ for (k = 0; k < n && acked < to_ack_first; k++) {
1790
+ rd_kafka_message_t *rkm =
1791
+ rd_kafka_messages_get(batches[j], k);
1792
+ ack_err = rd_kafka_share_acknowledge_type(
1793
+ rkshare, rkm,
1794
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT);
1795
+ TEST_ASSERT(ack_err == RD_KAFKA_RESP_ERR_NO_ERROR,
1796
+ "First-half ACCEPT %zu failed: %s", acked,
1797
+ rd_kafka_err2str(ack_err));
1798
+ acked++;
1799
+ }
1800
+ }
1801
+
1802
+ /* commit_async itself succeeds — it just schedules. */
1803
+ error = rd_kafka_share_commit_async(rkshare);
1804
+ TEST_ASSERT(!error, "First commit_async failed: %s",
1805
+ error ? rd_kafka_error_string(error) : "");
1806
+
1807
+ /* While un-acked records remain in the batch, share_poll must
1808
+ * return _STATE rather than proceed. */
1809
+ rd_kafka_messages_t *dummy = NULL;
1810
+ rd_kafka_error_t *e = rd_kafka_share_poll(rkshare, 1000, &dummy);
1811
+ TEST_ASSERT(e != NULL,
1812
+ "Expected share_poll to return _STATE while "
1813
+ "un-acked records remain, got NULL error");
1814
+ TEST_ASSERT(rd_kafka_error_code(e) == RD_KAFKA_RESP_ERR__STATE,
1815
+ "Expected _STATE, got %s",
1816
+ rd_kafka_err2name(rd_kafka_error_code(e)));
1817
+ TEST_SAY("Got expected _STATE from share_poll: %s\n",
1818
+ rd_kafka_error_string(e));
1819
+ rd_kafka_error_destroy(e);
1820
+ rd_kafka_messages_destroy(dummy);
1821
+
1822
+ /* Acknowledge the remaining records across the batches. */
1823
+ for (j = 0; j < batch_cnt; j++) {
1824
+ size_t k, n = rd_kafka_messages_count(batches[j]);
1825
+ for (k = 0; k < n; k++) {
1826
+ rd_kafka_message_t *rkm;
1827
+ if (seen < to_ack_first) {
1828
+ seen++;
1829
+ continue;
1830
+ }
1831
+ rkm = rd_kafka_messages_get(batches[j], k);
1832
+ ack_err = rd_kafka_share_acknowledge_type(
1833
+ rkshare, rkm,
1834
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT);
1835
+ TEST_ASSERT(ack_err == RD_KAFKA_RESP_ERR_NO_ERROR,
1836
+ "Second-half ACCEPT %zu (offset=%" PRId64
1837
+ ") failed: %s",
1838
+ seen, rkm->offset,
1839
+ rd_kafka_err2str(ack_err));
1840
+ seen++;
1841
+ }
1842
+ }
1843
+
1844
+ /* Second commit_async commits the remaining acks */
1845
+ error = rd_kafka_share_commit_async(rkshare);
1846
+ TEST_ASSERT(!error, "Second commit_async failed: %s",
1847
+ error ? rd_kafka_error_string(error) : "");
1848
+
1849
+ /* Now share_poll can proceed and drive piggybacked acks. The two
1850
+ * commits may surface as one or two callbacks depending on broker
1851
+ * coalescing, so wait on total_offsets rather than callback_cnt:
1852
+ * the first commit's callback can fire during the _STATE-returning
1853
+ * poll above, which would short-circuit a callback_cnt>=1 wait
1854
+ * before the second commit lands. */
1855
+ while (state.total_offsets < (size_t)total && wait_elapsed < 15000) {
1856
+ wait_batch = NULL;
1857
+ wait_err = rd_kafka_share_poll(rkshare, 100, &wait_batch);
1858
+ if (wait_err)
1859
+ rd_kafka_error_destroy(wait_err);
1860
+ rd_kafka_messages_destroy(wait_batch);
1861
+ wait_elapsed += 100;
1862
+ }
1863
+ TEST_ASSERT(state.callback_cnt >= 1,
1864
+ "Expected ack commit callback to fire after full batch "
1865
+ "ack + commit_async, got %d",
1866
+ state.callback_cnt);
1867
+ TEST_ASSERT(test_ack_cb_state_first_err(&state) ==
1868
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1869
+ "Callback errored: %s",
1870
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state)));
1871
+ TEST_ASSERT(state.total_offsets >= (size_t)total,
1872
+ "Expected callback to report %zu offsets, got %zu", total,
1873
+ state.total_offsets);
1874
+ TEST_SAY("Partial commit callbacks=%d, total_offsets=%zu\n",
1875
+ state.callback_cnt, state.total_offsets);
1876
+
1877
+ for (j = 0; j < batch_cnt; j++)
1878
+ rd_kafka_messages_destroy(batches[j]);
1879
+
1880
+ test_share_consumer_close(rkshare);
1881
+ test_share_destroy(rkshare);
1882
+ test_ack_cb_state_destroy(&state);
1883
+
1884
+ SUB_TEST_PASS();
1885
+ }
1886
+
1887
+
1888
+ /* ===================================================================
1889
+ * Implicit ack callback fires from next poll's piggybacked acks
1890
+ * without any explicit commit_async call.
1891
+ * =================================================================== */
1892
+ static void do_test_implicit_callback_no_explicit_commit(void) {
1893
+ const char *topic;
1894
+ const char *group = "commit-async-implicit-no-commit";
1895
+ rd_kafka_share_t *rkshare;
1896
+ rd_kafka_error_t *error;
1897
+ rd_kafka_messages_t *batch = NULL;
1898
+ size_t rcvd, j;
1899
+ test_ack_cb_state_t state = {0};
1900
+ const int msg_cnt = 20;
1901
+ int consumed = 0;
1902
+ int attempts = 0;
1903
+
1904
+ SUB_TEST();
1905
+
1906
+ topic = test_mk_topic_name("0173-ca-impl-no-commit", 1);
1907
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1908
+ test_produce_msgs_simple(common_producer, topic, 0, msg_cnt);
1909
+
1910
+ rkshare =
1911
+ test_create_share_consumer_with_cb(group, "implicit", &state, NULL);
1912
+ test_share_set_auto_offset_reset(group, "earliest");
1913
+ subscribe_consumer(rkshare, &topic, 1);
1914
+
1915
+ /* First poll: drain all produced records. share_poll typically
1916
+ * returns the whole broker-side RecordBatch (so all msg_cnt records
1917
+ * arrive in one call), but the loop tolerates split deliveries. */
1918
+ while (consumed < msg_cnt && attempts++ < 30) {
1919
+ error = rd_kafka_share_poll(rkshare, 2000, &batch);
1920
+ if (error) {
1921
+ rd_kafka_error_destroy(error);
1922
+ continue;
1923
+ }
1924
+ rcvd = rd_kafka_messages_count(batch);
1925
+ for (j = 0; j < rcvd; j++) {
1926
+ rd_kafka_message_t *rkm =
1927
+ rd_kafka_messages_get(batch, j);
1928
+ if (!rkm->err)
1929
+ consumed++;
1930
+ }
1931
+ rd_kafka_messages_destroy(batch);
1932
+ batch = NULL;
1933
+ }
1934
+ TEST_ASSERT(consumed == msg_cnt,
1935
+ "Expected to consume %d messages, got %d", msg_cnt,
1936
+ consumed);
1937
+
1938
+ /* Subsequent polls without any commit_async — callback should still
1939
+ * fire via piggybacked acks on the next ShareFetch. */
1940
+ attempts = 10;
1941
+ while (attempts-- > 0 && state.callback_cnt == 0) {
1942
+ error = rd_kafka_share_poll(rkshare, 2000, &batch);
1943
+ if (error)
1944
+ rd_kafka_error_destroy(error);
1945
+ rd_kafka_messages_destroy(batch);
1946
+ batch = NULL;
1947
+ }
1948
+
1949
+ TEST_ASSERT(state.callback_cnt >= 1,
1950
+ "Expected callback from implicit piggybacked acks (no "
1951
+ "explicit commit), got %d",
1952
+ state.callback_cnt);
1953
+ TEST_ASSERT(test_ack_cb_state_first_err(&state) ==
1954
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1955
+ "Callback errored: %s",
1956
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state)));
1957
+
1958
+ test_share_consumer_close(rkshare);
1959
+ test_share_destroy(rkshare);
1960
+ test_ack_cb_state_destroy(&state);
1961
+
1962
+ SUB_TEST_PASS();
1963
+ }
1964
+
1965
+
1966
+ /* ===================================================================
1967
+ * Lock-expiry surfaces an error in the ack commit callback.
1968
+ *
1969
+ * Explicit mode + short lock duration: poll a record, acknowledge it,
1970
+ * sleep past the lock expiry, then commit_async. The broker should
1971
+ * reject the stale ack and the error must be reported via the
1972
+ * acknowledgement commit callback.
1973
+ * =================================================================== */
1974
+ static void do_test_lock_expiry_callback_err(void) {
1975
+ const char *topic;
1976
+ const char *group = "commit-async-lock-expiry-cb";
1977
+ rd_kafka_share_t *rkshare;
1978
+ rd_kafka_error_t *error;
1979
+ rd_kafka_resp_err_t ack_err;
1980
+ rd_kafka_messages_t *batch = NULL;
1981
+ size_t j;
1982
+ size_t consumed = 0;
1983
+ int attempts = 0;
1984
+ test_ack_cb_state_t state = {0};
1985
+
1986
+ SUB_TEST();
1987
+
1988
+ topic = test_mk_topic_name("0173-ca-lock-cb", 1);
1989
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
1990
+ test_produce_msgs_simple(common_producer, topic, 0, 3);
1991
+
1992
+ rkshare =
1993
+ test_create_share_consumer_with_cb(group, "explicit", &state, NULL);
1994
+ test_share_set_auto_offset_reset(group, "earliest");
1995
+ set_group_lock_duration(group, "3000");
1996
+ subscribe_consumer(rkshare, &topic, 1);
1997
+
1998
+ /* Consume records (acquires lock) */
1999
+ while (consumed == 0 && attempts++ < 30) {
2000
+ error = rd_kafka_share_poll(rkshare, 2000, &batch);
2001
+ if (error) {
2002
+ rd_kafka_error_destroy(error);
2003
+ continue;
2004
+ }
2005
+ consumed = rd_kafka_messages_count(batch);
2006
+ }
2007
+ TEST_ASSERT(consumed > 0, "Expected to consume records, got 0");
2008
+
2009
+ /* Acknowledge all consumed records explicitly */
2010
+ for (j = 0; j < consumed; j++) {
2011
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
2012
+ ack_err = rd_kafka_share_acknowledge_type(
2013
+ rkshare, rkm, RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT);
2014
+ TEST_ASSERT(ack_err == RD_KAFKA_RESP_ERR_NO_ERROR,
2015
+ "ACCEPT failed: %s", rd_kafka_err2str(ack_err));
2016
+ }
2017
+
2018
+ /* Sleep past acquisition-lock expiry (3s + buffer) */
2019
+ TEST_SAY("Sleeping 4 s for lock expiry before commit_async...\n");
2020
+ rd_sleep(4);
2021
+
2022
+ /* commit_async on stale acks - broker should reject them */
2023
+ error = rd_kafka_share_commit_async(rkshare);
2024
+ TEST_ASSERT(!error, "commit_async returned error directly: %s",
2025
+ error ? rd_kafka_error_string(error) : "");
2026
+
2027
+ test_wait_for_cb_with_poll(&state, rkshare, 1, 15000);
2028
+
2029
+ TEST_SAY("Callback count=%d, total_offsets=%zu, last_err=%s\n",
2030
+ state.callback_cnt, state.total_offsets,
2031
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state)));
2032
+
2033
+ TEST_ASSERT(state.callback_cnt >= 1,
2034
+ "Expected callback to fire for stale ack, got %d",
2035
+ state.callback_cnt);
2036
+ /* Broker rejects acks against records whose acquisition lock has
2037
+ * expired with INVALID_RECORD_STATE. */
2038
+ TEST_ASSERT(test_ack_cb_state_first_err(&state) ==
2039
+ RD_KAFKA_RESP_ERR_INVALID_RECORD_STATE,
2040
+ "Expected INVALID_RECORD_STATE in callback after lock "
2041
+ "expiry, got %s",
2042
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state)));
2043
+
2044
+ rd_kafka_messages_destroy(batch);
2045
+ batch = NULL;
2046
+
2047
+ test_share_consumer_close(rkshare);
2048
+ test_share_destroy(rkshare);
2049
+ test_ack_cb_state_destroy(&state);
2050
+
2051
+ SUB_TEST_PASS();
2052
+ }
2053
+
2054
+
2055
+ typedef struct {
2056
+ test_ack_cb_state_t base;
2057
+ int saw_consumer_name;
2058
+ } k44_state_t;
2059
+
2060
+ static void k44_safe_api_cb(rd_kafka_share_t *rkshare,
2061
+ rd_kafka_share_partition_offsets_list_t *partitions,
2062
+ rd_kafka_resp_err_t err,
2063
+ void *opaque) {
2064
+ k44_state_t *st = opaque;
2065
+ rd_kafka_t *rk;
2066
+
2067
+ /* Reuse base counters via the shared helper */
2068
+ test_share_ack_cb(rkshare, partitions, err, &st->base);
2069
+
2070
+ /* Safely read consumer identity from within the callback */
2071
+ rk = test_share_consumer_get_rk(rkshare);
2072
+ if (rk && rd_kafka_name(rk))
2073
+ st->saw_consumer_name = 1;
2074
+ }
2075
+
2076
+ /* ===================================================================
2077
+ * Callback safely reads consumer state and the consumer keeps working
2078
+ * afterwards.
2079
+ *
2080
+ * Implicit mode: poll a batch, commit_async to drive piggyback acks,
2081
+ * wait for the ack callback. The callback calls
2082
+ * test_share_consumer_get_rk()+rd_kafka_name() to prove that
2083
+ * consumer-state APIs are safe to invoke from within the callback.
2084
+ * After the callback runs, a second round of consume_batch must still
2085
+ * return records, proving the consumer is not wedged.
2086
+ * =================================================================== */
2087
+ static void do_test_safe_api_from_callback(void) {
2088
+ const char *topic;
2089
+ const char *group = "commit-async-safe-api-cb";
2090
+ rd_kafka_share_t *rkshare;
2091
+ rd_kafka_error_t *error;
2092
+ rd_kafka_messages_t *batch = NULL;
2093
+ size_t rcvd, j;
2094
+ int consumed = 0;
2095
+ int second_round = 0;
2096
+ int attempts = 0;
2097
+ k44_state_t st = {0};
2098
+
2099
+ SUB_TEST();
2100
+
2101
+ topic = test_mk_topic_name("0173-ca-safe-api-cb", 1);
2102
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
2103
+ test_produce_msgs_simple(common_producer, topic, 0, 10);
2104
+
2105
+ rkshare = test_create_share_consumer_with_cb(
2106
+ group, "implicit", (test_ack_cb_state_t *)&st, k44_safe_api_cb);
2107
+ test_share_set_auto_offset_reset(group, "earliest");
2108
+ subscribe_consumer(rkshare, &topic, 1);
2109
+
2110
+ /* First-round consumption */
2111
+ while (consumed < 5 && attempts++ < 30) {
2112
+ error = rd_kafka_share_poll(rkshare, 2000, &batch);
2113
+ if (error) {
2114
+ rd_kafka_error_destroy(error);
2115
+ continue;
2116
+ }
2117
+ rcvd = rd_kafka_messages_count(batch);
2118
+ for (j = 0; j < rcvd; j++) {
2119
+ rd_kafka_message_t *rkm =
2120
+ rd_kafka_messages_get(batch, j);
2121
+ if (!rkm->err)
2122
+ consumed++;
2123
+ }
2124
+ rd_kafka_messages_destroy(batch);
2125
+ batch = NULL;
2126
+ }
2127
+
2128
+ /* Drive piggyback acks - this fires the callback */
2129
+ error = rd_kafka_share_commit_async(rkshare);
2130
+ TEST_ASSERT(!error, "commit_async failed");
2131
+ test_wait_for_cb_with_poll(&st.base, rkshare, 1, 10000);
2132
+
2133
+ TEST_ASSERT(st.base.callback_cnt >= 1, "Expected callback, got %d",
2134
+ st.base.callback_cnt);
2135
+ TEST_ASSERT(st.saw_consumer_name,
2136
+ "Callback should have read consumer name via "
2137
+ "test_share_consumer_get_rk + rd_kafka_name");
2138
+
2139
+ /* After callback has run, the consumer must still function. */
2140
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
2141
+ attempts = 0;
2142
+ while (second_round < 5 && attempts++ < 30) {
2143
+ error = rd_kafka_share_poll(rkshare, 2000, &batch);
2144
+ if (error) {
2145
+ rd_kafka_error_destroy(error);
2146
+ continue;
2147
+ }
2148
+ rcvd = rd_kafka_messages_count(batch);
2149
+ for (j = 0; j < rcvd; j++) {
2150
+ rd_kafka_message_t *rkm =
2151
+ rd_kafka_messages_get(batch, j);
2152
+ if (!rkm->err)
2153
+ second_round++;
2154
+ }
2155
+ rd_kafka_messages_destroy(batch);
2156
+ batch = NULL;
2157
+ }
2158
+ TEST_ASSERT(second_round > 0,
2159
+ "Consumer stopped working after callback invocation");
2160
+
2161
+ test_share_consumer_close(rkshare);
2162
+ test_share_destroy(rkshare);
2163
+ test_ack_cb_state_destroy(&st.base);
2164
+
2165
+ SUB_TEST_PASS();
2166
+ }
2167
+
2168
+
2169
+ typedef struct {
2170
+ test_ack_cb_state_t base;
2171
+ int side_effect_runs;
2172
+ } k45_state_t;
2173
+
2174
+ static void
2175
+ k45_side_effect_cb(rd_kafka_share_t *rkshare,
2176
+ rd_kafka_share_partition_offsets_list_t *partitions,
2177
+ rd_kafka_resp_err_t err,
2178
+ void *opaque) {
2179
+ k45_state_t *st = opaque;
2180
+
2181
+ test_share_ack_cb(rkshare, partitions, err, &st->base);
2182
+
2183
+ /* Heavy-ish side effect: simulate user application work. */
2184
+ st->side_effect_runs++;
2185
+ rd_sleep(0); /* yield - safe no-op time pass */
2186
+ }
2187
+
2188
+ /* ===================================================================
2189
+ * Application-side side effects in the ack callback do not crash or
2190
+ * hang the poll thread.
2191
+ *
2192
+ * Implicit mode: poll one batch, commit_async, and wait for the
2193
+ * callback. The callback bumps a side-effect counter and yields
2194
+ * via rd_sleep(0) to simulate user work. We assert the side effect
2195
+ * ran and that the consumer is still healthy on a follow-up
2196
+ * consume_batch / commit_async round.
2197
+ * =================================================================== */
2198
+ static void do_test_callback_side_effects_dont_break(void) {
2199
+ const char *topic;
2200
+ const char *group = "commit-async-side-effect-cb";
2201
+ rd_kafka_share_t *rkshare;
2202
+ rd_kafka_error_t *error;
2203
+ rd_kafka_messages_t *batch = NULL;
2204
+ size_t rcvd, j;
2205
+ int consumed = 0;
2206
+ int post_count = 0;
2207
+ int attempts = 0;
2208
+ k45_state_t st = {0};
2209
+
2210
+ SUB_TEST();
2211
+
2212
+ topic = test_mk_topic_name("0173-ca-side-effect-cb", 1);
2213
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
2214
+ test_produce_msgs_simple(common_producer, topic, 0, 10);
2215
+
2216
+ rkshare = test_create_share_consumer_with_cb(
2217
+ group, "implicit", (test_ack_cb_state_t *)&st, k45_side_effect_cb);
2218
+ test_share_set_auto_offset_reset(group, "earliest");
2219
+ subscribe_consumer(rkshare, &topic, 1);
2220
+
2221
+ /* Consume the produced records */
2222
+ while (consumed < 10 && attempts++ < 30) {
2223
+ error = rd_kafka_share_poll(rkshare, 2000, &batch);
2224
+ if (error) {
2225
+ rd_kafka_error_destroy(error);
2226
+ continue;
2227
+ }
2228
+ rcvd = rd_kafka_messages_count(batch);
2229
+ for (j = 0; j < rcvd; j++) {
2230
+ rd_kafka_message_t *rkm =
2231
+ rd_kafka_messages_get(batch, j);
2232
+ if (!rkm->err)
2233
+ consumed++;
2234
+ }
2235
+ rd_kafka_messages_destroy(batch);
2236
+ batch = NULL;
2237
+ }
2238
+ TEST_ASSERT(consumed > 0, "Expected to consume records, got 0");
2239
+
2240
+ /* Drive the side-effect callback at least once via commit_async +
2241
+ * wait_for_cb_with_poll. */
2242
+ error = rd_kafka_share_commit_async(rkshare);
2243
+ TEST_ASSERT(!error, "commit_async failed: %s",
2244
+ error ? rd_kafka_error_string(error) : "");
2245
+ test_wait_for_cb_with_poll(&st.base, rkshare, 1, 10000);
2246
+
2247
+ TEST_ASSERT(st.base.callback_cnt >= 1,
2248
+ "Expected callback to fire at least once, got %d",
2249
+ st.base.callback_cnt);
2250
+ TEST_ASSERT(st.side_effect_runs >= 1,
2251
+ "Side-effect did not run inside callback (%d runs vs "
2252
+ "%d callbacks)",
2253
+ st.side_effect_runs, st.base.callback_cnt);
2254
+ TEST_ASSERT(test_ack_cb_state_first_err(&st.base) ==
2255
+ RD_KAFKA_RESP_ERR_NO_ERROR,
2256
+ "Callback err: %s",
2257
+ rd_kafka_err2name(test_ack_cb_state_first_err(&st.base)));
2258
+ TEST_SAY("consumed=%d, callbacks=%d, side_effects=%d\n", consumed,
2259
+ st.base.callback_cnt, st.side_effect_runs);
2260
+
2261
+ /* Consumer must still be usable after the side-effect callback ran:
2262
+ * produce more records and verify poll still works. */
2263
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
2264
+ attempts = 0;
2265
+ while (post_count == 0 && attempts++ < 30) {
2266
+ error = rd_kafka_share_poll(rkshare, 2000, &batch);
2267
+ if (error) {
2268
+ rd_kafka_error_destroy(error);
2269
+ continue;
2270
+ }
2271
+ rcvd = rd_kafka_messages_count(batch);
2272
+ for (j = 0; j < rcvd; j++) {
2273
+ rd_kafka_message_t *rkm =
2274
+ rd_kafka_messages_get(batch, j);
2275
+ if (!rkm->err)
2276
+ post_count++;
2277
+ }
2278
+ rd_kafka_messages_destroy(batch);
2279
+ batch = NULL;
2280
+ }
2281
+ TEST_ASSERT(post_count > 0,
2282
+ "Consumer stopped working after side-effect callback");
2283
+ TEST_SAY("Post-callback poll returned %d msgs\n", post_count);
2284
+
2285
+ test_share_consumer_close(rkshare);
2286
+ test_share_destroy(rkshare);
2287
+ test_ack_cb_state_destroy(&st.base);
2288
+
2289
+ SUB_TEST_PASS();
2290
+ }
2291
+
2292
+ static void do_test_change_callback(void) {
2293
+ const char *topic;
2294
+ const char *group = "change-callback";
2295
+ rd_kafka_share_t *rkshare;
2296
+ rd_kafka_error_t *error;
2297
+ rd_kafka_messages_t *batch = NULL;
2298
+ size_t rcvd, j;
2299
+ int consumed = 0;
2300
+ int attempts = 0;
2301
+ int cb_a_after = 0;
2302
+ test_ack_cb_state_t state_a = {0};
2303
+ test_ack_cb_state_t state_b = {0};
2304
+
2305
+ SUB_TEST();
2306
+
2307
+ topic = test_mk_topic_name("0173-change-callback", 1);
2308
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
2309
+
2310
+ /* Phase 1: produce first batch of messages */
2311
+ test_produce_msgs_simple(common_producer, topic, 0, 20);
2312
+
2313
+ /* Create consumer with callback A registered */
2314
+ rkshare = test_create_share_consumer_with_cb(group, "explicit",
2315
+ &state_a, NULL);
2316
+ /* Set offset reset to earliest */
2317
+ test_share_set_auto_offset_reset(group, "earliest");
2318
+ subscribe_consumer(rkshare, &topic, 1);
2319
+
2320
+ /* Phase 1: consume the first batch and commit with callback A */
2321
+ while (consumed < 20 && attempts++ < 30) {
2322
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2323
+ if (error) {
2324
+ rd_kafka_error_destroy(error);
2325
+ continue;
2326
+ }
2327
+ rcvd = rd_kafka_messages_count(batch);
2328
+ for (j = 0; j < rcvd; j++) {
2329
+ rd_kafka_message_t *rkm =
2330
+ rd_kafka_messages_get(batch, j);
2331
+ if (!rkm->err) {
2332
+ consumed++;
2333
+ rd_kafka_share_acknowledge(rkshare, rkm);
2334
+ }
2335
+ }
2336
+ rd_kafka_messages_destroy(batch);
2337
+ batch = NULL;
2338
+ }
2339
+
2340
+ TEST_ASSERT(consumed > 0, "Expected to consume some messages");
2341
+
2342
+ error = rd_kafka_share_commit_async(rkshare);
2343
+ TEST_ASSERT(!error, "First commit_async failed: %s",
2344
+ error ? rd_kafka_error_string(error) : "");
2345
+
2346
+ /* Wait for callback A */
2347
+ test_wait_for_cb_with_poll(&state_a, rkshare, 1, 10000);
2348
+ TEST_ASSERT(state_a.callback_cnt == 1,
2349
+ "Expected callback A to be invoked once, got %d",
2350
+ state_a.callback_cnt);
2351
+ TEST_ASSERT(state_b.callback_cnt == 0,
2352
+ "Expected callback B NOT to be invoked yet, got %d",
2353
+ state_b.callback_cnt);
2354
+ TEST_SAY("Phase 1: callback A invoked %d times, total offsets=%zu\n",
2355
+ state_a.callback_cnt, state_a.total_offsets);
2356
+
2357
+ /* Phase 2: replace callback A with callback B (different opaque) */
2358
+ error = rd_kafka_share_set_acknowledgement_commit_cb(
2359
+ rkshare, test_share_ack_cb, &state_b);
2360
+ TEST_ASSERT(error == NULL,
2361
+ "Expected to change callback successfully, got %s",
2362
+ rd_kafka_error_string(error));
2363
+
2364
+ /* Remember current callback A count so we can verify it doesn't
2365
+ * increase after the swap. */
2366
+ cb_a_after = state_a.callback_cnt;
2367
+
2368
+ /* Produce a fresh batch so phase 2 has new messages to consume */
2369
+ test_produce_msgs_simple(common_producer, topic, 0, 20);
2370
+
2371
+ /* Consume the fresh batch */
2372
+ consumed = 0;
2373
+ attempts = 0;
2374
+ while (consumed < 20 && attempts++ < 30) {
2375
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2376
+ if (error) {
2377
+ rd_kafka_error_destroy(error);
2378
+ continue;
2379
+ }
2380
+ rcvd = rd_kafka_messages_count(batch);
2381
+ for (j = 0; j < rcvd; j++) {
2382
+ rd_kafka_message_t *rkm =
2383
+ rd_kafka_messages_get(batch, j);
2384
+ if (!rkm->err) {
2385
+ consumed++;
2386
+ rd_kafka_share_acknowledge(rkshare, rkm);
2387
+ }
2388
+ }
2389
+ rd_kafka_messages_destroy(batch);
2390
+ batch = NULL;
2391
+ }
2392
+
2393
+ TEST_ASSERT(consumed > 0,
2394
+ "Expected to consume some messages after callback swap");
2395
+
2396
+ error = rd_kafka_share_commit_async(rkshare);
2397
+ TEST_ASSERT(!error, "Second commit_async failed: %s",
2398
+ error ? rd_kafka_error_string(error) : "");
2399
+
2400
+ /* Wait for callback B to be invoked */
2401
+ test_wait_for_cb_with_poll(&state_b, rkshare, 1, 10000);
2402
+ TEST_ASSERT(state_b.callback_cnt == 1,
2403
+ "Expected callback B to be invoked once, got %d",
2404
+ state_b.callback_cnt);
2405
+
2406
+ /* Critical assertion: callback A's count must NOT have increased
2407
+ * since the swap - the new callback should be receiving the new
2408
+ * results, not the old one. */
2409
+ TEST_ASSERT(state_a.callback_cnt == cb_a_after,
2410
+ "Callback A was invoked AFTER swap (was %d, now %d) "
2411
+ "- new callback should have received the results",
2412
+ cb_a_after, state_a.callback_cnt);
2413
+
2414
+ TEST_SAY(
2415
+ "Phase 2: callback A unchanged at %d, callback B invoked "
2416
+ "%d times, total offsets=%zu\n",
2417
+ state_a.callback_cnt, state_b.callback_cnt, state_b.total_offsets);
2418
+
2419
+ /* Phase 3: unregister callback (NULL). After this, no callback
2420
+ * should be invoked even when new acks are committed. */
2421
+ int cb_a_before_phase3 = state_a.callback_cnt;
2422
+ int cb_b_before_phase3 = state_b.callback_cnt;
2423
+
2424
+ error =
2425
+ rd_kafka_share_set_acknowledgement_commit_cb(rkshare, NULL, NULL);
2426
+ TEST_ASSERT(error == NULL, "Failed to unregister callback: %s",
2427
+ rd_kafka_error_string(error));
2428
+
2429
+ /* Produce a fresh batch to trigger more acknowledgements */
2430
+ test_produce_msgs_simple(common_producer, topic, 0, 20);
2431
+
2432
+ /* Consume the fresh batch */
2433
+ consumed = 0;
2434
+ attempts = 0;
2435
+ while (consumed < 20 && attempts++ < 30) {
2436
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2437
+ if (error) {
2438
+ rd_kafka_error_destroy(error);
2439
+ continue;
2440
+ }
2441
+ rcvd = rd_kafka_messages_count(batch);
2442
+ for (j = 0; j < rcvd; j++) {
2443
+ rd_kafka_message_t *rkm =
2444
+ rd_kafka_messages_get(batch, j);
2445
+ if (!rkm->err) {
2446
+ consumed++;
2447
+ rd_kafka_share_acknowledge(rkshare, rkm);
2448
+ }
2449
+ }
2450
+ rd_kafka_messages_destroy(batch);
2451
+ batch = NULL;
2452
+ }
2453
+ TEST_ASSERT(consumed > 0,
2454
+ "Expected to consume some messages after unregister");
2455
+
2456
+ error = rd_kafka_share_commit_async(rkshare);
2457
+ TEST_ASSERT(!error, "Third commit_async failed: %s",
2458
+ error ? rd_kafka_error_string(error) : "");
2459
+
2460
+ /* Poll for a while to give any pending callbacks a chance to fire.
2461
+ * We can't use test_wait_for_cb_with_poll because we EXPECT no
2462
+ * callback - so we poll manually with a fixed budget. */
2463
+ int poll_iters = 20;
2464
+ while (poll_iters-- > 0) {
2465
+ error = rd_kafka_share_poll(rkshare, 200, &batch);
2466
+ if (error)
2467
+ rd_kafka_error_destroy(error);
2468
+ rcvd = rd_kafka_messages_count(batch);
2469
+ for (j = 0; j < rcvd; j++) {
2470
+ rd_kafka_message_t *rkm =
2471
+ rd_kafka_messages_get(batch, j);
2472
+ if (!rkm->err) {
2473
+ consumed++;
2474
+ rd_kafka_share_acknowledge(rkshare, rkm);
2475
+ }
2476
+ }
2477
+ rd_kafka_messages_destroy(batch);
2478
+ batch = NULL;
2479
+ }
2480
+
2481
+ /* Critical assertion: neither callback should have been invoked
2482
+ * after the unregister. */
2483
+ TEST_ASSERT(state_a.callback_cnt == cb_a_before_phase3,
2484
+ "Callback A was invoked after unregister (was %d, now %d)",
2485
+ cb_a_before_phase3, state_a.callback_cnt);
2486
+ TEST_ASSERT(state_b.callback_cnt == cb_b_before_phase3,
2487
+ "Callback B was invoked after unregister (was %d, now %d)",
2488
+ cb_b_before_phase3, state_b.callback_cnt);
2489
+
2490
+ TEST_SAY(
2491
+ "Phase 3: no callbacks invoked after unregister "
2492
+ "(A=%d, B=%d unchanged)\n",
2493
+ state_a.callback_cnt, state_b.callback_cnt);
2494
+
2495
+ rd_kafka_share_consumer_close(rkshare);
2496
+ rd_kafka_share_destroy(rkshare);
2497
+ test_ack_cb_state_destroy(&state_a);
2498
+ test_ack_cb_state_destroy(&state_b);
2499
+
2500
+ SUB_TEST_PASS();
2501
+ }
2502
+
2503
+
2504
+ /* ===================================================================
2505
+ * Test: reentrancy protection - share consumer APIs cannot be called
2506
+ * from within the acknowledgement callback.
2507
+ *
2508
+ * Verifies that calling any share consumer API from inside the
2509
+ * registered acknowledgement callback returns RD_KAFKA_RESP_ERR__STATE
2510
+ * (for rd_kafka_resp_err_t APIs) or a STATE-coded error object
2511
+ * (for rd_kafka_error_t APIs).
2512
+ * =================================================================== */
2513
+ typedef struct reentrancy_check_state_s {
2514
+ test_ack_cb_state_t base; /**< Must be first - we cast to base */
2515
+ int rejections; /**< Count of correctly rejected calls */
2516
+ int failures; /**< Count of calls that did NOT reject */
2517
+ rd_kafka_share_t *rkshare;
2518
+ } reentrancy_check_state_t;
2519
+
2520
+ static void
2521
+ reentrancy_check_cb(rd_kafka_share_t *rkshare,
2522
+ rd_kafka_share_partition_offsets_list_t *partitions,
2523
+ rd_kafka_resp_err_t err,
2524
+ void *opaque) {
2525
+ reentrancy_check_state_t *st = (reentrancy_check_state_t *)opaque;
2526
+ rd_kafka_error_t *error_obj;
2527
+ rd_kafka_resp_err_t resp_err;
2528
+ rd_kafka_messages_t *batch = NULL;
2529
+ rd_kafka_topic_partition_list_t *subs;
2530
+
2531
+ /* Update base state for callback tracking */
2532
+ test_ack_cb_state_push_err(&st->base, err);
2533
+ if (partitions) {
2534
+ const rd_kafka_share_partition_offsets_t *p =
2535
+ rd_kafka_share_partition_offsets_list_get(partitions, 0);
2536
+ if (p)
2537
+ st->base.total_offsets +=
2538
+ rd_kafka_share_partition_offsets_offsets_cnt(p);
2539
+ }
2540
+
2541
+ /* Try rd_kafka_share_poll - should fail with _STATE */
2542
+ error_obj = rd_kafka_share_poll(rkshare, 100, &batch);
2543
+ if (error_obj &&
2544
+ rd_kafka_error_code(error_obj) == RD_KAFKA_RESP_ERR__STATE) {
2545
+ st->rejections++;
2546
+ rd_kafka_error_destroy(error_obj);
2547
+ } else {
2548
+ st->failures++;
2549
+ if (error_obj)
2550
+ rd_kafka_error_destroy(error_obj);
2551
+ }
2552
+ rd_kafka_messages_destroy(batch);
2553
+
2554
+ /* Try rd_kafka_share_commit_async - should fail with _STATE */
2555
+ error_obj = rd_kafka_share_commit_async(rkshare);
2556
+ if (error_obj &&
2557
+ rd_kafka_error_code(error_obj) == RD_KAFKA_RESP_ERR__STATE) {
2558
+ st->rejections++;
2559
+ rd_kafka_error_destroy(error_obj);
2560
+ } else {
2561
+ st->failures++;
2562
+ if (error_obj)
2563
+ rd_kafka_error_destroy(error_obj);
2564
+ }
2565
+
2566
+ /* Try rd_kafka_share_consumer_close - should fail with _STATE */
2567
+ error_obj = rd_kafka_share_consumer_close(rkshare);
2568
+ if (error_obj &&
2569
+ rd_kafka_error_code(error_obj) == RD_KAFKA_RESP_ERR__STATE) {
2570
+ st->rejections++;
2571
+ rd_kafka_error_destroy(error_obj);
2572
+ } else {
2573
+ st->failures++;
2574
+ if (error_obj)
2575
+ rd_kafka_error_destroy(error_obj);
2576
+ }
2577
+
2578
+ /* Try rd_kafka_share_subscribe - should fail with _STATE */
2579
+ subs = rd_kafka_topic_partition_list_new(1);
2580
+ rd_kafka_topic_partition_list_add(subs, "dummy", RD_KAFKA_PARTITION_UA);
2581
+ resp_err = rd_kafka_share_subscribe(rkshare, subs);
2582
+ if (resp_err == RD_KAFKA_RESP_ERR__STATE)
2583
+ st->rejections++;
2584
+ else
2585
+ st->failures++;
2586
+ rd_kafka_topic_partition_list_destroy(subs);
2587
+
2588
+ /* Try rd_kafka_share_unsubscribe - should fail with _STATE */
2589
+ resp_err = rd_kafka_share_unsubscribe(rkshare);
2590
+ if (resp_err == RD_KAFKA_RESP_ERR__STATE)
2591
+ st->rejections++;
2592
+ else
2593
+ st->failures++;
2594
+
2595
+ /* Try rd_kafka_share_set_acknowledgement_commit_cb - should fail with
2596
+ * _STATE because you can't change the callback from within itself */
2597
+ error_obj =
2598
+ rd_kafka_share_set_acknowledgement_commit_cb(rkshare, NULL, NULL);
2599
+ if (error_obj &&
2600
+ rd_kafka_error_code(error_obj) == RD_KAFKA_RESP_ERR__STATE)
2601
+ st->rejections++;
2602
+ else
2603
+ st->failures++;
2604
+ RD_IF_FREE(error_obj, rd_kafka_error_destroy);
2605
+ }
2606
+
2607
+ static void do_test_reentrancy_protection(void) {
2608
+ const char *topic;
2609
+ const char *group = "reentrancy-protection";
2610
+ rd_kafka_share_t *rkshare;
2611
+ rd_kafka_error_t *error;
2612
+ rd_kafka_messages_t *batch = NULL;
2613
+ size_t rcvd, j;
2614
+ int consumed = 0;
2615
+ int attempts = 0;
2616
+ reentrancy_check_state_t state = {0};
2617
+
2618
+ SUB_TEST();
2619
+
2620
+ topic = test_mk_topic_name("0173-reentrancy", 1);
2621
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
2622
+ test_produce_msgs_simple(common_producer, topic, 0, 50);
2623
+
2624
+ /* Create consumer without callback - we'll set it at runtime */
2625
+ rkshare = test_create_share_consumer(group, "explicit");
2626
+ state.rkshare = rkshare;
2627
+
2628
+ /* Register the reentrancy-checking callback at runtime */
2629
+ error = rd_kafka_share_set_acknowledgement_commit_cb(
2630
+ rkshare, reentrancy_check_cb, &state);
2631
+ TEST_ASSERT(error == NULL, "Failed to set callback: %s",
2632
+ rd_kafka_error_string(error));
2633
+
2634
+ /* Set offset reset to earliest */
2635
+ test_share_set_auto_offset_reset(group, "earliest");
2636
+ subscribe_consumer(rkshare, &topic, 1);
2637
+
2638
+ /* Consume messages to trigger acknowledgement and callback invocation
2639
+ */
2640
+ while (consumed < 50 && attempts++ < 30) {
2641
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2642
+ if (error) {
2643
+ rd_kafka_error_destroy(error);
2644
+ continue;
2645
+ }
2646
+ rcvd = rd_kafka_messages_count(batch);
2647
+ for (j = 0; j < rcvd; j++) {
2648
+ rd_kafka_message_t *rkm =
2649
+ rd_kafka_messages_get(batch, j);
2650
+ if (!rkm->err) {
2651
+ consumed++;
2652
+ rd_kafka_share_acknowledge(rkshare, rkm);
2653
+ }
2654
+ }
2655
+ rd_kafka_messages_destroy(batch);
2656
+ batch = NULL;
2657
+ }
2658
+ TEST_ASSERT(consumed > 0, "Expected to consume some messages");
2659
+
2660
+ /* commit_async triggers the callback */
2661
+ error = rd_kafka_share_commit_async(rkshare);
2662
+ TEST_ASSERT(!error, "commit_async failed: %s",
2663
+ error ? rd_kafka_error_string(error) : "");
2664
+
2665
+ /* Wait for callback to be invoked at least once */
2666
+ test_wait_for_cb_with_poll(&state.base, rkshare, 1, 10000);
2667
+ TEST_ASSERT(state.base.callback_cnt == 1,
2668
+ "Expected callback to be invoked, got %d",
2669
+ state.base.callback_cnt);
2670
+
2671
+ TEST_SAY("Callback invoked %d times, rejections=%d, failures=%d\n",
2672
+ state.base.callback_cnt, state.rejections, state.failures);
2673
+
2674
+ /* Every callback invocation tries 6 share consumer APIs, all of
2675
+ * which must reject the call from within the callback. */
2676
+ TEST_ASSERT(state.failures == 0,
2677
+ "Expected 0 failures (all APIs should reject), got %d",
2678
+ state.failures);
2679
+ TEST_ASSERT(state.rejections == 6, "Expected 6 rejections, got %d",
2680
+ state.rejections);
2681
+
2682
+ rd_kafka_share_consumer_close(rkshare);
2683
+ rd_kafka_share_destroy(rkshare);
2684
+ test_ack_cb_state_destroy(&state.base);
2685
+
2686
+ SUB_TEST_PASS();
2687
+ }
2688
+
2689
+
2690
+
2691
+ int main_0173_share_consumer_commit_async(int argc, char **argv) {
2692
+ test_timeout_set(120);
2693
+ common_producer = test_create_producer();
2694
+ common_admin = test_create_producer();
2695
+
2696
+ do_test_implicit_second_consumer();
2697
+ do_test_explicit_second_consumer();
2698
+ do_test_mixed_acks_second_consumer();
2699
+ do_test_multi_topic_partition();
2700
+ do_test_produce_consume_loop();
2701
+ do_test_multi_round_mixed_second_consumer();
2702
+ do_test_no_pending_acks();
2703
+ do_test_multiple_commit_async_calls();
2704
+ do_test_commit_between_produces();
2705
+ do_test_all_release_second_consumer();
2706
+ do_test_all_reject_second_consumer();
2707
+ do_test_per_record_commit_async();
2708
+ do_test_lock_timeout_redelivery();
2709
+ /* Callback test */
2710
+ do_test_commit_async_callback();
2711
+
2712
+ /* Partial-batch / callback edge cases */
2713
+ do_test_partial_batch_commit_async();
2714
+ do_test_implicit_callback_no_explicit_commit();
2715
+ do_test_lock_expiry_callback_err();
2716
+ do_test_safe_api_from_callback();
2717
+ do_test_callback_side_effects_dont_break();
2718
+
2719
+ /* Callback management and reentrancy */
2720
+ do_test_change_callback();
2721
+ do_test_reentrancy_protection();
2722
+
2723
+ rd_kafka_destroy(common_admin);
2724
+ rd_kafka_destroy(common_producer);
2725
+
2726
+ return 0;
2727
+ }
2728
+
2729
+ int main_0173_share_consumer_commit_async_local(int argc, char **argv) {
2730
+ TEST_SKIP_MOCK_CLUSTER(0);
2731
+
2732
+ do_test_mock_inflight_caching();
2733
+
2734
+ return 0;
2735
+ }