@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,2986 @@
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_sync() API tests.
35
+ *
36
+ * Tests the commit_sync API in both implicit and explicit ack modes.
37
+ * Verifies that commit_sync synchronously commits acknowledged records
38
+ * and returns per-partition results.
39
+ */
40
+
41
+ #define CONSUME_ARRAY 10001
42
+
43
+ /** Common producer reused across all non-mock subtests. */
44
+ static rd_kafka_t *common_producer;
45
+
46
+ /** Common admin client reused across all non-mock subtests. */
47
+ static rd_kafka_t *common_admin;
48
+
49
+
50
+ /**
51
+ * @brief Create share consumer with specified ack mode.
52
+ * @param ack_mode "implicit" or "explicit"
53
+ */
54
+ static rd_kafka_share_t *create_share_consumer(const char *group_id,
55
+ const char *ack_mode) {
56
+ rd_kafka_share_t *rkshare;
57
+ rd_kafka_conf_t *conf;
58
+ char errstr[512];
59
+
60
+ test_conf_init(&conf, NULL, 0);
61
+
62
+ rd_kafka_conf_set(conf, "group.id", group_id, errstr, sizeof(errstr));
63
+ rd_kafka_conf_set(conf, "share.acknowledgement.mode", ack_mode, errstr,
64
+ sizeof(errstr));
65
+
66
+ rkshare = rd_kafka_share_consumer_new(conf, errstr, sizeof(errstr));
67
+ TEST_ASSERT(rkshare, "Failed to create share consumer: %s", errstr);
68
+
69
+ return rkshare;
70
+ }
71
+
72
+
73
+ /**
74
+ * @brief Set share.record.lock.duration.ms for a share group.
75
+ */
76
+ static void set_group_lock_duration(const char *group_name,
77
+ const char *duration_ms) {
78
+ const char *cfg[] = {"share.record.lock.duration.ms", "SET",
79
+ duration_ms};
80
+
81
+ test_IncrementalAlterConfigs_simple(
82
+ common_admin, RD_KAFKA_RESOURCE_GROUP, group_name, cfg, 1);
83
+ }
84
+
85
+
86
+ /**
87
+ * @brief Subscribe a share consumer to topics.
88
+ */
89
+ static void subscribe_consumer(rd_kafka_share_t *rkshare,
90
+ const char **topics,
91
+ int topic_cnt) {
92
+ rd_kafka_topic_partition_list_t *subs;
93
+ rd_kafka_resp_err_t err;
94
+ int i;
95
+
96
+ subs = rd_kafka_topic_partition_list_new(topic_cnt);
97
+ for (i = 0; i < topic_cnt; i++)
98
+ rd_kafka_topic_partition_list_add(subs, topics[i],
99
+ RD_KAFKA_PARTITION_UA);
100
+
101
+ err = rd_kafka_share_subscribe(rkshare, subs);
102
+ TEST_ASSERT(!err, "Subscribe failed: %s", rd_kafka_err2str(err));
103
+
104
+ rd_kafka_topic_partition_list_destroy(subs);
105
+ }
106
+
107
+
108
+ /* ===================================================================
109
+ * Test 1: Basic implicit ack mode commit_sync.
110
+ *
111
+ * Implicit mode auto-ACCEPTs all records from previous poll.
112
+ * Consumer consumes records, calls commit_sync, verifies
113
+ * per-partition results show NO_ERROR.
114
+ * =================================================================== */
115
+ static void do_test_basic_implicit_commit_sync(void) {
116
+ const char *topic;
117
+ const char *group = "commit-sync-implicit-basic";
118
+ rd_kafka_share_t *rkshare;
119
+ rd_kafka_error_t *error;
120
+ rd_kafka_topic_partition_list_t *partitions = NULL;
121
+ rd_kafka_messages_t *batch = NULL;
122
+ size_t rcvd;
123
+ size_t j;
124
+ int consumed = 0;
125
+ int attempts = 0;
126
+
127
+ SUB_TEST();
128
+
129
+ topic = test_mk_topic_name("0176-cs-impl-basic", 1);
130
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
131
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
132
+
133
+ rkshare = create_share_consumer(group, "implicit");
134
+ test_share_set_auto_offset_reset(group, "earliest");
135
+ set_group_lock_duration(group, "3000");
136
+ subscribe_consumer(rkshare, &topic, 1);
137
+
138
+ /* Consume records */
139
+ while (consumed == 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
+ consumed++;
152
+ }
153
+ rd_kafka_messages_destroy(batch);
154
+ batch = NULL;
155
+ }
156
+
157
+ TEST_SAY("Consumed %d messages\n", consumed);
158
+ TEST_ASSERT(consumed == 5, "Expected 5 messages, got %d", consumed);
159
+
160
+ /* commit_sync should flush implicit acks */
161
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
162
+ TEST_ASSERT(!error, "commit_sync failed: %s",
163
+ error ? rd_kafka_error_string(error) : "");
164
+ TEST_ASSERT(partitions != NULL,
165
+ "Expected per-partition results, got NULL");
166
+
167
+ TEST_SAY("commit_sync returned %d partition(s)\n", partitions->cnt);
168
+ TEST_ASSERT(partitions->cnt == 1,
169
+ "Expected exactly 1 partition result, got %d",
170
+ partitions->cnt);
171
+ TEST_SAY(" %s [%" PRId32 "]: %s\n", partitions->elems[0].topic,
172
+ partitions->elems[0].partition,
173
+ rd_kafka_err2str(partitions->elems[0].err));
174
+ TEST_ASSERT(!strcmp(partitions->elems[0].topic, topic),
175
+ "Expected topic %s, got %s", topic,
176
+ partitions->elems[0].topic);
177
+ TEST_ASSERT(partitions->elems[0].partition == 0,
178
+ "Expected partition 0, got %" PRId32,
179
+ partitions->elems[0].partition);
180
+ TEST_ASSERT(partitions->elems[0].err == RD_KAFKA_RESP_ERR_NO_ERROR,
181
+ "Expected NO_ERROR, got %s",
182
+ rd_kafka_err2str(partitions->elems[0].err));
183
+
184
+ rd_kafka_topic_partition_list_destroy(partitions);
185
+
186
+ /* Wait for acquisition lock to expire (3s + 1s buffer) */
187
+ rd_sleep(4);
188
+
189
+ /* Produce 5 verification records and consume with the same
190
+ * consumer — should get only these 5 (dc == 1), proving
191
+ * the first batch was committed and not redelivered. */
192
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
193
+
194
+ consumed = 0;
195
+ attempts = 0;
196
+ while (consumed < 5 && attempts++ < 30) {
197
+ error = rd_kafka_share_poll(rkshare, 5000, &batch);
198
+ if (error) {
199
+ rd_kafka_error_destroy(error);
200
+ continue;
201
+ }
202
+ rcvd = rd_kafka_messages_count(batch);
203
+
204
+ for (j = 0; j < rcvd; j++) {
205
+ rd_kafka_message_t *rkm =
206
+ rd_kafka_messages_get(batch, j);
207
+ if (!rkm->err) {
208
+ TEST_ASSERT(
209
+ rd_kafka_message_delivery_count(rkm) == 1,
210
+ "Got redelivered record at offset %" PRId64
211
+ " (delivery_count=%d)",
212
+ rkm->offset,
213
+ rd_kafka_message_delivery_count(rkm));
214
+ consumed++;
215
+ }
216
+ }
217
+ rd_kafka_messages_destroy(batch);
218
+ batch = NULL;
219
+ }
220
+
221
+ TEST_SAY("Verification: got %d messages (expected 5)\n", consumed);
222
+ TEST_ASSERT(consumed == 5, "Expected 5 verification records, got %d",
223
+ consumed);
224
+
225
+ test_share_consumer_close(rkshare);
226
+ test_share_destroy(rkshare);
227
+
228
+ SUB_TEST_PASS();
229
+ }
230
+
231
+
232
+ /* ===================================================================
233
+ * Test 2: Basic explicit ack mode commit_sync.
234
+ *
235
+ * Explicit mode requires the app to ACCEPT each record via
236
+ * rd_kafka_share_acknowledge(). Then commit_sync flushes acks
237
+ * and returns per-partition results.
238
+ * =================================================================== */
239
+ static void do_test_basic_explicit_commit_sync(void) {
240
+ const char *topic;
241
+ const char *group = "commit-sync-explicit-basic";
242
+ rd_kafka_share_t *rkshare;
243
+ rd_kafka_error_t *error;
244
+ rd_kafka_topic_partition_list_t *partitions = NULL;
245
+ rd_kafka_messages_t *batch = NULL;
246
+ size_t rcvd;
247
+ size_t j;
248
+ int consumed = 0;
249
+ int attempts = 0;
250
+
251
+ SUB_TEST();
252
+
253
+ topic = test_mk_topic_name("0176-cs-expl-basic", 1);
254
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
255
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
256
+
257
+ rkshare = create_share_consumer(group, "explicit");
258
+ test_share_set_auto_offset_reset(group, "earliest");
259
+ set_group_lock_duration(group, "3000");
260
+ subscribe_consumer(rkshare, &topic, 1);
261
+
262
+ /* Consume records and explicitly ACCEPT each */
263
+ while (consumed == 0 && attempts++ < 30) {
264
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
265
+ if (error) {
266
+ rd_kafka_error_destroy(error);
267
+ continue;
268
+ }
269
+ rcvd = rd_kafka_messages_count(batch);
270
+
271
+ for (j = 0; j < rcvd; j++) {
272
+ rd_kafka_message_t *rkm =
273
+ rd_kafka_messages_get(batch, j);
274
+ if (!rkm->err) {
275
+ rd_kafka_share_acknowledge(rkshare, rkm);
276
+ consumed++;
277
+ }
278
+ }
279
+ rd_kafka_messages_destroy(batch);
280
+ batch = NULL;
281
+ }
282
+
283
+ TEST_SAY("Consumed and acknowledged %d messages\n", consumed);
284
+ TEST_ASSERT(consumed == 5, "Expected 5 messages, got %d", consumed);
285
+
286
+ /* commit_sync should flush explicit acks */
287
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
288
+ TEST_ASSERT(!error, "commit_sync failed: %s",
289
+ error ? rd_kafka_error_string(error) : "");
290
+ TEST_ASSERT(partitions != NULL,
291
+ "Expected per-partition results, got NULL");
292
+
293
+ TEST_SAY("commit_sync returned %d partition(s)\n", partitions->cnt);
294
+ TEST_ASSERT(partitions->cnt == 1,
295
+ "Expected exactly 1 partition result, got %d",
296
+ partitions->cnt);
297
+ TEST_SAY(" %s [%" PRId32 "]: %s\n", partitions->elems[0].topic,
298
+ partitions->elems[0].partition,
299
+ rd_kafka_err2str(partitions->elems[0].err));
300
+ TEST_ASSERT(!strcmp(partitions->elems[0].topic, topic),
301
+ "Expected topic %s, got %s", topic,
302
+ partitions->elems[0].topic);
303
+ TEST_ASSERT(partitions->elems[0].partition == 0,
304
+ "Expected partition 0, got %" PRId32,
305
+ partitions->elems[0].partition);
306
+ TEST_ASSERT(partitions->elems[0].err == RD_KAFKA_RESP_ERR_NO_ERROR,
307
+ "Expected NO_ERROR, got %s",
308
+ rd_kafka_err2str(partitions->elems[0].err));
309
+
310
+ rd_kafka_topic_partition_list_destroy(partitions);
311
+
312
+ /* Wait for acquisition lock to expire (3s + 1s buffer) */
313
+ rd_sleep(4);
314
+
315
+ /* Produce 5 verification records and consume with the same
316
+ * consumer — should get only these 5 (dc == 1), proving
317
+ * the first batch was committed and not redelivered. */
318
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
319
+
320
+ consumed = 0;
321
+ attempts = 0;
322
+ while (consumed < 5 && attempts++ < 30) {
323
+ error = rd_kafka_share_poll(rkshare, 5000, &batch);
324
+ if (error) {
325
+ rd_kafka_error_destroy(error);
326
+ continue;
327
+ }
328
+ rcvd = rd_kafka_messages_count(batch);
329
+
330
+ for (j = 0; j < rcvd; j++) {
331
+ rd_kafka_message_t *rkm =
332
+ rd_kafka_messages_get(batch, j);
333
+ if (!rkm->err) {
334
+ TEST_ASSERT(
335
+ rd_kafka_message_delivery_count(rkm) == 1,
336
+ "Got redelivered record at offset %" PRId64
337
+ " (delivery_count=%d)",
338
+ rkm->offset,
339
+ rd_kafka_message_delivery_count(rkm));
340
+ consumed++;
341
+ }
342
+ }
343
+ rd_kafka_messages_destroy(batch);
344
+ batch = NULL;
345
+ }
346
+
347
+ TEST_SAY("Verification: got %d messages (expected 5)\n", consumed);
348
+ TEST_ASSERT(consumed == 5, "Expected 5 verification records, got %d",
349
+ consumed);
350
+
351
+ test_share_consumer_close(rkshare);
352
+ test_share_destroy(rkshare);
353
+
354
+ SUB_TEST_PASS();
355
+ }
356
+
357
+
358
+ /* ===================================================================
359
+ * Test 3: No pending acks — commit_sync with nothing to commit.
360
+ *
361
+ * Subscribe but do not consume any records. commit_sync should
362
+ * return NULL error and NULL partitions.
363
+ * =================================================================== */
364
+ static void do_test_no_pending_acks(void) {
365
+ const char *topic;
366
+ const char *group = "commit-sync-no-pending";
367
+ rd_kafka_share_t *rkshare;
368
+ rd_kafka_error_t *error;
369
+ rd_kafka_topic_partition_list_t *partitions = NULL;
370
+
371
+ SUB_TEST();
372
+
373
+ topic = test_mk_topic_name("0176-cs-no-pending", 1);
374
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
375
+
376
+ rkshare = create_share_consumer(group, "explicit");
377
+ test_share_set_auto_offset_reset(group, "earliest");
378
+ subscribe_consumer(rkshare, &topic, 1);
379
+
380
+ /* commit_sync with no consumed records */
381
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
382
+ TEST_ASSERT(!error, "commit_sync failed: %s",
383
+ error ? rd_kafka_error_string(error) : "");
384
+ TEST_ASSERT(partitions == NULL,
385
+ "Expected NULL partitions when no acks pending, "
386
+ "got %d partition(s)",
387
+ partitions ? partitions->cnt : -1);
388
+
389
+ TEST_SAY(
390
+ "commit_sync with no pending acks returned NULL as expected\n");
391
+
392
+ test_share_consumer_close(rkshare);
393
+ test_share_destroy(rkshare);
394
+
395
+ SUB_TEST_PASS();
396
+ }
397
+
398
+
399
+ /* ===================================================================
400
+ * Test 4: commit_sync prevents redelivery.
401
+ *
402
+ * Consumer A consumes and commit_sync's all records, then closes.
403
+ * Consumer B (same group) should see 0 records since they were
404
+ * all committed by A.
405
+ * =================================================================== */
406
+ static void do_test_commit_sync_prevents_redelivery(void) {
407
+ const char *topic;
408
+ const char *group = "commit-sync-no-redeliver";
409
+ rd_kafka_share_t *rkshare;
410
+ rd_kafka_error_t *error;
411
+ rd_kafka_topic_partition_list_t *partitions = NULL;
412
+ rd_kafka_messages_t *batch = NULL;
413
+ size_t rcvd;
414
+ size_t j;
415
+ int consumed = 0;
416
+ int attempts = 0;
417
+
418
+ SUB_TEST();
419
+
420
+ topic = test_mk_topic_name("0176-cs-no-redeliver", 1);
421
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
422
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
423
+
424
+ /* Consumer A: consume all and commit_sync */
425
+ rkshare = create_share_consumer(group, "implicit");
426
+ test_share_set_auto_offset_reset(group, "earliest");
427
+ subscribe_consumer(rkshare, &topic, 1);
428
+
429
+ while (consumed == 0 && attempts++ < 30) {
430
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
431
+ if (error) {
432
+ rd_kafka_error_destroy(error);
433
+ continue;
434
+ }
435
+ rcvd = rd_kafka_messages_count(batch);
436
+
437
+ for (j = 0; j < rcvd; j++) {
438
+ rd_kafka_message_t *rkm =
439
+ rd_kafka_messages_get(batch, j);
440
+ if (!rkm->err)
441
+ consumed++;
442
+ }
443
+ rd_kafka_messages_destroy(batch);
444
+ batch = NULL;
445
+ }
446
+
447
+ TEST_SAY("Consumer A consumed %d messages\n", consumed);
448
+ TEST_ASSERT(consumed == 5, "Expected 5, got %d", consumed);
449
+
450
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
451
+ TEST_ASSERT(!error, "commit_sync failed: %s",
452
+ error ? rd_kafka_error_string(error) : "");
453
+ RD_IF_FREE(partitions, rd_kafka_topic_partition_list_destroy);
454
+
455
+ test_share_consumer_close(rkshare);
456
+ test_share_destroy(rkshare);
457
+
458
+ /* Produce 5 verification records */
459
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
460
+
461
+ /* Consumer B: should only get the 5 verification records.
462
+ * Close tears down the connection and broker releases
463
+ * records immediately — no lock wait needed. */
464
+ rkshare = create_share_consumer(group, "implicit");
465
+ subscribe_consumer(rkshare, &topic, 1);
466
+
467
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
468
+ rcvd = rd_kafka_messages_count(batch);
469
+ TEST_SAY("Consumer B share_poll returned: rcvd=%zu, error=%s\n", rcvd,
470
+ error ? rd_kafka_error_string(error) : "none");
471
+ if (error)
472
+ rd_kafka_error_destroy(error);
473
+
474
+ consumed = 0;
475
+ for (j = 0; j < rcvd; j++) {
476
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
477
+ if (!rkm->err) {
478
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
479
+ "Consumer B got redelivered record at "
480
+ "offset %" PRId64 " (delivery_count=%d)",
481
+ rkm->offset,
482
+ rd_kafka_message_delivery_count(rkm));
483
+ consumed++;
484
+ }
485
+ }
486
+ rd_kafka_messages_destroy(batch);
487
+ batch = NULL;
488
+
489
+ TEST_SAY("Consumer B got %d messages (expected 5)\n", consumed);
490
+ TEST_ASSERT(consumed == 5, "Expected 5 verification records, got %d",
491
+ consumed);
492
+
493
+ test_share_consumer_close(rkshare);
494
+ test_share_destroy(rkshare);
495
+
496
+ SUB_TEST_PASS();
497
+ }
498
+
499
+
500
+ /* ===================================================================
501
+ * Test 5: Mixed ack types — ACCEPT, RELEASE, REJECT.
502
+ *
503
+ * Consumer A: ACCEPT first 5, RELEASE next 3, REJECT last 2,
504
+ * then commit_sync. Consumer B should only receive the 3
505
+ * RELEASE'd records with delivery_count >= 2.
506
+ * =================================================================== */
507
+ static void do_test_mixed_ack_types(void) {
508
+ const char *topic;
509
+ const char *group = "commit-sync-mixed-acks";
510
+ rd_kafka_share_t *rkshare;
511
+ rd_kafka_error_t *error;
512
+ rd_kafka_topic_partition_list_t *partitions = NULL;
513
+ rd_kafka_messages_t *batch = NULL;
514
+ size_t rcvd;
515
+ size_t j;
516
+ int consumed = 0;
517
+ int attempts = 0;
518
+ int64_t released_offsets[3];
519
+ int released_cnt = 0;
520
+
521
+ SUB_TEST();
522
+
523
+ topic = test_mk_topic_name("0176-cs-mixed-acks", 1);
524
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
525
+ test_produce_msgs_simple(common_producer, topic, 0, 10);
526
+
527
+ /* Consumer A: consume all 10 in a single batch, apply mixed ack
528
+ * types */
529
+ rkshare = create_share_consumer(group, "explicit");
530
+ test_share_set_auto_offset_reset(group, "earliest");
531
+ subscribe_consumer(rkshare, &topic, 1);
532
+
533
+ while (consumed == 0 && attempts++ < 30) {
534
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
535
+ if (error) {
536
+ rd_kafka_error_destroy(error);
537
+ continue;
538
+ }
539
+ rcvd = rd_kafka_messages_count(batch);
540
+
541
+ /* Skip partial batches — wait for all 10 in one call */
542
+ if (rcvd < 10) {
543
+ rd_kafka_messages_destroy(batch);
544
+ batch = NULL;
545
+ continue;
546
+ }
547
+
548
+ for (j = 0; j < rcvd; j++) {
549
+ rd_kafka_resp_err_t err;
550
+ rd_kafka_message_t *rkm =
551
+ rd_kafka_messages_get(batch, j);
552
+
553
+ if (rkm->err)
554
+ continue;
555
+
556
+ if (consumed < 5) {
557
+ /* ACCEPT first 5 */
558
+ err = rd_kafka_share_acknowledge_type(
559
+ rkshare, rkm,
560
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT);
561
+ } else if (consumed < 8) {
562
+ /* RELEASE next 3 */
563
+ released_offsets[released_cnt++] = rkm->offset;
564
+ err = rd_kafka_share_acknowledge_type(
565
+ rkshare, rkm,
566
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE);
567
+ } else {
568
+ /* REJECT last 2 */
569
+ err = rd_kafka_share_acknowledge_type(
570
+ rkshare, rkm,
571
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_REJECT);
572
+ }
573
+
574
+ TEST_ASSERT(!err, "acknowledge_type failed: %s",
575
+ rd_kafka_err2str(err));
576
+ consumed++;
577
+ }
578
+ rd_kafka_messages_destroy(batch);
579
+ batch = NULL;
580
+ }
581
+
582
+ TEST_SAY(
583
+ "Consumer A consumed %d messages "
584
+ "(5 ACCEPT, 3 RELEASE, 2 REJECT)\n",
585
+ consumed);
586
+ TEST_ASSERT(consumed == 10, "Expected 10, got %d", consumed);
587
+ TEST_ASSERT(released_cnt == 3, "Expected 3 released, got %d",
588
+ released_cnt);
589
+
590
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
591
+ TEST_ASSERT(!error, "commit_sync failed: %s",
592
+ error ? rd_kafka_error_string(error) : "");
593
+ TEST_ASSERT(partitions != NULL,
594
+ "Expected per-partition results, got NULL");
595
+ TEST_ASSERT(partitions->cnt == 1,
596
+ "Expected exactly 1 partition result, got %d",
597
+ partitions->cnt);
598
+ TEST_SAY(" %s [%" PRId32 "]: %s\n", partitions->elems[0].topic,
599
+ partitions->elems[0].partition,
600
+ rd_kafka_err2str(partitions->elems[0].err));
601
+ TEST_ASSERT(!strcmp(partitions->elems[0].topic, topic),
602
+ "Expected topic %s, got %s", topic,
603
+ partitions->elems[0].topic);
604
+ TEST_ASSERT(partitions->elems[0].partition == 0,
605
+ "Expected partition 0, got %" PRId32,
606
+ partitions->elems[0].partition);
607
+ TEST_ASSERT(partitions->elems[0].err == RD_KAFKA_RESP_ERR_NO_ERROR,
608
+ "Expected NO_ERROR, got %s",
609
+ rd_kafka_err2str(partitions->elems[0].err));
610
+
611
+ rd_kafka_topic_partition_list_destroy(partitions);
612
+
613
+ test_share_consumer_close(rkshare);
614
+ test_share_destroy(rkshare);
615
+
616
+ /* Consumer B: should only get the 3 RELEASE'd records */
617
+ rkshare = create_share_consumer(group, "implicit");
618
+ subscribe_consumer(rkshare, &topic, 1);
619
+
620
+ consumed = 0;
621
+ attempts = 0;
622
+ while (consumed < 3 && attempts++ < 10) {
623
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
624
+ if (error) {
625
+ rd_kafka_error_destroy(error);
626
+ continue;
627
+ }
628
+ rcvd = rd_kafka_messages_count(batch);
629
+
630
+ for (j = 0; j < rcvd; j++) {
631
+ rd_kafka_message_t *rkm =
632
+ rd_kafka_messages_get(batch, j);
633
+ if (!rkm->err) {
634
+ int16_t dc;
635
+ int k;
636
+ rd_bool_t found = rd_false;
637
+
638
+ dc = rd_kafka_message_delivery_count(rkm);
639
+ TEST_ASSERT(
640
+ dc >= 2,
641
+ "Consumer B got record at offset %" PRId64
642
+ " with delivery_count=%d, expected >= 2",
643
+ rkm->offset, (int)dc);
644
+
645
+ /* Verify it is one of the released offsets */
646
+ for (k = 0; k < released_cnt; k++) {
647
+ if (rkm->offset ==
648
+ released_offsets[k]) {
649
+ found = rd_true;
650
+ break;
651
+ }
652
+ }
653
+ TEST_ASSERT(found,
654
+ "Consumer B got offset %" PRId64
655
+ " which was not RELEASE'd",
656
+ rkm->offset);
657
+
658
+ consumed++;
659
+ }
660
+ }
661
+ rd_kafka_messages_destroy(batch);
662
+ batch = NULL;
663
+ }
664
+
665
+ TEST_SAY("Consumer B consumed %d messages (expected 3 RELEASE'd)\n",
666
+ consumed);
667
+ TEST_ASSERT(consumed == 3,
668
+ "Consumer B got %d records, expected 3 RELEASE'd",
669
+ consumed);
670
+
671
+ test_share_consumer_close(rkshare);
672
+ test_share_destroy(rkshare);
673
+
674
+ SUB_TEST_PASS();
675
+ }
676
+
677
+
678
+ /* ===================================================================
679
+ * Test 6: Multiple commit_sync calls.
680
+ *
681
+ * Consume 50 records, ACCEPT 10 at a time and commit_sync after
682
+ * each batch of 10 (5 commit_sync calls total). After each
683
+ * commit_sync, immediately call commit_sync again to verify
684
+ * a no-op commit (no pending acks) returns NULL/NULL cleanly.
685
+ * Consumer B should get 0 records.
686
+ * =================================================================== */
687
+ static void do_test_multiple_commit_sync_calls(void) {
688
+ const char *topic;
689
+ const char *group = "commit-sync-multi-calls";
690
+ rd_kafka_share_t *rkshare;
691
+ rd_kafka_error_t *error;
692
+ rd_kafka_topic_partition_list_t *partitions = NULL;
693
+ rd_kafka_messages_t *batch = NULL;
694
+ size_t rcvd;
695
+ size_t j;
696
+ int consumed = 0;
697
+ int attempts = 0;
698
+ int commit_cnt = 0;
699
+ int acked_since_last_commit = 0;
700
+
701
+ SUB_TEST();
702
+
703
+ topic = test_mk_topic_name("0176-cs-multi-calls", 1);
704
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
705
+ test_produce_msgs_simple(common_producer, topic, 0, 50);
706
+
707
+ rkshare = create_share_consumer(group, "explicit");
708
+ test_share_set_auto_offset_reset(group, "earliest");
709
+ subscribe_consumer(rkshare, &topic, 1);
710
+
711
+ /* Consume all 50, commit_sync every 10 records */
712
+ while (consumed < 50 && attempts++ < 60) {
713
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
714
+ if (error) {
715
+ rd_kafka_error_destroy(error);
716
+ continue;
717
+ }
718
+ rcvd = rd_kafka_messages_count(batch);
719
+
720
+ for (j = 0; j < rcvd; j++) {
721
+ rd_kafka_message_t *rkm =
722
+ rd_kafka_messages_get(batch, j);
723
+ if (!rkm->err) {
724
+ rd_kafka_share_acknowledge(rkshare, rkm);
725
+ consumed++;
726
+ acked_since_last_commit++;
727
+ }
728
+
729
+ /* commit_sync every 10 records */
730
+ if (acked_since_last_commit == 10) {
731
+ partitions = NULL;
732
+ error = rd_kafka_share_commit_sync(
733
+ rkshare, 30000, &partitions);
734
+ commit_cnt++;
735
+ TEST_ASSERT(
736
+ !error, "commit_sync #%d failed: %s",
737
+ commit_cnt,
738
+ error ? rd_kafka_error_string(error) : "");
739
+ TEST_ASSERT(
740
+ partitions != NULL,
741
+ "commit_sync #%d: expected results, "
742
+ "got NULL",
743
+ commit_cnt);
744
+ TEST_ASSERT(partitions->cnt == 1,
745
+ "commit_sync #%d: expected 1 "
746
+ "partition, got %d",
747
+ commit_cnt, partitions->cnt);
748
+ TEST_ASSERT(
749
+ !strcmp(partitions->elems[0].topic, topic),
750
+ "commit_sync #%d: expected topic "
751
+ "%s, got %s",
752
+ commit_cnt, topic,
753
+ partitions->elems[0].topic);
754
+ TEST_ASSERT(partitions->elems[0].partition == 0,
755
+ "commit_sync #%d: expected "
756
+ "partition 0, got %" PRId32,
757
+ commit_cnt,
758
+ partitions->elems[0].partition);
759
+ TEST_ASSERT(
760
+ partitions->elems[0].err ==
761
+ RD_KAFKA_RESP_ERR_NO_ERROR,
762
+ "commit_sync #%d: %s [%" PRId32 "] got %s",
763
+ commit_cnt, partitions->elems[0].topic,
764
+ partitions->elems[0].partition,
765
+ rd_kafka_err2str(partitions->elems[0].err));
766
+
767
+ rd_kafka_topic_partition_list_destroy(
768
+ partitions);
769
+ TEST_SAY(
770
+ "commit_sync #%d OK "
771
+ "(consumed %d so far)\n",
772
+ commit_cnt, consumed);
773
+ acked_since_last_commit = 0;
774
+
775
+ /* Immediately call commit_sync again —
776
+ * no pending acks, should return
777
+ * NULL/NULL */
778
+ partitions = NULL;
779
+ error = rd_kafka_share_commit_sync(
780
+ rkshare, 30000, &partitions);
781
+ TEST_ASSERT(
782
+ !error,
783
+ "back-to-back commit_sync after #%d "
784
+ "failed: %s",
785
+ commit_cnt,
786
+ error ? rd_kafka_error_string(error) : "");
787
+ TEST_ASSERT(
788
+ partitions == NULL,
789
+ "back-to-back commit_sync after #%d: "
790
+ "expected NULL partitions, got %d",
791
+ commit_cnt,
792
+ partitions ? partitions->cnt : -1);
793
+ TEST_SAY(
794
+ "back-to-back commit_sync after "
795
+ "#%d returned NULL as expected\n",
796
+ commit_cnt);
797
+ }
798
+ }
799
+ rd_kafka_messages_destroy(batch);
800
+ batch = NULL;
801
+ }
802
+
803
+ TEST_SAY("Consumer A consumed %d messages, %d commit_sync calls\n",
804
+ consumed, commit_cnt);
805
+ TEST_ASSERT(consumed == 50, "Expected 50, got %d", consumed);
806
+ TEST_ASSERT(commit_cnt == 5, "Expected 5 commit_sync calls, got %d",
807
+ commit_cnt);
808
+
809
+ test_share_consumer_close(rkshare);
810
+ test_share_destroy(rkshare);
811
+
812
+ /* Produce 5 verification records */
813
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
814
+
815
+ /* Consumer B: should only get the 5 verification records */
816
+ rkshare = create_share_consumer(group, "implicit");
817
+ subscribe_consumer(rkshare, &topic, 1);
818
+
819
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
820
+ rcvd = rd_kafka_messages_count(batch);
821
+ TEST_SAY("Consumer B share_poll returned: rcvd=%zu, error=%s\n", rcvd,
822
+ error ? rd_kafka_error_string(error) : "none");
823
+ if (error)
824
+ rd_kafka_error_destroy(error);
825
+
826
+ consumed = 0;
827
+ for (j = 0; j < rcvd; j++) {
828
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
829
+ if (!rkm->err) {
830
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
831
+ "Consumer B got redelivered record at "
832
+ "offset %" PRId64 " (delivery_count=%d)",
833
+ rkm->offset,
834
+ rd_kafka_message_delivery_count(rkm));
835
+ consumed++;
836
+ }
837
+ }
838
+ rd_kafka_messages_destroy(batch);
839
+ batch = NULL;
840
+
841
+ TEST_SAY("Consumer B got %d messages (expected 5)\n", consumed);
842
+ TEST_ASSERT(consumed == 5, "Expected 5 verification records, got %d",
843
+ consumed);
844
+
845
+ test_share_consumer_close(rkshare);
846
+ test_share_destroy(rkshare);
847
+
848
+ SUB_TEST_PASS();
849
+ }
850
+
851
+
852
+ /* ===================================================================
853
+ * Test 7: Multi-topic multi-partition commit_sync.
854
+ *
855
+ * 10 topics with 6 partitions each, 10 messages per partition
856
+ * (600 total). Consume in batches, apply mixed ack types
857
+ * (ACCEPT ~50%, RELEASE ~30%, REJECT ~20%) and commit_sync
858
+ * after each consume_batch call.
859
+ *
860
+ * RELEASE'd records are redelivered. Continue consuming until
861
+ * accepted + rejected == total_msgs (all records settled).
862
+ * On delivery_count >= MAX_REDELIVERY_ROUNDS, force ACCEPT.
863
+ * Verify commit_sync returns NO_ERROR each time and that
864
+ * delivery_count stays within bounds.
865
+ * =================================================================== */
866
+ #define MULTI_TP_TOPICS 10
867
+ #define MULTI_TP_PARTITIONS 6
868
+ #define MULTI_TP_MSGS_PER_PARTITION 10
869
+ #define MAX_REDELIVERY_ROUNDS 4
870
+
871
+ static rd_kafka_share_AcknowledgeType_t get_ack_type(int index) {
872
+ int r = index % 10;
873
+ if (r < 5)
874
+ return RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT;
875
+ else if (r < 8)
876
+ return RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE;
877
+ else
878
+ return RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_REJECT;
879
+ }
880
+
881
+ static void do_test_multi_topic_partition(void) {
882
+ const char *group = "commit-sync-multi-tp";
883
+ rd_kafka_share_t *rkshare;
884
+ rd_kafka_error_t *error;
885
+ rd_kafka_topic_partition_list_t *partitions = NULL;
886
+ rd_kafka_messages_t *batch = NULL;
887
+ char *topics[MULTI_TP_TOPICS];
888
+ size_t rcvd;
889
+ size_t j;
890
+ int total_consumed = 0;
891
+ int accepted = 0, released = 0, rejected = 0;
892
+ int attempts = 0;
893
+ int commit_cnt = 0;
894
+ int total_msgs =
895
+ MULTI_TP_TOPICS * MULTI_TP_PARTITIONS * MULTI_TP_MSGS_PER_PARTITION;
896
+ int i, p;
897
+ int16_t max_dc_seen = 0;
898
+
899
+ SUB_TEST();
900
+
901
+ /* Create topics and produce 10 messages per partition */
902
+ for (i = 0; i < MULTI_TP_TOPICS; i++) {
903
+ topics[i] = rd_strdup(test_mk_topic_name("0176-cs-mtp", 1));
904
+ test_create_topic_wait_exists(common_admin, topics[i],
905
+ MULTI_TP_PARTITIONS, -1,
906
+ 60 * 1000);
907
+ for (p = 0; p < MULTI_TP_PARTITIONS; p++)
908
+ test_produce_msgs_simple(common_producer, topics[i], p,
909
+ MULTI_TP_MSGS_PER_PARTITION);
910
+ }
911
+
912
+ rkshare = create_share_consumer(group, "explicit");
913
+ test_share_set_auto_offset_reset(group, "earliest");
914
+ subscribe_consumer(rkshare, (const char **)topics, MULTI_TP_TOPICS);
915
+
916
+ /* Consume until all records are settled
917
+ * (accepted + rejected == total_msgs) */
918
+ while (accepted + rejected < total_msgs && attempts++ < 200) {
919
+ rd_kafka_topic_partition_list_t *batch_tps;
920
+ int batch_cnt = 0;
921
+
922
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
923
+ if (error) {
924
+ rd_kafka_error_destroy(error);
925
+ continue;
926
+ }
927
+ rcvd = rd_kafka_messages_count(batch);
928
+
929
+ /* Track unique topic-partitions in this batch */
930
+ batch_tps = rd_kafka_topic_partition_list_new(
931
+ MULTI_TP_TOPICS * MULTI_TP_PARTITIONS);
932
+
933
+ for (j = 0; j < rcvd; j++) {
934
+ rd_kafka_share_AcknowledgeType_t ack_type;
935
+ rd_kafka_resp_err_t err;
936
+ int16_t dc;
937
+ const char *msg_topic;
938
+ rd_kafka_message_t *rkm =
939
+ rd_kafka_messages_get(batch, j);
940
+
941
+ if (rkm->err)
942
+ continue;
943
+
944
+ msg_topic = rd_kafka_topic_name(rkm->rkt);
945
+
946
+ /* Add to batch TPs if not already present */
947
+ if (!rd_kafka_topic_partition_list_find(
948
+ batch_tps, msg_topic, rkm->partition))
949
+ rd_kafka_topic_partition_list_add(
950
+ batch_tps, msg_topic, rkm->partition);
951
+
952
+ dc = rd_kafka_message_delivery_count(rkm);
953
+ if (dc > max_dc_seen)
954
+ max_dc_seen = dc;
955
+
956
+ /* On final delivery attempt, force ACCEPT */
957
+ if (dc >= MAX_REDELIVERY_ROUNDS)
958
+ ack_type =
959
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT;
960
+ else
961
+ ack_type = get_ack_type(total_consumed);
962
+
963
+ err = rd_kafka_share_acknowledge_type(rkshare, rkm,
964
+ ack_type);
965
+ TEST_ASSERT(!err, "acknowledge_type failed: %s",
966
+ rd_kafka_err2str(err));
967
+
968
+ if (ack_type == RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT)
969
+ accepted++;
970
+ else if (ack_type ==
971
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE)
972
+ released++;
973
+ else
974
+ rejected++;
975
+
976
+ total_consumed++;
977
+ batch_cnt++;
978
+ }
979
+ rd_kafka_messages_destroy(batch);
980
+ batch = NULL;
981
+
982
+ if (batch_cnt == 0) {
983
+ rd_kafka_topic_partition_list_destroy(batch_tps);
984
+ continue;
985
+ }
986
+
987
+ /* commit_sync after each batch */
988
+ partitions = NULL;
989
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
990
+ commit_cnt++;
991
+ TEST_ASSERT(!error, "commit_sync #%d failed: %s", commit_cnt,
992
+ error ? rd_kafka_error_string(error) : "");
993
+ TEST_ASSERT(partitions != NULL,
994
+ "commit_sync #%d: expected results, got NULL",
995
+ commit_cnt);
996
+
997
+ /* Verify commit_sync returned exactly the TPs we
998
+ * consumed from in this batch */
999
+ TEST_ASSERT(partitions->cnt == batch_tps->cnt,
1000
+ "commit_sync #%d: expected %d partition(s), got %d",
1001
+ commit_cnt, batch_tps->cnt, partitions->cnt);
1002
+
1003
+ for (i = 0; i < partitions->cnt; i++) {
1004
+ rd_kafka_topic_partition_t *rktpar =
1005
+ &partitions->elems[i];
1006
+ TEST_ASSERT(rktpar->err == RD_KAFKA_RESP_ERR_NO_ERROR,
1007
+ "commit_sync #%d: %s [%" PRId32 "] got %s",
1008
+ commit_cnt, rktpar->topic,
1009
+ rktpar->partition,
1010
+ rd_kafka_err2str(rktpar->err));
1011
+ TEST_ASSERT(rd_kafka_topic_partition_list_find(
1012
+ batch_tps, rktpar->topic,
1013
+ rktpar->partition) != NULL,
1014
+ "commit_sync #%d: unexpected partition "
1015
+ "%s [%" PRId32 "] in results",
1016
+ commit_cnt, rktpar->topic,
1017
+ rktpar->partition);
1018
+ }
1019
+
1020
+ TEST_SAY(
1021
+ "commit_sync #%d OK (%d in batch, %d total, "
1022
+ "settled=%d/%d, %d partition results)\n",
1023
+ commit_cnt, batch_cnt, total_consumed, accepted + rejected,
1024
+ total_msgs, partitions->cnt);
1025
+
1026
+ rd_kafka_topic_partition_list_destroy(partitions);
1027
+ rd_kafka_topic_partition_list_destroy(batch_tps);
1028
+ }
1029
+
1030
+ TEST_SAY(
1031
+ "Total consumed=%d (original=%d), commit_sync calls=%d, "
1032
+ "accepted=%d, released=%d, rejected=%d, "
1033
+ "max delivery_count=%d\n",
1034
+ total_consumed, total_msgs, commit_cnt, accepted, released,
1035
+ rejected, (int)max_dc_seen);
1036
+
1037
+ TEST_ASSERT(accepted + rejected == total_msgs,
1038
+ "Expected accepted(%d) + rejected(%d) == %d", accepted,
1039
+ rejected, total_msgs);
1040
+
1041
+ /* Verify redelivery happened */
1042
+ TEST_ASSERT(max_dc_seen > 1,
1043
+ "Expected redeliveries (max delivery_count > 1), "
1044
+ "got max=%d",
1045
+ (int)max_dc_seen);
1046
+
1047
+ TEST_ASSERT(max_dc_seen <= MAX_REDELIVERY_ROUNDS,
1048
+ "Max delivery_count=%d exceeds limit=%d", (int)max_dc_seen,
1049
+ MAX_REDELIVERY_ROUNDS);
1050
+
1051
+ test_share_consumer_close(rkshare);
1052
+ test_share_destroy(rkshare);
1053
+
1054
+ for (i = 0; i < MULTI_TP_TOPICS; i++)
1055
+ rd_free(topics[i]);
1056
+
1057
+ SUB_TEST_PASS();
1058
+ }
1059
+
1060
+
1061
+ /* ===================================================================
1062
+ * Mock broker infrastructure (same pattern as 0173).
1063
+ * =================================================================== */
1064
+ typedef struct test_ctx_s {
1065
+ rd_kafka_t *producer;
1066
+ rd_kafka_mock_cluster_t *mcluster;
1067
+ const char *bootstraps;
1068
+ } test_ctx_t;
1069
+
1070
+ static test_ctx_t test_ctx_new(void) {
1071
+ test_ctx_t ctx;
1072
+ rd_kafka_conf_t *conf;
1073
+ char errstr[512];
1074
+
1075
+ memset(&ctx, 0, sizeof(ctx));
1076
+
1077
+ ctx.mcluster = test_mock_cluster_new(3, &ctx.bootstraps);
1078
+
1079
+ TEST_ASSERT(rd_kafka_mock_set_apiversion(
1080
+ ctx.mcluster, RD_KAFKAP_ShareGroupHeartbeat, 1, 1) ==
1081
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1082
+ "Failed to enable ShareGroupHeartbeat");
1083
+ TEST_ASSERT(rd_kafka_mock_set_apiversion(ctx.mcluster,
1084
+ RD_KAFKAP_ShareFetch, 1, 1) ==
1085
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1086
+ "Failed to enable ShareFetch");
1087
+
1088
+ /* Set auto.offset.reset=earliest so tests that produce
1089
+ * before consuming see all records. */
1090
+ rd_kafka_mock_sharegroup_set_auto_offset_reset(ctx.mcluster, 1);
1091
+
1092
+ test_conf_init(&conf, NULL, 0);
1093
+ test_conf_set(conf, "bootstrap.servers", ctx.bootstraps);
1094
+ rd_kafka_conf_set_dr_msg_cb(conf, test_dr_msg_cb);
1095
+
1096
+ ctx.producer =
1097
+ rd_kafka_new(RD_KAFKA_PRODUCER, conf, errstr, sizeof(errstr));
1098
+ TEST_ASSERT(ctx.producer != NULL, "Failed to create producer: %s",
1099
+ errstr);
1100
+
1101
+ return ctx;
1102
+ }
1103
+
1104
+ static void test_ctx_destroy(test_ctx_t *ctx) {
1105
+ if (ctx->producer)
1106
+ rd_kafka_destroy(ctx->producer);
1107
+ if (ctx->mcluster)
1108
+ test_mock_cluster_destroy(ctx->mcluster);
1109
+ memset(ctx, 0, sizeof(*ctx));
1110
+ }
1111
+
1112
+ static void
1113
+ mock_produce_messages(rd_kafka_t *producer, const char *topic, int msgcnt) {
1114
+ int i;
1115
+ for (i = 0; i < msgcnt; i++) {
1116
+ char payload[64];
1117
+ snprintf(payload, sizeof(payload), "%s-%d", topic, i);
1118
+ TEST_ASSERT(rd_kafka_producev(
1119
+ producer, RD_KAFKA_V_TOPIC(topic),
1120
+ RD_KAFKA_V_VALUE(payload, strlen(payload)),
1121
+ RD_KAFKA_V_MSGFLAGS(RD_KAFKA_MSG_F_COPY),
1122
+ RD_KAFKA_V_END) == RD_KAFKA_RESP_ERR_NO_ERROR,
1123
+ "Produce failed");
1124
+ }
1125
+ rd_kafka_flush(producer, 5000);
1126
+ }
1127
+
1128
+ /**
1129
+ * @brief Create share consumer for mock broker tests.
1130
+ *
1131
+ * Unlike create_share_consumer() which uses test_conf_init with real
1132
+ * broker settings, this uses mock cluster bootstraps.
1133
+ */
1134
+ static rd_kafka_share_t *create_mock_share_consumer(const char *bootstraps,
1135
+ const char *group_id,
1136
+ const char *ack_mode) {
1137
+ rd_kafka_conf_t *conf;
1138
+ rd_kafka_share_t *rkshare;
1139
+
1140
+ test_conf_init(&conf, NULL, 0);
1141
+ test_conf_set(conf, "bootstrap.servers", bootstraps);
1142
+ test_conf_set(conf, "group.id", group_id);
1143
+ test_conf_set(conf, "share.acknowledgement.mode", ack_mode);
1144
+
1145
+ rkshare = rd_kafka_share_consumer_new(conf, NULL, 0);
1146
+ TEST_ASSERT(rkshare != NULL, "Failed to create share consumer");
1147
+ return rkshare;
1148
+ }
1149
+
1150
+ static int count_share_ack_requests(rd_kafka_mock_cluster_t *mcluster) {
1151
+ size_t cnt;
1152
+ size_t i;
1153
+ rd_kafka_mock_request_t **requests;
1154
+ int share_ack_cnt = 0;
1155
+
1156
+ requests = rd_kafka_mock_get_requests(mcluster, &cnt);
1157
+
1158
+ for (i = 0; i < cnt; i++) {
1159
+ int16_t api_key = rd_kafka_mock_request_api_key(requests[i]);
1160
+ if (api_key == RD_KAFKAP_ShareAcknowledge)
1161
+ share_ack_cnt++;
1162
+ }
1163
+
1164
+ rd_kafka_mock_request_destroy_array(requests, cnt);
1165
+ return share_ack_cnt;
1166
+ }
1167
+
1168
+
1169
+ /* ===================================================================
1170
+ * Test 8: Mock — verify commit_sync uses ShareAcknowledge RPC.
1171
+ *
1172
+ * Consume 50 records, ACCEPT 10 at a time, commit_sync after
1173
+ * each batch. Track ShareAcknowledge requests and verify the
1174
+ * count matches the number of commit_sync calls that returned
1175
+ * at least 1 partition result.
1176
+ * =================================================================== */
1177
+ static void do_test_mock_uses_share_acknowledge(void) {
1178
+ test_ctx_t ctx;
1179
+ rd_kafka_share_t *rkshare;
1180
+ rd_kafka_error_t *error;
1181
+ rd_kafka_topic_partition_list_t *partitions = NULL;
1182
+ const char *topic = "mock-cs-share-ack";
1183
+ const int msgcnt = 50;
1184
+ int consumed = 0;
1185
+ int attempts = 0;
1186
+ int commit_cnt = 0;
1187
+ int commit_with_partitions = 0;
1188
+ int acked_since_last_commit = 0;
1189
+ int share_ack_cnt;
1190
+ int i;
1191
+
1192
+ SUB_TEST_QUICK();
1193
+
1194
+ ctx = test_ctx_new();
1195
+ TEST_SAY("Mock cluster ready, bootstraps=%s\n", ctx.bootstraps);
1196
+
1197
+ TEST_ASSERT(rd_kafka_mock_topic_create(ctx.mcluster, topic, 1, 1) ==
1198
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1199
+ "Failed to create mock topic");
1200
+ TEST_SAY("Created mock topic [%s] (1 partition)\n", topic);
1201
+
1202
+ mock_produce_messages(ctx.producer, topic, msgcnt);
1203
+ TEST_SAY("Produced %d messages to [%s]\n", msgcnt, topic);
1204
+
1205
+ rkshare = create_mock_share_consumer(ctx.bootstraps, "sg-mock-cs-ack",
1206
+ "explicit");
1207
+ TEST_SAY(
1208
+ "Created share consumer (group=sg-mock-cs-ack, "
1209
+ "ack=explicit)\n");
1210
+
1211
+ subscribe_consumer(rkshare, &topic, 1);
1212
+ TEST_SAY("Subscribed to [%s]\n", topic);
1213
+
1214
+ /* Clear and start tracking requests */
1215
+ rd_kafka_mock_start_request_tracking(ctx.mcluster);
1216
+ rd_kafka_mock_clear_requests(ctx.mcluster);
1217
+ TEST_SAY(
1218
+ "Started mock request tracking; entering consume loop "
1219
+ "(msgcnt=%d, batch=%d)\n",
1220
+ msgcnt, CONSUME_ARRAY);
1221
+
1222
+ /* Consume all records, ACCEPT each, commit_sync every 10 */
1223
+ while (consumed < msgcnt && attempts++ < 30) {
1224
+ rd_kafka_messages_t *batch = NULL;
1225
+ size_t rcvd;
1226
+ size_t j;
1227
+
1228
+ TEST_SAY(
1229
+ "Iter %d: calling share_poll "
1230
+ "(consumed=%d/%d, acked_since_last=%d)\n",
1231
+ attempts, consumed, msgcnt, acked_since_last_commit);
1232
+
1233
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1234
+ if (error) {
1235
+ TEST_SAY(
1236
+ "Iter %d: share_poll error: %s "
1237
+ "(rcvd=%zu)\n",
1238
+ attempts, rd_kafka_error_string(error),
1239
+ rd_kafka_messages_count(batch));
1240
+ rd_kafka_error_destroy(error);
1241
+ rd_kafka_messages_destroy(batch);
1242
+ continue;
1243
+ }
1244
+ rcvd = rd_kafka_messages_count(batch);
1245
+
1246
+ TEST_SAY("Iter %d: share_poll returned %zu msg(s)\n", attempts,
1247
+ rcvd);
1248
+
1249
+ for (j = 0; j < rcvd; j++) {
1250
+ rd_kafka_message_t *rkm =
1251
+ rd_kafka_messages_get(batch, j);
1252
+ if (!rkm->err) {
1253
+ TEST_SAY(
1254
+ " ACK %s [%" PRId32 "] @ offset %" PRId64
1255
+ " (delivery_count=%d)\n",
1256
+ rd_kafka_topic_name(rkm->rkt),
1257
+ rkm->partition, rkm->offset,
1258
+ (int)rd_kafka_message_delivery_count(rkm));
1259
+ rd_kafka_share_acknowledge(rkshare, rkm);
1260
+ consumed++;
1261
+ acked_since_last_commit++;
1262
+ } else {
1263
+ TEST_SAY(
1264
+ " msg #%zu carries error %s on "
1265
+ "%s [%" PRId32 "] @ %" PRId64 "\n",
1266
+ j, rd_kafka_err2name(rkm->err),
1267
+ rkm->rkt ? rd_kafka_topic_name(rkm->rkt)
1268
+ : "(no-rkt)",
1269
+ rkm->partition, rkm->offset);
1270
+ }
1271
+
1272
+ if (acked_since_last_commit == 10) {
1273
+ partitions = NULL;
1274
+ TEST_SAY(
1275
+ "Calling commit_sync #%d "
1276
+ "(timeout=30000ms, acked=%d, "
1277
+ "consumed=%d)\n",
1278
+ commit_cnt + 1, acked_since_last_commit,
1279
+ consumed);
1280
+ error = rd_kafka_share_commit_sync(
1281
+ rkshare, 30000, &partitions);
1282
+ commit_cnt++;
1283
+ TEST_SAY(
1284
+ "commit_sync #%d returned: "
1285
+ "error=%s, partitions=%s, "
1286
+ "partition_cnt=%d\n",
1287
+ commit_cnt,
1288
+ error ? rd_kafka_error_string(error)
1289
+ : "NULL",
1290
+ partitions ? "non-NULL" : "NULL",
1291
+ partitions ? partitions->cnt : 0);
1292
+ TEST_ASSERT(
1293
+ !error, "commit_sync #%d failed: %s",
1294
+ commit_cnt,
1295
+ error ? rd_kafka_error_string(error) : "");
1296
+
1297
+ if (partitions != NULL) {
1298
+ commit_with_partitions++;
1299
+ for (i = 0; i < partitions->cnt; i++) {
1300
+ rd_kafka_topic_partition_t
1301
+ *rktpar =
1302
+ &partitions->elems[i];
1303
+ TEST_SAY(
1304
+ " commit_sync #%d "
1305
+ "partition[%d]: "
1306
+ "%s [%" PRId32
1307
+ "] offset=%" PRId64
1308
+ " err=%s\n",
1309
+ commit_cnt, i,
1310
+ rktpar->topic,
1311
+ rktpar->partition,
1312
+ rktpar->offset,
1313
+ rd_kafka_err2name(
1314
+ rktpar->err));
1315
+ TEST_ASSERT(
1316
+ rktpar->err ==
1317
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1318
+ "commit_sync #%d: "
1319
+ "%s [%" PRId32 "] got %s",
1320
+ commit_cnt, rktpar->topic,
1321
+ rktpar->partition,
1322
+ rd_kafka_err2str(
1323
+ rktpar->err));
1324
+ }
1325
+ rd_kafka_topic_partition_list_destroy(
1326
+ partitions);
1327
+ }
1328
+
1329
+ TEST_SAY(
1330
+ "commit_sync #%d OK "
1331
+ "(consumed %d so far)\n",
1332
+ commit_cnt, consumed);
1333
+ acked_since_last_commit = 0;
1334
+ }
1335
+ }
1336
+ rd_kafka_messages_destroy(batch);
1337
+ }
1338
+
1339
+ TEST_SAY(
1340
+ "Consume loop exited after %d iter(s): "
1341
+ "consumed=%d/%d, commit_cnt=%d, with_partitions=%d, "
1342
+ "acked_since_last=%d\n",
1343
+ attempts, consumed, msgcnt, commit_cnt, commit_with_partitions,
1344
+ acked_since_last_commit);
1345
+ TEST_ASSERT(consumed == msgcnt, "Expected %d, got %d", msgcnt,
1346
+ consumed);
1347
+
1348
+ /* Wait for async ops to complete before counting requests */
1349
+ TEST_SAY(
1350
+ "Sleeping 3s for async ops to drain before counting "
1351
+ "ShareAcknowledge requests\n");
1352
+ rd_sleep(3);
1353
+
1354
+ share_ack_cnt = count_share_ack_requests(ctx.mcluster);
1355
+ rd_kafka_mock_stop_request_tracking(ctx.mcluster);
1356
+
1357
+ TEST_SAY(
1358
+ "Mock: ShareAcknowledge requests=%d, "
1359
+ "commit_sync with partitions=%d\n",
1360
+ share_ack_cnt, commit_with_partitions);
1361
+ TEST_ASSERT(share_ack_cnt == commit_with_partitions,
1362
+ "Expected ShareAcknowledge count (%d) == commit_sync with "
1363
+ "partitions count (%d)",
1364
+ share_ack_cnt, commit_with_partitions);
1365
+
1366
+ test_share_consumer_close(rkshare);
1367
+ test_share_destroy(rkshare);
1368
+ TEST_SAY("Destroying mock test context\n");
1369
+ test_ctx_destroy(&ctx);
1370
+
1371
+ SUB_TEST_PASS();
1372
+ }
1373
+
1374
+
1375
+ /* ===================================================================
1376
+ * Test 9: Mock — commit_sync timeout and recovery.
1377
+ *
1378
+ * Phase 1: Consume 10 records, ACCEPT all. Set broker RTT to
1379
+ * 5000ms and call commit_sync with timeout_ms=2000. The call
1380
+ * should block ~2000ms then return REQUEST_TIMED_OUT (main
1381
+ * thread commit_sync deadline) or _TIMED_OUT (broker thread
1382
+ * socket.timeout.ms) per-partition. The broker still processes
1383
+ * the ack when the delayed response arrives.
1384
+ *
1385
+ * TODO KIP-932: Verify and maybe unify the timeout error
1386
+ * returned by commit_sync. librdkafka can return either
1387
+ * REQUEST_TIMED_OUT or _TIMED_OUT depending on which timer
1388
+ * fires first.
1389
+ *
1390
+ * Phase 2: Remove RTT, wait 5s for broker to finish processing.
1391
+ * Second consumer should get 0 records (acks were processed).
1392
+ *
1393
+ * Phase 3: Produce 10 more, consume and ACCEPT, commit_sync
1394
+ * with normal timeout → succeeds. Verifies recovery.
1395
+ * =================================================================== */
1396
+ static void do_test_mock_commit_sync_timeout(void) {
1397
+ test_ctx_t ctx;
1398
+ rd_kafka_share_t *rkshare;
1399
+ rd_kafka_error_t *error;
1400
+ rd_kafka_topic_partition_list_t *partitions = NULL;
1401
+ const char *topic = "mock-cs-timeout";
1402
+ const char *t = topic;
1403
+ const int msgcnt = 10;
1404
+ rd_kafka_messages_t *batch = NULL;
1405
+ size_t rcvd;
1406
+ size_t j;
1407
+ int consumed;
1408
+ int attempts;
1409
+ int i;
1410
+ rd_bool_t got_timed_out = rd_false;
1411
+ rd_ts_t t_start, t_elapsed_ms;
1412
+
1413
+ SUB_TEST_QUICK();
1414
+
1415
+ ctx = test_ctx_new();
1416
+
1417
+ TEST_ASSERT(rd_kafka_mock_topic_create(ctx.mcluster, topic, 1, 1) ==
1418
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1419
+ "Failed to create mock topic");
1420
+
1421
+ mock_produce_messages(ctx.producer, topic, msgcnt);
1422
+
1423
+ rkshare = create_mock_share_consumer(ctx.bootstraps,
1424
+ "sg-mock-cs-timeout", "explicit");
1425
+ subscribe_consumer(rkshare, &t, 1);
1426
+
1427
+ /* Phase 1: Consume all 10 records and ACCEPT */
1428
+ consumed = 0;
1429
+ attempts = 0;
1430
+ while (consumed < msgcnt && attempts++ < 30) {
1431
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1432
+ if (error) {
1433
+ rd_kafka_error_destroy(error);
1434
+ continue;
1435
+ }
1436
+ rcvd = rd_kafka_messages_count(batch);
1437
+
1438
+ for (j = 0; j < rcvd; j++) {
1439
+ rd_kafka_message_t *rkm =
1440
+ rd_kafka_messages_get(batch, j);
1441
+ if (!rkm->err) {
1442
+ rd_kafka_share_acknowledge(rkshare, rkm);
1443
+ consumed++;
1444
+ }
1445
+ }
1446
+ rd_kafka_messages_destroy(batch);
1447
+ batch = NULL;
1448
+ }
1449
+
1450
+ TEST_SAY("Phase 1: consumed and acknowledged %d messages\n", consumed);
1451
+ TEST_ASSERT(consumed == msgcnt, "Expected %d, got %d", msgcnt,
1452
+ consumed);
1453
+
1454
+ /* Inject 5000ms RTT on all brokers */
1455
+ rd_kafka_mock_broker_set_rtt(ctx.mcluster, -1, 5000);
1456
+
1457
+ /* commit_sync with 2000ms timeout — should block ~2000ms
1458
+ * then time out */
1459
+ t_start = test_clock();
1460
+ partitions = NULL;
1461
+ error = rd_kafka_share_commit_sync(rkshare, 2000, &partitions);
1462
+ t_elapsed_ms = (test_clock() - t_start) / 1000;
1463
+
1464
+ TEST_SAY("Phase 1: commit_sync returned after %" PRId64
1465
+ "ms, error=%s, partitions=%s\n",
1466
+ t_elapsed_ms, error ? rd_kafka_error_string(error) : "NULL",
1467
+ partitions ? "non-NULL" : "NULL");
1468
+
1469
+ /* Verify commit_sync blocked for ~2000ms (allow 1500-3000ms) */
1470
+ TEST_ASSERT(t_elapsed_ms >= 1500 && t_elapsed_ms <= 3000,
1471
+ "Expected commit_sync to block ~2000ms, "
1472
+ "got %" PRId64 "ms",
1473
+ t_elapsed_ms);
1474
+
1475
+ /* May return top-level error or per-partition timeout error.
1476
+ * Accept either:
1477
+ * - top-level error: main thread commit_sync deadline fired
1478
+ * first; rd_kafka_error_code(error) can be __TIMED_OUT
1479
+ * (raw — top-level errors are not run through the per-
1480
+ * partition translation funnel).
1481
+ * - per-partition REQUEST_TIMED_OUT: broker-thread reply
1482
+ * path stamped __TIMED_OUT on batches, translated to
1483
+ * REQUEST_TIMED_OUT at the app-facing funnel; or the api
1484
+ * timer cb wrote REQUEST_TIMED_OUT directly. */
1485
+ if (error) {
1486
+ TEST_SAY("Phase 1: top-level error: %s\n",
1487
+ rd_kafka_error_string(error));
1488
+ rd_kafka_error_destroy(error);
1489
+ got_timed_out = rd_true;
1490
+ }
1491
+
1492
+ if (partitions) {
1493
+ for (i = 0; i < partitions->cnt; i++) {
1494
+ rd_kafka_topic_partition_t *rktpar =
1495
+ &partitions->elems[i];
1496
+ TEST_SAY("Phase 1: %s [%" PRId32 "]: %s\n",
1497
+ rktpar->topic, rktpar->partition,
1498
+ rd_kafka_err2str(rktpar->err));
1499
+ if (rktpar->err == RD_KAFKA_RESP_ERR_REQUEST_TIMED_OUT)
1500
+ got_timed_out = rd_true;
1501
+ }
1502
+ rd_kafka_topic_partition_list_destroy(partitions);
1503
+ }
1504
+
1505
+ TEST_ASSERT(got_timed_out,
1506
+ "Expected REQUEST_TIMED_OUT or _TIMED_OUT error "
1507
+ "from commit_sync with short timeout");
1508
+
1509
+ /* Phase 2: Remove RTT, wait 5s for broker to finish
1510
+ * processing the delayed response */
1511
+ rd_kafka_mock_broker_set_rtt(ctx.mcluster, -1, 0);
1512
+ rd_sleep(5);
1513
+
1514
+ /* Close first consumer */
1515
+ test_share_consumer_close(rkshare);
1516
+ test_share_destroy(rkshare);
1517
+
1518
+ /* Second consumer: should get 0 records because the broker
1519
+ * processed the acks despite client-side timeout */
1520
+ rkshare = create_mock_share_consumer(ctx.bootstraps,
1521
+ "sg-mock-cs-timeout", "implicit");
1522
+ subscribe_consumer(rkshare, &t, 1);
1523
+
1524
+ consumed = 0;
1525
+ attempts = 0;
1526
+ while (attempts++ < 5) {
1527
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1528
+ if (error) {
1529
+ rd_kafka_error_destroy(error);
1530
+ continue;
1531
+ }
1532
+ rcvd = rd_kafka_messages_count(batch);
1533
+
1534
+ for (j = 0; j < rcvd; j++) {
1535
+ rd_kafka_message_t *rkm =
1536
+ rd_kafka_messages_get(batch, j);
1537
+ if (!rkm->err)
1538
+ consumed++;
1539
+ }
1540
+ rd_kafka_messages_destroy(batch);
1541
+ batch = NULL;
1542
+ }
1543
+
1544
+ TEST_SAY("Phase 2: second consumer got %d records (expected 0)\n",
1545
+ consumed);
1546
+ TEST_ASSERT(consumed == 0,
1547
+ "Second consumer got %d records, expected 0 "
1548
+ "(broker should have processed acks despite "
1549
+ "client-side timeout)",
1550
+ consumed);
1551
+
1552
+ test_share_consumer_close(rkshare);
1553
+ test_share_destroy(rkshare);
1554
+
1555
+ /* Phase 3: Produce more, consume, commit_sync normally —
1556
+ * verify recovery after timeout */
1557
+ mock_produce_messages(ctx.producer, topic, msgcnt);
1558
+
1559
+ rkshare = create_mock_share_consumer(ctx.bootstraps,
1560
+ "sg-mock-cs-timeout", "explicit");
1561
+ subscribe_consumer(rkshare, &t, 1);
1562
+
1563
+ consumed = 0;
1564
+ attempts = 0;
1565
+ while (consumed < msgcnt && attempts++ < 30) {
1566
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1567
+ if (error) {
1568
+ rd_kafka_error_destroy(error);
1569
+ continue;
1570
+ }
1571
+ rcvd = rd_kafka_messages_count(batch);
1572
+
1573
+ for (j = 0; j < rcvd; j++) {
1574
+ rd_kafka_message_t *rkm =
1575
+ rd_kafka_messages_get(batch, j);
1576
+ if (!rkm->err) {
1577
+ rd_kafka_share_acknowledge(rkshare, rkm);
1578
+ consumed++;
1579
+ }
1580
+ }
1581
+ rd_kafka_messages_destroy(batch);
1582
+ batch = NULL;
1583
+ }
1584
+
1585
+ TEST_SAY("Phase 3: consumed %d messages\n", consumed);
1586
+ TEST_ASSERT(consumed == msgcnt, "Expected %d, got %d", msgcnt,
1587
+ consumed);
1588
+
1589
+ partitions = NULL;
1590
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
1591
+ TEST_ASSERT(!error, "Phase 3: commit_sync failed: %s",
1592
+ error ? rd_kafka_error_string(error) : "");
1593
+ TEST_ASSERT(partitions != NULL,
1594
+ "Phase 3: expected partition results, got NULL");
1595
+
1596
+ for (i = 0; i < partitions->cnt; i++) {
1597
+ rd_kafka_topic_partition_t *rktpar = &partitions->elems[i];
1598
+ TEST_SAY("Phase 3: %s [%" PRId32 "]: %s\n", rktpar->topic,
1599
+ rktpar->partition, rd_kafka_err2str(rktpar->err));
1600
+ TEST_ASSERT(rktpar->err == RD_KAFKA_RESP_ERR_NO_ERROR,
1601
+ "Phase 3: expected NO_ERROR for %s [%" PRId32
1602
+ "], got %s",
1603
+ rktpar->topic, rktpar->partition,
1604
+ rd_kafka_err2str(rktpar->err));
1605
+ }
1606
+
1607
+ rd_kafka_topic_partition_list_destroy(partitions);
1608
+
1609
+ test_share_consumer_close(rkshare);
1610
+ test_share_destroy(rkshare);
1611
+ test_ctx_destroy(&ctx);
1612
+
1613
+ SUB_TEST_PASS();
1614
+ }
1615
+
1616
+
1617
+ /* ===================================================================
1618
+ * Test 10: Mixed commit types — commit_async and commit_sync.
1619
+ *
1620
+ * Produce 50 records. Consume all, ACCEPT each record
1621
+ * individually. For the first 10 records call commit_async,
1622
+ * then call commit_sync for the 11th. Repeat this pattern
1623
+ * (10 async + 1 sync) until all records are committed.
1624
+ *
1625
+ * Verify that commit_sync after async commits completes very
1626
+ * quickly (acks already sent by async, so sync has little or
1627
+ * no work to do). Second consumer should get 0 records.
1628
+ * =================================================================== */
1629
+ static void do_test_mixed_commit_types(void) {
1630
+ const char *topic;
1631
+ const char *group = "commit-sync-mixed-types";
1632
+ rd_kafka_share_t *rkshare;
1633
+ rd_kafka_error_t *error;
1634
+ rd_kafka_topic_partition_list_t *partitions = NULL;
1635
+ rd_kafka_messages_t *batch = NULL;
1636
+ size_t rcvd;
1637
+ size_t j;
1638
+ int consumed = 0;
1639
+ int attempts = 0;
1640
+ int async_cnt = 0;
1641
+ int sync_cnt = 0;
1642
+ int acked_since_last_sync = 0;
1643
+ int i;
1644
+ rd_ts_t t_start, t_elapsed_ms;
1645
+ rd_ts_t max_sync_elapsed_ms = 0;
1646
+
1647
+ SUB_TEST();
1648
+
1649
+ topic = test_mk_topic_name("0176-cs-mixed-types", 1);
1650
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
1651
+ test_produce_msgs_simple(common_producer, topic, 0, 50);
1652
+
1653
+ rkshare = create_share_consumer(group, "explicit");
1654
+ test_share_set_auto_offset_reset(group, "earliest");
1655
+ subscribe_consumer(rkshare, &topic, 1);
1656
+
1657
+ /* Consume all 50 records, ACCEPT each, alternate between
1658
+ * commit_async (10 times) and commit_sync (1 time) */
1659
+ while (consumed < 50 && attempts++ < 60) {
1660
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1661
+ if (error) {
1662
+ rd_kafka_error_destroy(error);
1663
+ continue;
1664
+ }
1665
+ rcvd = rd_kafka_messages_count(batch);
1666
+
1667
+ for (j = 0; j < rcvd; j++) {
1668
+ rd_kafka_message_t *rkm =
1669
+ rd_kafka_messages_get(batch, j);
1670
+ if (!rkm->err) {
1671
+ rd_kafka_share_acknowledge(rkshare, rkm);
1672
+ consumed++;
1673
+ acked_since_last_sync++;
1674
+
1675
+ if (acked_since_last_sync <= 10) {
1676
+ /* commit_async for first 10 */
1677
+ error = rd_kafka_share_commit_async(
1678
+ rkshare);
1679
+ TEST_ASSERT(
1680
+ !error,
1681
+ "commit_async #%d failed: %s",
1682
+ async_cnt + 1,
1683
+ error ? rd_kafka_error_string(error)
1684
+ : "");
1685
+ async_cnt++;
1686
+ }
1687
+
1688
+ if (acked_since_last_sync == 11) {
1689
+ /* commit_sync on the 11th —
1690
+ * should be fast since most acks
1691
+ * were already sent by async */
1692
+ partitions = NULL;
1693
+ t_start = test_clock();
1694
+ error = rd_kafka_share_commit_sync(
1695
+ rkshare, 30000, &partitions);
1696
+ t_elapsed_ms =
1697
+ (test_clock() - t_start) / 1000;
1698
+ sync_cnt++;
1699
+
1700
+ if (t_elapsed_ms > max_sync_elapsed_ms)
1701
+ max_sync_elapsed_ms =
1702
+ t_elapsed_ms;
1703
+
1704
+ TEST_ASSERT(
1705
+ !error,
1706
+ "commit_sync #%d failed: %s",
1707
+ sync_cnt,
1708
+ error ? rd_kafka_error_string(error)
1709
+ : "");
1710
+
1711
+ if (partitions) {
1712
+ for (i = 0; i < partitions->cnt;
1713
+ i++) {
1714
+ rd_kafka_topic_partition_t
1715
+ *rktpar =
1716
+ &partitions
1717
+ ->elems[i];
1718
+ TEST_ASSERT(
1719
+ rktpar->err ==
1720
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1721
+ "commit_sync #%d: "
1722
+ "%s [%" PRId32
1723
+ "] got %s",
1724
+ sync_cnt,
1725
+ rktpar->topic,
1726
+ rktpar->partition,
1727
+ rd_kafka_err2str(
1728
+ rktpar->err));
1729
+ }
1730
+ rd_kafka_topic_partition_list_destroy(
1731
+ partitions);
1732
+ }
1733
+
1734
+ TEST_SAY(
1735
+ "commit_sync #%d OK "
1736
+ "in %" PRId64
1737
+ "ms (consumed %d, "
1738
+ "%d async calls)\n",
1739
+ sync_cnt, t_elapsed_ms, consumed,
1740
+ async_cnt);
1741
+ acked_since_last_sync = 0;
1742
+ }
1743
+ }
1744
+ }
1745
+ rd_kafka_messages_destroy(batch);
1746
+ batch = NULL;
1747
+ }
1748
+
1749
+ /* Final commit_sync for any remaining acks */
1750
+ if (acked_since_last_sync > 0) {
1751
+ partitions = NULL;
1752
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
1753
+ sync_cnt++;
1754
+ TEST_ASSERT(!error, "final commit_sync failed: %s",
1755
+ error ? rd_kafka_error_string(error) : "");
1756
+ RD_IF_FREE(partitions, rd_kafka_topic_partition_list_destroy);
1757
+ TEST_SAY("Final commit_sync #%d OK\n", sync_cnt);
1758
+ }
1759
+
1760
+ TEST_SAY(
1761
+ "Consumed %d, async commits=%d, sync commits=%d, "
1762
+ "max sync elapsed=%" PRId64 "ms\n",
1763
+ consumed, async_cnt, sync_cnt, max_sync_elapsed_ms);
1764
+ TEST_ASSERT(consumed == 50, "Expected 50, got %d", consumed);
1765
+
1766
+ /* Verify commit_sync completed quickly — acks were mostly
1767
+ * already sent by commit_async, so sync should not wait long.
1768
+ * Allow up to 1000ms to account for broker round-trip. */
1769
+ TEST_ASSERT(max_sync_elapsed_ms < 1000,
1770
+ "commit_sync took too long (%" PRId64
1771
+ "ms), expected < 1000ms since acks were "
1772
+ "mostly sent by commit_async",
1773
+ max_sync_elapsed_ms);
1774
+
1775
+ /* Allow time for any in-flight async ack requests to complete
1776
+ * before closing the consumer. The final commit_async calls
1777
+ * may have dispatched acks that are still awaiting broker
1778
+ * response. */
1779
+ rd_sleep(3);
1780
+
1781
+ test_share_consumer_close(rkshare);
1782
+ test_share_destroy(rkshare);
1783
+
1784
+ /* Produce 5 verification records */
1785
+ test_produce_msgs_simple(common_producer, topic, 0, 5);
1786
+
1787
+ /* Second consumer: should only get the 5 verification records */
1788
+ rkshare = create_share_consumer(group, "implicit");
1789
+ subscribe_consumer(rkshare, &topic, 1);
1790
+
1791
+ error = rd_kafka_share_poll(rkshare, 15000, &batch);
1792
+ rcvd = rd_kafka_messages_count(batch);
1793
+ TEST_SAY("Consumer B share_poll returned: rcvd=%zu, error=%s\n", rcvd,
1794
+ error ? rd_kafka_error_string(error) : "none");
1795
+ if (error)
1796
+ rd_kafka_error_destroy(error);
1797
+
1798
+ consumed = 0;
1799
+ for (j = 0; j < rcvd; j++) {
1800
+ rd_kafka_message_t *rkm = rd_kafka_messages_get(batch, j);
1801
+ if (!rkm->err) {
1802
+ TEST_ASSERT(rd_kafka_message_delivery_count(rkm) == 1,
1803
+ "Consumer B got redelivered record at "
1804
+ "offset %" PRId64 " (delivery_count=%d)",
1805
+ rkm->offset,
1806
+ rd_kafka_message_delivery_count(rkm));
1807
+ consumed++;
1808
+ }
1809
+ }
1810
+ rd_kafka_messages_destroy(batch);
1811
+ batch = NULL;
1812
+
1813
+ TEST_SAY("Consumer B got %d messages (expected 5)\n", consumed);
1814
+ TEST_ASSERT(consumed == 5, "Expected 5 verification records, got %d",
1815
+ consumed);
1816
+
1817
+ test_share_consumer_close(rkshare);
1818
+ test_share_destroy(rkshare);
1819
+
1820
+ SUB_TEST_PASS();
1821
+ }
1822
+
1823
+
1824
+ /* ===================================================================
1825
+ * Test 11: Mock — broker dispatch priority (pending_commit_sync
1826
+ * dispatched before async_ack_details).
1827
+ *
1828
+ * Produce 30 messages. Consume all 30 in one batch. Then:
1829
+ * - ACCEPT first 10, commit_async → inflight (push entry 1: 2s RTT)
1830
+ * - ACCEPT next 10, commit_async → cached in async_ack_details
1831
+ * - ACCEPT last 10, commit_sync(5000) → pending_commit_sync
1832
+ *
1833
+ * When inflight completes at ~2s, broker dispatches next request.
1834
+ * If pending_commit_sync has priority (correct): sync dispatched
1835
+ * next (push entry 2: 2s RTT), completes at ~4s < 5s → NO_ERROR.
1836
+ * If async_ack_details has priority (wrong): async dispatched
1837
+ * next (2s RTT), then sync (2s RTT), completes at ~6s > 5s →
1838
+ * TIMED_OUT.
1839
+ *
1840
+ * Verification:
1841
+ * 1. commit_sync returns NO_ERROR and completes in ~4s (3500-4500ms)
1842
+ * 2. Per-partition results show NO_ERROR
1843
+ * 3. Second consumer gets 0 records (all acks processed)
1844
+ * =================================================================== */
1845
+ static void do_test_mock_broker_dispatch_priority(void) {
1846
+ test_ctx_t ctx;
1847
+ rd_kafka_share_t *rkshare;
1848
+ rd_kafka_error_t *error;
1849
+ rd_kafka_topic_partition_list_t *partitions = NULL;
1850
+ const char *topic = "mock-cs-dispatch-prio";
1851
+ const char *t = topic;
1852
+ const int msgcnt = 30;
1853
+ rd_kafka_messages_t *batch = NULL;
1854
+ rd_kafka_messages_t *all_batch = NULL;
1855
+ size_t rcvd;
1856
+ size_t j;
1857
+ int consumed;
1858
+ int attempts;
1859
+ int i;
1860
+ int32_t leader_broker_id = 1;
1861
+ rd_ts_t t_start, t_elapsed_ms;
1862
+
1863
+ SUB_TEST_QUICK();
1864
+
1865
+ ctx = test_ctx_new();
1866
+
1867
+ TEST_ASSERT(rd_kafka_mock_topic_create(ctx.mcluster, topic, 1, 1) ==
1868
+ RD_KAFKA_RESP_ERR_NO_ERROR,
1869
+ "Failed to create mock topic");
1870
+
1871
+ rd_kafka_mock_partition_set_leader(ctx.mcluster, topic, 0,
1872
+ leader_broker_id);
1873
+
1874
+ mock_produce_messages(ctx.producer, topic, msgcnt);
1875
+
1876
+ rkshare = create_mock_share_consumer(ctx.bootstraps, "sg-mock-cs-prio",
1877
+ "explicit");
1878
+ subscribe_consumer(rkshare, &t, 1);
1879
+
1880
+ /* Consume all 30 records in one batch, keep batch alive for
1881
+ * later acknowledge calls. */
1882
+ consumed = 0;
1883
+ attempts = 0;
1884
+ while (consumed == 0 && attempts++ < 30) {
1885
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
1886
+ if (error) {
1887
+ rd_kafka_error_destroy(error);
1888
+ rd_kafka_messages_destroy(batch);
1889
+ batch = NULL;
1890
+ continue;
1891
+ }
1892
+ rcvd = rd_kafka_messages_count(batch);
1893
+
1894
+ for (j = 0; j < rcvd; j++) {
1895
+ rd_kafka_message_t *rkm =
1896
+ rd_kafka_messages_get(batch, j);
1897
+ if (!rkm->err && consumed < msgcnt)
1898
+ consumed++;
1899
+ }
1900
+
1901
+ if (consumed == 0) {
1902
+ rd_kafka_messages_destroy(batch);
1903
+ batch = NULL;
1904
+ } else {
1905
+ all_batch = batch;
1906
+ batch = NULL;
1907
+ }
1908
+ }
1909
+
1910
+ TEST_SAY("Consumed %d/%d messages\n", consumed, msgcnt);
1911
+ TEST_ASSERT(consumed == msgcnt, "Expected %d, got %d", msgcnt,
1912
+ consumed);
1913
+
1914
+ /* Push 3 ShareAcknowledge entries with 2000ms RTT each on
1915
+ * the leader broker. These are consumed in order:
1916
+ * Entry 1: first commit_async inflight request (~2s)
1917
+ * Entry 2: pending_commit_sync (if priority correct) (~2s)
1918
+ * Entry 3: async_ack_details (dispatched last) (~2s) */
1919
+ rd_kafka_mock_broker_push_request_error_rtts(
1920
+ ctx.mcluster, leader_broker_id, RD_KAFKAP_ShareAcknowledge, 3,
1921
+ RD_KAFKA_RESP_ERR_NO_ERROR, 2000, RD_KAFKA_RESP_ERR_NO_ERROR, 2000,
1922
+ RD_KAFKA_RESP_ERR_NO_ERROR, 2000);
1923
+
1924
+ /* ACCEPT first 10, commit_async → sends first
1925
+ * ShareAcknowledge (inflight, push entry 1: 2s RTT) */
1926
+ for (i = 0; i < 10; i++)
1927
+ rd_kafka_share_acknowledge(rkshare,
1928
+ rd_kafka_messages_get(all_batch, i));
1929
+
1930
+ error = rd_kafka_share_commit_async(rkshare);
1931
+ TEST_ASSERT(!error, "commit_async #1 failed: %s",
1932
+ error ? rd_kafka_error_string(error) : "");
1933
+ TEST_SAY("commit_async #1 sent (inflight, 2s RTT)\n");
1934
+
1935
+ /* Small delay to ensure async request is dispatched to
1936
+ * broker thread before next commit */
1937
+ rd_usleep(200 * 1000, NULL);
1938
+
1939
+ /* ACCEPT next 10, commit_async → broker busy,
1940
+ * cached in async_ack_details */
1941
+ for (i = 10; i < 20; i++)
1942
+ rd_kafka_share_acknowledge(rkshare,
1943
+ rd_kafka_messages_get(all_batch, i));
1944
+
1945
+ error = rd_kafka_share_commit_async(rkshare);
1946
+ TEST_ASSERT(!error, "commit_async #2 failed: %s",
1947
+ error ? rd_kafka_error_string(error) : "");
1948
+ TEST_SAY("commit_async #2 sent (cached in async_ack_details)\n");
1949
+
1950
+ /* ACCEPT last 10, commit_sync(5000ms) → broker still busy,
1951
+ * stored in pending_commit_sync.
1952
+ *
1953
+ * Timeline if sync has dispatch priority (correct):
1954
+ * t=0s: inflight request sent
1955
+ * t=2s: inflight completes, sync dispatched (entry 2)
1956
+ * t=4s: sync completes → NO_ERROR (4s < 5s timeout)
1957
+ *
1958
+ * Timeline if async has priority (wrong):
1959
+ * t=0s: inflight request sent
1960
+ * t=2s: inflight completes, async dispatched (entry 2)
1961
+ * t=4s: async completes, sync dispatched (entry 3)
1962
+ * t=5s: sync timeout fires → TIMED_OUT (5s < 6s) */
1963
+ for (i = 20; i < 30; i++)
1964
+ rd_kafka_share_acknowledge(rkshare,
1965
+ rd_kafka_messages_get(all_batch, i));
1966
+
1967
+ TEST_SAY("Calling commit_sync(5000ms)\n");
1968
+ t_start = test_clock();
1969
+ partitions = NULL;
1970
+ error = rd_kafka_share_commit_sync(rkshare, 5000, &partitions);
1971
+ t_elapsed_ms = (test_clock() - t_start) / 1000;
1972
+
1973
+ TEST_SAY("commit_sync returned after %" PRId64 "ms, error=%s\n",
1974
+ t_elapsed_ms,
1975
+ error ? rd_kafka_error_string(error) : "NO_ERROR");
1976
+
1977
+ /* Verify no error */
1978
+ if (error) {
1979
+ TEST_ASSERT(rd_kafka_error_code(error) !=
1980
+ RD_KAFKA_RESP_ERR__TIMED_OUT,
1981
+ "commit_sync TIMED_OUT after %" PRId64
1982
+ "ms — pending_commit_sync was NOT dispatched "
1983
+ "before async_ack_details. "
1984
+ "Dispatch priority is broken.",
1985
+ t_elapsed_ms);
1986
+ rd_kafka_error_destroy(error);
1987
+ } else {
1988
+ TEST_SAY(
1989
+ "commit_sync returned NO_ERROR — dispatch "
1990
+ "priority is correct\n");
1991
+ }
1992
+
1993
+ /* Verify timing: should complete in ~4s (2s inflight + 2s sync),
1994
+ * not ~6s (2s inflight + 2s async + 2s sync). */
1995
+ TEST_ASSERT(t_elapsed_ms >= 3250 && t_elapsed_ms <= 4500,
1996
+ "Expected commit_sync to complete in ~4s (3250-4500ms), "
1997
+ "got %" PRId64 "ms. If >5s, dispatch priority is wrong.",
1998
+ t_elapsed_ms);
1999
+
2000
+ /* Verify per-partition results show NO_ERROR */
2001
+ TEST_ASSERT(partitions != NULL,
2002
+ "Expected per-partition results, got NULL");
2003
+
2004
+ for (i = 0; i < partitions->cnt; i++) {
2005
+ rd_kafka_topic_partition_t *rktpar = &partitions->elems[i];
2006
+ TEST_SAY(" %s [%" PRId32 "]: %s\n", rktpar->topic,
2007
+ rktpar->partition, rd_kafka_err2str(rktpar->err));
2008
+ TEST_ASSERT(rktpar->err == RD_KAFKA_RESP_ERR_NO_ERROR,
2009
+ "Expected NO_ERROR for %s [%" PRId32 "], got %s",
2010
+ rktpar->topic, rktpar->partition,
2011
+ rd_kafka_err2str(rktpar->err));
2012
+ }
2013
+
2014
+ rd_kafka_topic_partition_list_destroy(partitions);
2015
+
2016
+ /* Destroy the held batch (frees all message handles) */
2017
+ rd_kafka_messages_destroy(all_batch);
2018
+ all_batch = NULL;
2019
+
2020
+ /* Wait for remaining async to complete */
2021
+ rd_sleep(3);
2022
+
2023
+ test_share_consumer_close(rkshare);
2024
+ test_share_destroy(rkshare);
2025
+
2026
+ /* Second consumer: should get 0 records since all acks
2027
+ * were processed successfully */
2028
+ rkshare = create_mock_share_consumer(ctx.bootstraps, "sg-mock-cs-prio",
2029
+ "implicit");
2030
+ subscribe_consumer(rkshare, &t, 1);
2031
+
2032
+ consumed = 0;
2033
+ attempts = 0;
2034
+ while (attempts++ < 5) {
2035
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2036
+ if (error) {
2037
+ rd_kafka_error_destroy(error);
2038
+ continue;
2039
+ }
2040
+ rcvd = rd_kafka_messages_count(batch);
2041
+
2042
+ for (j = 0; j < rcvd; j++) {
2043
+ rd_kafka_message_t *rkm =
2044
+ rd_kafka_messages_get(batch, j);
2045
+ if (!rkm->err)
2046
+ consumed++;
2047
+ }
2048
+ rd_kafka_messages_destroy(batch);
2049
+ batch = NULL;
2050
+ }
2051
+
2052
+ TEST_SAY("Second consumer got %d records (expected 0)\n", consumed);
2053
+ TEST_ASSERT(consumed == 0,
2054
+ "Second consumer got %d records, expected 0 "
2055
+ "(all acks should have been processed)",
2056
+ consumed);
2057
+
2058
+ test_share_consumer_close(rkshare);
2059
+ test_share_destroy(rkshare);
2060
+ test_ctx_destroy(&ctx);
2061
+
2062
+ SUB_TEST_PASS();
2063
+ }
2064
+
2065
+
2066
+ /* Extended ack callback state with error tracking for sync commit tests */
2067
+ typedef struct ack_cb_state_s {
2068
+ test_ack_cb_state_t base; /* Base state from test.h */
2069
+ rd_kafka_resp_err_t errors[64]; /* Track errors per callback */
2070
+ int error_cnt;
2071
+ } ack_cb_state_t;
2072
+
2073
+ static void share_ack_cb(rd_kafka_share_t *rkshare,
2074
+ rd_kafka_share_partition_offsets_list_t *partitions,
2075
+ rd_kafka_resp_err_t err,
2076
+ void *opaque) {
2077
+ ack_cb_state_t *state = (ack_cb_state_t *)opaque;
2078
+ const rd_kafka_share_partition_offsets_t *entry;
2079
+ size_t partition_cnt, offsets_in_entry = 0;
2080
+
2081
+ (void)rkshare;
2082
+
2083
+ partition_cnt = rd_kafka_share_partition_offsets_list_count(partitions);
2084
+
2085
+ entry = rd_kafka_share_partition_offsets_list_get(partitions, 0);
2086
+ if (entry)
2087
+ offsets_in_entry =
2088
+ rd_kafka_share_partition_offsets_offsets_cnt(entry);
2089
+
2090
+ TEST_SAY("ACK CALLBACK: err=%s (%d), partitions=%zu, offsets=%zu\n",
2091
+ rd_kafka_err2name(err), err, partition_cnt, offsets_in_entry);
2092
+
2093
+ test_ack_cb_state_push_err(&state->base, err);
2094
+
2095
+ /* Track this error in our errors array */
2096
+ if (state->error_cnt < 64)
2097
+ state->errors[state->error_cnt++] = err;
2098
+
2099
+ if (entry)
2100
+ state->base.total_offsets += offsets_in_entry;
2101
+ }
2102
+
2103
+
2104
+
2105
+ /* ===================================================================
2106
+ * Test: commit_sync callback invocation.
2107
+ *
2108
+ * Verifies that the runtime acknowledgement callback is invoked after
2109
+ * commit_sync when using dedicated ShareAcknowledge request.
2110
+ * =================================================================== */
2111
+ static void do_test_commit_sync_callback(void) {
2112
+ const char *topic;
2113
+ const char *group = "commit-sync-callback";
2114
+ rd_kafka_share_t *rkshare;
2115
+ rd_kafka_error_t *error;
2116
+ rd_kafka_topic_partition_list_t *partitions = NULL;
2117
+ rd_kafka_messages_t *batch = NULL;
2118
+ size_t rcvd;
2119
+ size_t j;
2120
+ size_t consumed = 0;
2121
+ int attempts = 0;
2122
+ test_ack_cb_state_t state = {0};
2123
+
2124
+ SUB_TEST();
2125
+
2126
+ topic = test_mk_topic_name("0176-cs-callback", 1);
2127
+ test_create_topic_wait_exists(NULL, topic, 1, -1, 60 * 1000);
2128
+ test_produce_msgs_simple(common_producer, topic, 0, 50);
2129
+
2130
+ rkshare =
2131
+ test_create_share_consumer_with_cb(group, "explicit", &state, NULL);
2132
+ const char *grp_conf[] = {"share.auto.offset.reset", "SET", "earliest"};
2133
+ test_alter_group_configurations(group, grp_conf, 1);
2134
+ subscribe_consumer(rkshare, &topic, 1);
2135
+
2136
+ /* Consume and acknowledge messages */
2137
+ while (consumed < 20 && attempts++ < 30) {
2138
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2139
+ if (error) {
2140
+ rd_kafka_error_destroy(error);
2141
+ continue;
2142
+ }
2143
+ rcvd = rd_kafka_messages_count(batch);
2144
+ for (j = 0; j < rcvd; j++) {
2145
+ rd_kafka_message_t *rkm =
2146
+ rd_kafka_messages_get(batch, j);
2147
+ if (!rkm->err) {
2148
+ rd_kafka_share_acknowledge(rkshare, rkm);
2149
+ consumed++;
2150
+ }
2151
+ }
2152
+ rd_kafka_messages_destroy(batch);
2153
+ batch = NULL;
2154
+ }
2155
+
2156
+ TEST_SAY("Consumed and acknowledged %zu messages\n", consumed);
2157
+ TEST_ASSERT(consumed > 0, "Expected to consume some messages");
2158
+
2159
+ /* Call commit_sync to trigger callback */
2160
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
2161
+ TEST_ASSERT(!error, "commit_sync failed: %s",
2162
+ error ? rd_kafka_error_string(error) : "");
2163
+ RD_IF_FREE(partitions, rd_kafka_topic_partition_list_destroy);
2164
+
2165
+ /* Wait for callback */
2166
+ test_wait_for_cb_with_poll(&state, rkshare, 1, 10000);
2167
+
2168
+ TEST_SAY("Callback count=%d, total_offsets=%zu\n", state.callback_cnt,
2169
+ state.total_offsets);
2170
+
2171
+ TEST_ASSERT(state.callback_cnt == 1,
2172
+ "Expected callback to be invoked once, got %d",
2173
+ state.callback_cnt);
2174
+ TEST_ASSERT(state.total_offsets == consumed,
2175
+ "Expected %zu offsets in callback, got %zu", consumed,
2176
+ state.total_offsets);
2177
+
2178
+ test_share_consumer_close(rkshare);
2179
+ test_share_destroy(rkshare);
2180
+ test_ack_cb_state_destroy(&state);
2181
+
2182
+ SUB_TEST_PASS();
2183
+ }
2184
+
2185
+ /* ===================================================================
2186
+ * commit_sync after subscribed topic deletion surfaces a per-partition
2187
+ * error.
2188
+ *
2189
+ * Produce records, consume them, delete the subscribed topic, then
2190
+ * call commit_sync. The returned per-partition list should include the
2191
+ * deleted topic's partition with an error code (typically
2192
+ * UNKNOWN_TOPIC_OR_PART), not silent success.
2193
+ * =================================================================== */
2194
+ static void do_test_commit_sync_after_topic_deletion(void) {
2195
+ const char *topic_name;
2196
+ char *topic_dup;
2197
+ char *topics_for_delete[1];
2198
+ const char *group = "commit-sync-deleted-topic";
2199
+ rd_kafka_share_t *rkshare;
2200
+ rd_kafka_error_t *error;
2201
+ rd_kafka_topic_partition_list_t *partitions = NULL;
2202
+ rd_kafka_messages_t *batch = NULL;
2203
+ rd_kafka_resp_err_t del_err;
2204
+ size_t rcvd;
2205
+ size_t j;
2206
+ int consumed = 0;
2207
+ int attempts = 0;
2208
+ rd_bool_t topic_err_seen = rd_false;
2209
+
2210
+ SUB_TEST();
2211
+
2212
+ if (!strcmp(test_getenv("TEST_BROKER_OS", ""), "windows")) {
2213
+ TEST_SAY(
2214
+ "Skipping commit_sync-after-topic-deletion "
2215
+ "(broker on Windows)\n");
2216
+ SUB_TEST_PASS();
2217
+ return;
2218
+ }
2219
+
2220
+ topic_name = test_mk_topic_name("0176-cs-deleted", 1);
2221
+ topic_dup = rd_strdup(topic_name);
2222
+ test_create_topic_wait_exists(common_admin, topic_name, 1, -1,
2223
+ 60 * 1000);
2224
+ test_produce_msgs_simple(common_producer, topic_name, 0, 5);
2225
+
2226
+ rkshare = create_share_consumer(group, "implicit");
2227
+ test_share_set_auto_offset_reset(group, "earliest");
2228
+ subscribe_consumer(rkshare, &topic_name, 1);
2229
+
2230
+ /* Consume records (so there's something to commit) */
2231
+ while (consumed == 0 && attempts++ < 30) {
2232
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2233
+ if (error) {
2234
+ rd_kafka_error_destroy(error);
2235
+ continue;
2236
+ }
2237
+ rcvd = rd_kafka_messages_count(batch);
2238
+ for (j = 0; j < rcvd; j++) {
2239
+ rd_kafka_message_t *rkm =
2240
+ rd_kafka_messages_get(batch, j);
2241
+ if (!rkm->err)
2242
+ consumed++;
2243
+ }
2244
+ rd_kafka_messages_destroy(batch);
2245
+ batch = NULL;
2246
+ }
2247
+ TEST_ASSERT(consumed > 0, "Expected to consume records, got 0");
2248
+
2249
+ /* Delete the subscribed topic */
2250
+ TEST_SAY("Deleting topic %s\n", topic_name);
2251
+ topics_for_delete[0] = topic_dup;
2252
+ del_err = test_DeleteTopics_simple(common_admin, NULL,
2253
+ topics_for_delete, 1, NULL);
2254
+ TEST_ASSERT(del_err == RD_KAFKA_RESP_ERR_NO_ERROR,
2255
+ "DeleteTopics failed: %s", rd_kafka_err2str(del_err));
2256
+
2257
+ /* Wait for the topic delete to propagate in the cluster. */
2258
+ rd_sleep(3);
2259
+
2260
+ /* commit_sync should return per-partition results — at least one
2261
+ * partition's err should indicate the deletion. */
2262
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
2263
+ TEST_SAY("commit_sync returned: error=%s, partitions=%p\n",
2264
+ error ? rd_kafka_error_string(error) : "NULL",
2265
+ (void *)partitions);
2266
+
2267
+ if (error)
2268
+ rd_kafka_error_destroy(error);
2269
+
2270
+ if (partitions) {
2271
+ int i;
2272
+ TEST_SAY("commit_sync returned %d partition entries\n",
2273
+ partitions->cnt);
2274
+ for (i = 0; i < partitions->cnt; i++) {
2275
+ TEST_SAY(" %s [%" PRId32 "]: %s\n",
2276
+ partitions->elems[i].topic,
2277
+ partitions->elems[i].partition,
2278
+ rd_kafka_err2name(partitions->elems[i].err));
2279
+ if (partitions->elems[i].err !=
2280
+ RD_KAFKA_RESP_ERR_NO_ERROR)
2281
+ topic_err_seen = rd_true;
2282
+ }
2283
+ rd_kafka_topic_partition_list_destroy(partitions);
2284
+ }
2285
+
2286
+ TEST_ASSERT(topic_err_seen,
2287
+ "Expected commit_sync to surface a per-partition error "
2288
+ "after topic deletion");
2289
+
2290
+ rd_free(topic_dup);
2291
+
2292
+ test_share_consumer_close(rkshare);
2293
+ test_share_destroy(rkshare);
2294
+
2295
+ SUB_TEST_PASS();
2296
+ }
2297
+
2298
+
2299
+ /* ===================================================================
2300
+ * Chaos: strict per-record ack / commit interleaving.
2301
+ *
2302
+ * Pattern over 30 ACCEPTed records:
2303
+ * Phase A (records 0-14): one commit per ack, alternating
2304
+ * commit_sync (odd-indexed acks) and
2305
+ * commit_async (even-indexed acks).
2306
+ * Phase B (records 15-29): one commit per every 2 acks,
2307
+ * alternating commit_sync / commit_async
2308
+ * by pair index.
2309
+ * Verifies the library survives tight ack/commit alternation and
2310
+ * that the ack callback fires at least once across the run.
2311
+ * =================================================================== */
2312
+ static void do_test_chaos_111_ack_commit_interleave(void) {
2313
+ const char *topic;
2314
+ const char *group = "commit-sync-chaos-111";
2315
+ rd_kafka_share_t *rkshare;
2316
+ rd_kafka_error_t *error;
2317
+ rd_kafka_topic_partition_list_t *partitions = NULL;
2318
+ rd_kafka_messages_t *batch = NULL;
2319
+ size_t rcvd;
2320
+ size_t j;
2321
+ int consumed = 0;
2322
+ int attempts = 0;
2323
+ int sync_cnt = 0;
2324
+ int async_cnt = 0;
2325
+ const int total_msgs = 30;
2326
+ const int phase_a = 15; /* 1-1 interleave region */
2327
+ ack_cb_state_t state = {0};
2328
+
2329
+ SUB_TEST();
2330
+
2331
+ topic = test_mk_topic_name("0176-cs-chaos-111", 1);
2332
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
2333
+ test_produce_msgs_simple(common_producer, topic, 0, total_msgs);
2334
+
2335
+ rkshare = test_create_share_consumer_with_cb(group, "explicit",
2336
+ &state.base, share_ack_cb);
2337
+ test_share_set_auto_offset_reset(group, "earliest");
2338
+ subscribe_consumer(rkshare, &topic, 1);
2339
+
2340
+ while (consumed < total_msgs && attempts++ < 60) {
2341
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2342
+ if (error) {
2343
+ rd_kafka_error_destroy(error);
2344
+ continue;
2345
+ }
2346
+ rcvd = rd_kafka_messages_count(batch);
2347
+
2348
+ for (j = 0; j < rcvd && consumed < total_msgs; j++) {
2349
+ rd_kafka_message_t *rkm =
2350
+ rd_kafka_messages_get(batch, j);
2351
+ if (rkm->err)
2352
+ continue;
2353
+
2354
+ rd_kafka_share_acknowledge(rkshare, rkm);
2355
+ consumed++;
2356
+
2357
+ if (consumed <= phase_a) {
2358
+ /* Phase A: ack 1 -> commit alternates */
2359
+ if (consumed % 2 == 1) {
2360
+ error = rd_kafka_share_commit_sync(
2361
+ rkshare, 30000, &partitions);
2362
+ TEST_ASSERT(
2363
+ !error,
2364
+ "phase A commit_sync #%d: %s",
2365
+ sync_cnt + 1,
2366
+ error ? rd_kafka_error_string(error)
2367
+ : "");
2368
+ RD_IF_FREE(
2369
+ partitions,
2370
+ rd_kafka_topic_partition_list_destroy);
2371
+ partitions = NULL;
2372
+ sync_cnt++;
2373
+ } else {
2374
+ error = rd_kafka_share_commit_async(
2375
+ rkshare);
2376
+ TEST_ASSERT(
2377
+ !error,
2378
+ "phase A commit_async #%d: %s",
2379
+ async_cnt + 1,
2380
+ error ? rd_kafka_error_string(error)
2381
+ : "");
2382
+ async_cnt++;
2383
+ }
2384
+ } else if (consumed % 2 == 0) {
2385
+ /* Phase B: commit every 2 records,
2386
+ * alternating sync/async by pair index */
2387
+ int pair_idx = (consumed - phase_a) / 2;
2388
+ if (pair_idx % 2 == 1) {
2389
+ error = rd_kafka_share_commit_async(
2390
+ rkshare);
2391
+ TEST_ASSERT(
2392
+ !error,
2393
+ "phase B commit_async #%d: %s",
2394
+ async_cnt + 1,
2395
+ error ? rd_kafka_error_string(error)
2396
+ : "");
2397
+ async_cnt++;
2398
+ } else {
2399
+ error = rd_kafka_share_commit_sync(
2400
+ rkshare, 30000, &partitions);
2401
+ TEST_ASSERT(
2402
+ !error,
2403
+ "phase B commit_sync #%d: %s",
2404
+ sync_cnt + 1,
2405
+ error ? rd_kafka_error_string(error)
2406
+ : "");
2407
+ RD_IF_FREE(
2408
+ partitions,
2409
+ rd_kafka_topic_partition_list_destroy);
2410
+ partitions = NULL;
2411
+ sync_cnt++;
2412
+ }
2413
+ }
2414
+ }
2415
+ rd_kafka_messages_destroy(batch);
2416
+ batch = NULL;
2417
+ }
2418
+
2419
+ TEST_ASSERT(consumed == total_msgs, "Expected %d records, consumed %d",
2420
+ total_msgs, consumed);
2421
+
2422
+ /* Drain any pending async commit callbacks. */
2423
+ test_wait_for_cb_with_poll(&state.base, rkshare, sync_cnt + async_cnt,
2424
+ 10000);
2425
+
2426
+ TEST_SAY(
2427
+ "chaos 1-1-1: consumed=%d sync=%d async=%d "
2428
+ "callbacks=%d offsets=%zu last_err=%s\n",
2429
+ consumed, sync_cnt, async_cnt, state.base.callback_cnt,
2430
+ state.base.total_offsets,
2431
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
2432
+
2433
+ TEST_ASSERT(state.base.callback_cnt >= 1,
2434
+ "Expected at least 1 ack callback, got %d",
2435
+ state.base.callback_cnt);
2436
+ TEST_ASSERT(
2437
+ test_ack_cb_state_first_err(&state.base) ==
2438
+ RD_KAFKA_RESP_ERR_NO_ERROR,
2439
+ "Ack callback reported err: %s",
2440
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
2441
+
2442
+ test_share_consumer_close(rkshare);
2443
+ test_share_destroy(rkshare);
2444
+ test_ack_cb_state_destroy(&state.base);
2445
+
2446
+ SUB_TEST_PASS();
2447
+ }
2448
+
2449
+
2450
+ /* ===================================================================
2451
+ * Chaos: per-record alternating commit_sync / commit_async.
2452
+ *
2453
+ * Consume 40 records and, for every single ACCEPTed record, alternate
2454
+ * between commit_sync and commit_async. Stresses the library's
2455
+ * bookkeeping under the tightest possible commit cadence.
2456
+ * =================================================================== */
2457
+ static void do_test_chaos_alternating_commits(void) {
2458
+ const char *topic;
2459
+ const char *group = "commit-sync-chaos-alt";
2460
+ rd_kafka_share_t *rkshare;
2461
+ rd_kafka_error_t *error;
2462
+ rd_kafka_topic_partition_list_t *partitions = NULL;
2463
+ rd_kafka_messages_t *batch = NULL;
2464
+ size_t rcvd;
2465
+ size_t j;
2466
+ int consumed = 0;
2467
+ int attempts = 0;
2468
+ int sync_cnt = 0;
2469
+ int async_cnt = 0;
2470
+ const int total_msgs = 40;
2471
+ ack_cb_state_t state = {0};
2472
+
2473
+ SUB_TEST();
2474
+
2475
+ topic = test_mk_topic_name("0176-cs-chaos-alt", 1);
2476
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
2477
+ test_produce_msgs_simple(common_producer, topic, 0, total_msgs);
2478
+
2479
+ rkshare = test_create_share_consumer_with_cb(group, "explicit",
2480
+ &state.base, share_ack_cb);
2481
+ test_share_set_auto_offset_reset(group, "earliest");
2482
+ subscribe_consumer(rkshare, &topic, 1);
2483
+
2484
+ while (consumed < total_msgs && attempts++ < 60) {
2485
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2486
+ if (error) {
2487
+ rd_kafka_error_destroy(error);
2488
+ continue;
2489
+ }
2490
+ rcvd = rd_kafka_messages_count(batch);
2491
+
2492
+ for (j = 0; j < rcvd && consumed < total_msgs; j++) {
2493
+ rd_kafka_message_t *rkm =
2494
+ rd_kafka_messages_get(batch, j);
2495
+ if (rkm->err)
2496
+ continue;
2497
+ rd_kafka_share_acknowledge(rkshare, rkm);
2498
+ consumed++;
2499
+
2500
+ if (consumed % 2 == 1) {
2501
+ error = rd_kafka_share_commit_sync(
2502
+ rkshare, 30000, &partitions);
2503
+ TEST_ASSERT(
2504
+ !error, "commit_sync #%d: %s", sync_cnt + 1,
2505
+ error ? rd_kafka_error_string(error) : "");
2506
+ RD_IF_FREE(
2507
+ partitions,
2508
+ rd_kafka_topic_partition_list_destroy);
2509
+ partitions = NULL;
2510
+ sync_cnt++;
2511
+ } else {
2512
+ error = rd_kafka_share_commit_async(rkshare);
2513
+ TEST_ASSERT(!error, "commit_async #%d: %s",
2514
+ async_cnt + 1,
2515
+ error ? rd_kafka_error_string(error)
2516
+ : "");
2517
+ async_cnt++;
2518
+ }
2519
+ }
2520
+ rd_kafka_messages_destroy(batch);
2521
+ batch = NULL;
2522
+ }
2523
+
2524
+ TEST_ASSERT(consumed == total_msgs, "Expected %d records, consumed %d",
2525
+ total_msgs, consumed);
2526
+
2527
+ test_wait_for_cb_with_poll(&state.base, rkshare, sync_cnt + async_cnt,
2528
+ 10000);
2529
+
2530
+ TEST_SAY(
2531
+ "chaos alt: consumed=%d sync=%d async=%d "
2532
+ "callbacks=%d offsets=%zu last_err=%s\n",
2533
+ consumed, sync_cnt, async_cnt, state.base.callback_cnt,
2534
+ state.base.total_offsets,
2535
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
2536
+
2537
+ TEST_ASSERT(
2538
+ test_ack_cb_state_first_err(&state.base) ==
2539
+ RD_KAFKA_RESP_ERR_NO_ERROR,
2540
+ "Ack callback reported err: %s",
2541
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
2542
+
2543
+ test_share_consumer_close(rkshare);
2544
+ test_share_destroy(rkshare);
2545
+ test_ack_cb_state_destroy(&state.base);
2546
+
2547
+ SUB_TEST_PASS();
2548
+ }
2549
+
2550
+
2551
+ /* ===================================================================
2552
+ * Chaos: randomized ack types and commit modes (fixed seed).
2553
+ *
2554
+ * Seeded PRNG so the sequence is fully reproducible across runs.
2555
+ * For each ACCEPTed/RELEASEd/REJECTed record we may immediately issue
2556
+ * a commit (sync or async), with the choices driven by the PRNG.
2557
+ * Verifies that the lib survives a realistic mixed workload and that
2558
+ * ACCEPT'd offsets eventually flow through the ack callback without
2559
+ * errors.
2560
+ * =================================================================== */
2561
+ static void do_test_chaos_random_ack_random_commit(void) {
2562
+ const char *topic;
2563
+ const char *group = "commit-sync-chaos-rand";
2564
+ rd_kafka_share_t *rkshare;
2565
+ rd_kafka_error_t *error;
2566
+ rd_kafka_topic_partition_list_t *partitions = NULL;
2567
+ rd_kafka_messages_t *batch = NULL;
2568
+ size_t rcvd;
2569
+ size_t j;
2570
+ int processed = 0;
2571
+ int accepted = 0;
2572
+ int released = 0;
2573
+ int rejected = 0;
2574
+ int attempts = 0;
2575
+ int sync_cnt = 0;
2576
+ int async_cnt = 0;
2577
+ const int produce_cnt = 80;
2578
+ const int target = 60; /* stop after this many processed */
2579
+ ack_cb_state_t state = {0};
2580
+
2581
+ SUB_TEST();
2582
+
2583
+ /* Fixed seed: this test must produce the same chaos sequence
2584
+ * across runs so a failure can be reproduced deterministically.
2585
+ * Seed is set before any rand() call inside this test; no earlier
2586
+ * test in main_0176_share_consumer_commit_sync calls rand(). */
2587
+ srand(0xC0FFEE);
2588
+
2589
+ topic = test_mk_topic_name("0176-cs-chaos-rand", 1);
2590
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
2591
+ test_produce_msgs_simple(common_producer, topic, 0, produce_cnt);
2592
+
2593
+ rkshare = test_create_share_consumer_with_cb(group, "explicit",
2594
+ &state.base, share_ack_cb);
2595
+ test_share_set_auto_offset_reset(group, "earliest");
2596
+ subscribe_consumer(rkshare, &topic, 1);
2597
+
2598
+ while (processed < target && attempts++ < 80) {
2599
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2600
+ if (error) {
2601
+ rd_kafka_error_destroy(error);
2602
+ continue;
2603
+ }
2604
+ rcvd = rd_kafka_messages_count(batch);
2605
+
2606
+ /* Drain the entire batch every iteration. Past `target`,
2607
+ * stop driving chaos but still ACCEPT every record so
2608
+ * nothing is left ACQUIRED in the lib's inflight map —
2609
+ * otherwise close/destroy would have to clean it up,
2610
+ * which the share consumer doesn't currently handle. */
2611
+ for (j = 0; j < rcvd; j++) {
2612
+ rd_kafka_share_AcknowledgeType_t at;
2613
+ rd_kafka_resp_err_t aerr;
2614
+ int r;
2615
+ rd_kafka_message_t *rkm =
2616
+ rd_kafka_messages_get(batch, j);
2617
+
2618
+ if (rkm->err)
2619
+ continue;
2620
+
2621
+ if (processed >= target) {
2622
+ /* Drain-only path: ACCEPT to release the
2623
+ * lock without affecting chaos stats. */
2624
+ aerr = rd_kafka_share_acknowledge_type(
2625
+ rkshare, rkm,
2626
+ RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT);
2627
+ TEST_ASSERT(!aerr, "drain ACCEPT failed: %s",
2628
+ rd_kafka_err2str(aerr));
2629
+ continue;
2630
+ }
2631
+
2632
+ r = rand() % 6;
2633
+ if (r < 4) {
2634
+ at = RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT;
2635
+ accepted++;
2636
+ } else if (r == 4) {
2637
+ at = RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE;
2638
+ released++;
2639
+ } else {
2640
+ at = RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_REJECT;
2641
+ rejected++;
2642
+ }
2643
+
2644
+ aerr =
2645
+ rd_kafka_share_acknowledge_type(rkshare, rkm, at);
2646
+ TEST_ASSERT(!aerr, "acknowledge_type(%d) failed: %s",
2647
+ (int)at, rd_kafka_err2str(aerr));
2648
+ processed++;
2649
+
2650
+ /* ~1 in 3 records: issue a commit. Sync vs async
2651
+ * decided by another PRNG draw. */
2652
+ if (rand() % 3 == 0) {
2653
+ if (rand() % 2 == 0) {
2654
+ error = rd_kafka_share_commit_sync(
2655
+ rkshare, 30000, &partitions);
2656
+ TEST_ASSERT(
2657
+ !error, "commit_sync #%d: %s",
2658
+ sync_cnt + 1,
2659
+ error ? rd_kafka_error_string(error)
2660
+ : "");
2661
+ RD_IF_FREE(
2662
+ partitions,
2663
+ rd_kafka_topic_partition_list_destroy);
2664
+ partitions = NULL;
2665
+ sync_cnt++;
2666
+ } else {
2667
+ error = rd_kafka_share_commit_async(
2668
+ rkshare);
2669
+ TEST_ASSERT(
2670
+ !error, "commit_async #%d: %s",
2671
+ async_cnt + 1,
2672
+ error ? rd_kafka_error_string(error)
2673
+ : "");
2674
+ async_cnt++;
2675
+ }
2676
+ }
2677
+ }
2678
+ rd_kafka_messages_destroy(batch);
2679
+ batch = NULL;
2680
+ }
2681
+
2682
+ /* Final flush. */
2683
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
2684
+ TEST_ASSERT(!error, "final commit_sync: %s",
2685
+ error ? rd_kafka_error_string(error) : "");
2686
+ RD_IF_FREE(partitions, rd_kafka_topic_partition_list_destroy);
2687
+ partitions = NULL;
2688
+ sync_cnt++;
2689
+
2690
+ test_wait_for_cb_with_poll(&state.base, rkshare, sync_cnt + async_cnt,
2691
+ 15000);
2692
+
2693
+ TEST_SAY(
2694
+ "chaos random: processed=%d accept=%d release=%d reject=%d "
2695
+ "sync=%d async=%d callbacks=%d offsets=%zu\n",
2696
+ processed, accepted, released, rejected, sync_cnt, async_cnt,
2697
+ state.base.callback_cnt, state.base.total_offsets);
2698
+
2699
+ TEST_ASSERT(state.base.callback_cnt >= 1,
2700
+ "Expected at least 1 ack callback, got %d",
2701
+ state.base.callback_cnt);
2702
+ TEST_ASSERT(
2703
+ test_ack_cb_state_first_err(&state.base) ==
2704
+ RD_KAFKA_RESP_ERR_NO_ERROR,
2705
+ "Ack callback reported err: %s",
2706
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
2707
+
2708
+ test_share_consumer_close(rkshare);
2709
+ test_share_destroy(rkshare);
2710
+ test_ack_cb_state_destroy(&state.base);
2711
+
2712
+ SUB_TEST_PASS();
2713
+ }
2714
+
2715
+
2716
+ /* ===================================================================
2717
+ * Chaos: burst of back-to-back commit_async then a flushing commit_sync.
2718
+ *
2719
+ * Consume 50 records. After every ACCEPT, fire 5 commit_async in a row
2720
+ * followed by 1 commit_sync. Stresses the in-flight async commit queue
2721
+ * and the path where commit_sync drains a backlog of pending acks.
2722
+ * =================================================================== */
2723
+ static void do_test_chaos_burst_commits(void) {
2724
+ const char *topic;
2725
+ const char *group = "commit-sync-chaos-burst";
2726
+ rd_kafka_share_t *rkshare;
2727
+ rd_kafka_error_t *error;
2728
+ rd_kafka_topic_partition_list_t *partitions = NULL;
2729
+ rd_kafka_messages_t *batch = NULL;
2730
+ size_t rcvd;
2731
+ size_t j;
2732
+ int consumed = 0;
2733
+ int attempts = 0;
2734
+ int sync_cnt = 0;
2735
+ int async_cnt = 0;
2736
+ int b;
2737
+ const int total_msgs = 50;
2738
+ const int burst_size = 5;
2739
+ ack_cb_state_t state = {0};
2740
+
2741
+ SUB_TEST();
2742
+
2743
+ topic = test_mk_topic_name("0176-cs-chaos-burst", 1);
2744
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
2745
+ test_produce_msgs_simple(common_producer, topic, 0, total_msgs);
2746
+
2747
+ rkshare = test_create_share_consumer_with_cb(group, "explicit",
2748
+ &state.base, share_ack_cb);
2749
+ test_share_set_auto_offset_reset(group, "earliest");
2750
+ subscribe_consumer(rkshare, &topic, 1);
2751
+
2752
+ while (consumed < total_msgs && attempts++ < 60) {
2753
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2754
+ if (error) {
2755
+ rd_kafka_error_destroy(error);
2756
+ continue;
2757
+ }
2758
+ rcvd = rd_kafka_messages_count(batch);
2759
+
2760
+ for (j = 0; j < rcvd && consumed < total_msgs; j++) {
2761
+ rd_kafka_message_t *rkm =
2762
+ rd_kafka_messages_get(batch, j);
2763
+ if (rkm->err)
2764
+ continue;
2765
+ rd_kafka_share_acknowledge(rkshare, rkm);
2766
+ consumed++;
2767
+
2768
+ for (b = 0; b < burst_size; b++) {
2769
+ error = rd_kafka_share_commit_async(rkshare);
2770
+ TEST_ASSERT(
2771
+ !error, "burst commit_async #%d: %s",
2772
+ async_cnt + 1,
2773
+ error ? rd_kafka_error_string(error) : "");
2774
+ async_cnt++;
2775
+ }
2776
+
2777
+ error = rd_kafka_share_commit_sync(rkshare, 30000,
2778
+ &partitions);
2779
+ TEST_ASSERT(!error, "burst commit_sync #%d: %s",
2780
+ sync_cnt + 1,
2781
+ error ? rd_kafka_error_string(error) : "");
2782
+ RD_IF_FREE(partitions,
2783
+ rd_kafka_topic_partition_list_destroy);
2784
+ partitions = NULL;
2785
+ sync_cnt++;
2786
+ }
2787
+ rd_kafka_messages_destroy(batch);
2788
+ batch = NULL;
2789
+ }
2790
+
2791
+ TEST_ASSERT(consumed == total_msgs, "Expected %d records, consumed %d",
2792
+ total_msgs, consumed);
2793
+
2794
+ test_wait_for_cb_with_poll(&state.base, rkshare, sync_cnt, 15000);
2795
+
2796
+ TEST_SAY(
2797
+ "chaos burst: consumed=%d sync=%d async=%d "
2798
+ "callbacks=%d offsets=%zu\n",
2799
+ consumed, sync_cnt, async_cnt, state.base.callback_cnt,
2800
+ state.base.total_offsets);
2801
+
2802
+ TEST_ASSERT(
2803
+ test_ack_cb_state_first_err(&state.base) ==
2804
+ RD_KAFKA_RESP_ERR_NO_ERROR,
2805
+ "Ack callback reported err: %s",
2806
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
2807
+
2808
+ test_share_consumer_close(rkshare);
2809
+ test_share_destroy(rkshare);
2810
+ test_ack_cb_state_destroy(&state.base);
2811
+
2812
+ SUB_TEST_PASS();
2813
+ }
2814
+
2815
+
2816
+ /* ===================================================================
2817
+ * Chaos: callback-driven receipt accounting under varying batch sizes.
2818
+ *
2819
+ * Consume 60 records and commit with progressively varying batch sizes
2820
+ * (ack 1 -> commit, ack 2 -> commit, ack 3 -> commit, ...) alternating
2821
+ * between commit_sync and commit_async. After the run, verify the ack
2822
+ * callback received offset counts that account for all ACCEPTed records.
2823
+ * =================================================================== */
2824
+ static void do_test_chaos_callback_receipt_match(void) {
2825
+ const char *topic;
2826
+ const char *group = "commit-sync-chaos-receipts";
2827
+ rd_kafka_share_t *rkshare;
2828
+ rd_kafka_error_t *error;
2829
+ rd_kafka_topic_partition_list_t *partitions = NULL;
2830
+ rd_kafka_messages_t *batch = NULL;
2831
+ size_t rcvd;
2832
+ size_t j;
2833
+ int consumed = 0;
2834
+ int attempts = 0;
2835
+ int acked_since_commit = 0;
2836
+ int batch_target = 1;
2837
+ int sync_cnt = 0;
2838
+ int async_cnt = 0;
2839
+ const int total_msgs = 60;
2840
+ ack_cb_state_t state = {0};
2841
+
2842
+ SUB_TEST();
2843
+
2844
+ topic = test_mk_topic_name("0176-cs-chaos-receipts", 1);
2845
+ test_create_topic_wait_exists(common_admin, topic, 1, -1, 60 * 1000);
2846
+ test_produce_msgs_simple(common_producer, topic, 0, total_msgs);
2847
+
2848
+ rkshare = test_create_share_consumer_with_cb(group, "explicit",
2849
+ &state.base, share_ack_cb);
2850
+ test_share_set_auto_offset_reset(group, "earliest");
2851
+ subscribe_consumer(rkshare, &topic, 1);
2852
+
2853
+ while (consumed < total_msgs && attempts++ < 60) {
2854
+ error = rd_kafka_share_poll(rkshare, 3000, &batch);
2855
+ if (error) {
2856
+ rd_kafka_error_destroy(error);
2857
+ continue;
2858
+ }
2859
+ rcvd = rd_kafka_messages_count(batch);
2860
+
2861
+ for (j = 0; j < rcvd && consumed < total_msgs; j++) {
2862
+ rd_kafka_message_t *rkm =
2863
+ rd_kafka_messages_get(batch, j);
2864
+ if (rkm->err)
2865
+ continue;
2866
+ rd_kafka_share_acknowledge(rkshare, rkm);
2867
+ consumed++;
2868
+ acked_since_commit++;
2869
+
2870
+ if (acked_since_commit < batch_target)
2871
+ continue;
2872
+
2873
+ /* Alternate sync (odd batch idx) / async (even). */
2874
+ if ((sync_cnt + async_cnt) % 2 == 0) {
2875
+ error = rd_kafka_share_commit_sync(
2876
+ rkshare, 30000, &partitions);
2877
+ TEST_ASSERT(
2878
+ !error, "receipts commit_sync #%d: %s",
2879
+ sync_cnt + 1,
2880
+ error ? rd_kafka_error_string(error) : "");
2881
+ RD_IF_FREE(
2882
+ partitions,
2883
+ rd_kafka_topic_partition_list_destroy);
2884
+ partitions = NULL;
2885
+ sync_cnt++;
2886
+ } else {
2887
+ error = rd_kafka_share_commit_async(rkshare);
2888
+ TEST_ASSERT(
2889
+ !error, "receipts commit_async #%d: %s",
2890
+ async_cnt + 1,
2891
+ error ? rd_kafka_error_string(error) : "");
2892
+ async_cnt++;
2893
+ }
2894
+ acked_since_commit = 0;
2895
+ batch_target = (batch_target % 5) + 1;
2896
+ }
2897
+ rd_kafka_messages_destroy(batch);
2898
+ batch = NULL;
2899
+ }
2900
+
2901
+ /* Drain any leftover acked-but-not-committed records. */
2902
+ if (acked_since_commit > 0) {
2903
+ error = rd_kafka_share_commit_sync(rkshare, 30000, &partitions);
2904
+ TEST_ASSERT(!error, "final commit_sync: %s",
2905
+ error ? rd_kafka_error_string(error) : "");
2906
+ RD_IF_FREE(partitions, rd_kafka_topic_partition_list_destroy);
2907
+ partitions = NULL;
2908
+ sync_cnt++;
2909
+ }
2910
+
2911
+ TEST_ASSERT(consumed == total_msgs, "Expected %d records, consumed %d",
2912
+ total_msgs, consumed);
2913
+
2914
+ test_wait_for_cb_with_poll(&state.base, rkshare, sync_cnt + async_cnt,
2915
+ 15000);
2916
+
2917
+ TEST_SAY(
2918
+ "chaos receipts: consumed=%d sync=%d async=%d "
2919
+ "callbacks=%d offsets=%zu\n",
2920
+ consumed, sync_cnt, async_cnt, state.base.callback_cnt,
2921
+ state.base.total_offsets);
2922
+
2923
+ TEST_ASSERT(state.base.callback_cnt >= 1,
2924
+ "Expected at least 1 ack callback, got %d",
2925
+ state.base.callback_cnt);
2926
+ TEST_ASSERT(state.base.total_offsets >= (size_t)consumed,
2927
+ "Expected callback to surface at least %d offsets, got %zu",
2928
+ consumed, state.base.total_offsets);
2929
+ TEST_ASSERT(
2930
+ test_ack_cb_state_first_err(&state.base) ==
2931
+ RD_KAFKA_RESP_ERR_NO_ERROR,
2932
+ "Ack callback reported err: %s",
2933
+ rd_kafka_err2name(test_ack_cb_state_first_err(&state.base)));
2934
+
2935
+ test_share_consumer_close(rkshare);
2936
+ test_share_destroy(rkshare);
2937
+ test_ack_cb_state_destroy(&state.base);
2938
+
2939
+ SUB_TEST_PASS();
2940
+ }
2941
+
2942
+
2943
+ int main_0176_share_consumer_commit_sync(int argc, char **argv) {
2944
+ test_timeout_set(120);
2945
+ common_producer = test_create_producer();
2946
+ common_admin = test_create_producer();
2947
+
2948
+ /* Real broker tests */
2949
+ do_test_basic_implicit_commit_sync();
2950
+ do_test_basic_explicit_commit_sync();
2951
+ do_test_no_pending_acks();
2952
+ do_test_commit_sync_prevents_redelivery();
2953
+ do_test_mixed_ack_types();
2954
+ do_test_multiple_commit_sync_calls();
2955
+ do_test_multi_topic_partition();
2956
+ do_test_mixed_commit_types();
2957
+
2958
+ /* Callback test */
2959
+ do_test_commit_sync_callback();
2960
+
2961
+ /* commit_sync after subscribed topic was deleted */
2962
+ do_test_commit_sync_after_topic_deletion();
2963
+
2964
+ /* Chaos: interleaved ack / commit patterns. */
2965
+ do_test_chaos_111_ack_commit_interleave();
2966
+ do_test_chaos_alternating_commits();
2967
+ do_test_chaos_random_ack_random_commit();
2968
+ do_test_chaos_burst_commits();
2969
+ do_test_chaos_callback_receipt_match();
2970
+
2971
+ rd_kafka_destroy(common_admin);
2972
+ rd_kafka_destroy(common_producer);
2973
+
2974
+ return 0;
2975
+ }
2976
+
2977
+ int main_0176_share_consumer_commit_sync_local(int argc, char **argv) {
2978
+ /* Mock broker tests only (no real broker needed) */
2979
+ TEST_SKIP_MOCK_CLUSTER(0);
2980
+
2981
+ do_test_mock_uses_share_acknowledge();
2982
+ do_test_mock_commit_sync_timeout();
2983
+ do_test_mock_broker_dispatch_priority();
2984
+
2985
+ return 0;
2986
+ }