karafka 2.4.0 → 2.4.18

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 (326) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +26 -34
  4. data/.github/workflows/ci.yml +18 -6
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +146 -1
  7. data/Gemfile +10 -5
  8. data/Gemfile.lock +60 -39
  9. data/LICENSE +8 -3
  10. data/bin/integrations +13 -1
  11. data/certs/cert.pem +26 -0
  12. data/config/locales/errors.yml +18 -2
  13. data/config/locales/pro_errors.yml +44 -0
  14. data/docker-compose.yml +1 -3
  15. data/karafka.gemspec +6 -4
  16. data/lib/active_job/queue_adapters/karafka_adapter.rb +18 -7
  17. data/lib/karafka/active_job/dispatcher.rb +13 -0
  18. data/lib/karafka/active_job/job_extensions.rb +3 -0
  19. data/lib/karafka/admin.rb +86 -0
  20. data/lib/karafka/app.rb +17 -0
  21. data/lib/karafka/base_consumer.rb +130 -19
  22. data/lib/karafka/cli/base.rb +24 -8
  23. data/lib/karafka/cli/install.rb +2 -1
  24. data/lib/karafka/cli/server.rb +1 -0
  25. data/lib/karafka/cli/swarm.rb +1 -0
  26. data/lib/karafka/cli/topics/align.rb +12 -2
  27. data/lib/karafka/cli/topics/plan.rb +54 -6
  28. data/lib/karafka/cli/topics.rb +45 -18
  29. data/lib/karafka/connection/client.rb +102 -35
  30. data/lib/karafka/connection/listener.rb +48 -11
  31. data/lib/karafka/connection/messages_buffer.rb +19 -6
  32. data/lib/karafka/connection/proxy.rb +3 -0
  33. data/lib/karafka/connection/raw_messages_buffer.rb +43 -9
  34. data/lib/karafka/connection/rebalance_manager.rb +24 -13
  35. data/lib/karafka/contracts/config.rb +4 -0
  36. data/lib/karafka/contracts/consumer_group.rb +17 -0
  37. data/lib/karafka/contracts/routing.rb +59 -0
  38. data/lib/karafka/contracts/topic.rb +14 -0
  39. data/lib/karafka/embedded.rb +46 -3
  40. data/lib/karafka/errors.rb +3 -2
  41. data/lib/karafka/helpers/async.rb +11 -2
  42. data/lib/karafka/helpers/config_importer.rb +13 -0
  43. data/lib/karafka/instrumentation/assignments_tracker.rb +7 -2
  44. data/lib/karafka/instrumentation/logger_listener.rb +45 -4
  45. data/lib/karafka/instrumentation/notifications.rb +12 -0
  46. data/lib/karafka/instrumentation/vendors/appsignal/client.rb +32 -11
  47. data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +1 -1
  48. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +3 -1
  49. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +17 -19
  50. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +27 -18
  51. data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +2 -2
  52. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +41 -13
  53. data/lib/karafka/messages/message.rb +9 -9
  54. data/lib/karafka/pro/active_job/consumer.rb +2 -10
  55. data/lib/karafka/pro/active_job/dispatcher.rb +67 -19
  56. data/lib/karafka/pro/active_job/job_options_contract.rb +12 -10
  57. data/lib/karafka/pro/base_consumer.rb +2 -10
  58. data/lib/karafka/pro/cleaner/errors.rb +2 -10
  59. data/lib/karafka/pro/cleaner/messages/message.rb +14 -12
  60. data/lib/karafka/pro/cleaner/messages/messages.rb +2 -10
  61. data/lib/karafka/pro/cleaner/messages/metadata.rb +41 -0
  62. data/lib/karafka/pro/cleaner.rb +3 -10
  63. data/lib/karafka/pro/connection/manager.rb +6 -10
  64. data/lib/karafka/pro/connection/multiplexing/listener.rb +2 -10
  65. data/lib/karafka/pro/contracts/base.rb +2 -10
  66. data/lib/karafka/pro/contracts/server_cli_options.rb +2 -10
  67. data/lib/karafka/pro/encryption/cipher.rb +2 -10
  68. data/lib/karafka/pro/encryption/contracts/config.rb +2 -10
  69. data/lib/karafka/pro/encryption/errors.rb +2 -10
  70. data/lib/karafka/pro/encryption/messages/middleware.rb +2 -10
  71. data/lib/karafka/pro/encryption/messages/parser.rb +2 -10
  72. data/lib/karafka/pro/encryption/setup/config.rb +2 -10
  73. data/lib/karafka/pro/encryption.rb +2 -10
  74. data/lib/karafka/pro/instrumentation/performance_tracker.rb +2 -10
  75. data/lib/karafka/pro/iterator/expander.rb +2 -10
  76. data/lib/karafka/pro/iterator/tpl_builder.rb +2 -10
  77. data/lib/karafka/pro/iterator.rb +2 -10
  78. data/lib/karafka/pro/loader.rb +5 -11
  79. data/lib/karafka/pro/processing/adaptive_iterator/consumer.rb +54 -0
  80. data/lib/karafka/pro/processing/adaptive_iterator/tracker.rb +67 -0
  81. data/lib/karafka/pro/processing/collapser.rb +2 -10
  82. data/lib/karafka/pro/processing/coordinator.rb +2 -10
  83. data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +2 -10
  84. data/lib/karafka/pro/processing/coordinators/filters_applier.rb +19 -10
  85. data/lib/karafka/pro/processing/coordinators/virtual_offset_manager.rb +2 -10
  86. data/lib/karafka/pro/processing/executor.rb +2 -10
  87. data/lib/karafka/pro/processing/expansions_selector.rb +3 -10
  88. data/lib/karafka/pro/processing/filters/base.rb +14 -10
  89. data/lib/karafka/pro/processing/filters/delayer.rb +4 -12
  90. data/lib/karafka/pro/processing/filters/expirer.rb +2 -10
  91. data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +2 -10
  92. data/lib/karafka/pro/processing/filters/throttler.rb +2 -10
  93. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +2 -10
  94. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +4 -10
  95. data/lib/karafka/pro/processing/jobs/eofed_non_blocking.rb +26 -0
  96. data/lib/karafka/pro/processing/jobs/periodic.rb +4 -10
  97. data/lib/karafka/pro/processing/jobs/periodic_non_blocking.rb +4 -10
  98. data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +4 -10
  99. data/lib/karafka/pro/processing/jobs_builder.rb +14 -10
  100. data/lib/karafka/pro/processing/jobs_queue.rb +2 -10
  101. data/lib/karafka/pro/processing/offset_metadata/consumer.rb +2 -10
  102. data/lib/karafka/pro/processing/offset_metadata/fetcher.rb +2 -10
  103. data/lib/karafka/pro/processing/offset_metadata/listener.rb +2 -10
  104. data/lib/karafka/pro/processing/partitioner.rb +35 -24
  105. data/lib/karafka/pro/processing/periodic_job/consumer.rb +2 -10
  106. data/lib/karafka/pro/processing/piping/consumer.rb +2 -10
  107. data/lib/karafka/pro/processing/schedulers/base.rb +2 -10
  108. data/lib/karafka/pro/processing/schedulers/default.rb +3 -10
  109. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +3 -11
  110. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +3 -11
  111. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +2 -10
  112. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +2 -10
  113. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +3 -11
  114. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +3 -11
  115. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +2 -10
  116. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +2 -10
  117. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +2 -10
  118. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +3 -11
  119. data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +2 -10
  120. data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +2 -10
  121. data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +2 -10
  122. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +5 -13
  123. data/lib/karafka/pro/processing/strategies/aj/mom.rb +2 -10
  124. data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +2 -10
  125. data/lib/karafka/pro/processing/strategies/base.rb +2 -10
  126. data/lib/karafka/pro/processing/strategies/default.rb +140 -58
  127. data/lib/karafka/pro/processing/strategies/dlq/default.rb +23 -15
  128. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +2 -10
  129. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +3 -11
  130. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +7 -11
  131. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +2 -10
  132. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +2 -10
  133. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +19 -11
  134. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +2 -10
  135. data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +2 -10
  136. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +3 -11
  137. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +19 -11
  138. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +2 -10
  139. data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +2 -10
  140. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +24 -16
  141. data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +2 -10
  142. data/lib/karafka/pro/processing/strategies/dlq/vp.rb +2 -10
  143. data/lib/karafka/pro/processing/strategies/ftr/default.rb +17 -12
  144. data/lib/karafka/pro/processing/strategies/ftr/vp.rb +2 -10
  145. data/lib/karafka/pro/processing/strategies/lrj/default.rb +5 -13
  146. data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +3 -11
  147. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +2 -10
  148. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +2 -10
  149. data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +2 -10
  150. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +4 -12
  151. data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +2 -10
  152. data/lib/karafka/pro/processing/strategies/lrj/vp.rb +2 -10
  153. data/lib/karafka/pro/processing/strategies/mom/default.rb +2 -10
  154. data/lib/karafka/pro/processing/strategies/mom/ftr.rb +2 -10
  155. data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +2 -10
  156. data/lib/karafka/pro/processing/strategies/mom/vp.rb +2 -10
  157. data/lib/karafka/pro/processing/strategies/vp/default.rb +5 -10
  158. data/lib/karafka/pro/processing/strategies.rb +2 -10
  159. data/lib/karafka/pro/processing/strategy_selector.rb +2 -10
  160. data/lib/karafka/pro/processing/subscription_groups_coordinator.rb +2 -10
  161. data/lib/karafka/pro/recurring_tasks/consumer.rb +97 -0
  162. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +45 -0
  163. data/lib/karafka/pro/recurring_tasks/contracts/task.rb +33 -0
  164. data/lib/karafka/pro/recurring_tasks/deserializer.rb +27 -0
  165. data/lib/karafka/pro/recurring_tasks/dispatcher.rb +79 -0
  166. data/lib/karafka/pro/recurring_tasks/errors.rb +26 -0
  167. data/lib/karafka/pro/recurring_tasks/executor.rb +144 -0
  168. data/lib/karafka/pro/recurring_tasks/listener.rb +30 -0
  169. data/lib/karafka/pro/recurring_tasks/matcher.rb +30 -0
  170. data/lib/karafka/pro/recurring_tasks/schedule.rb +55 -0
  171. data/lib/karafka/pro/recurring_tasks/serializer.rb +105 -0
  172. data/lib/karafka/pro/recurring_tasks/setup/config.rb +44 -0
  173. data/lib/karafka/pro/recurring_tasks/task.rb +143 -0
  174. data/lib/karafka/pro/recurring_tasks.rb +79 -0
  175. data/lib/karafka/pro/routing/features/active_job/builder.rb +2 -10
  176. data/lib/karafka/pro/routing/features/active_job.rb +2 -10
  177. data/lib/karafka/pro/routing/features/adaptive_iterator/config.rb +26 -0
  178. data/lib/karafka/pro/routing/features/adaptive_iterator/contracts/topic.rb +66 -0
  179. data/lib/karafka/pro/routing/features/adaptive_iterator/topic.rb +54 -0
  180. data/lib/karafka/pro/routing/features/adaptive_iterator.rb +23 -0
  181. data/lib/karafka/pro/routing/features/base.rb +2 -10
  182. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +2 -10
  183. data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +2 -10
  184. data/lib/karafka/pro/routing/features/dead_letter_queue.rb +2 -10
  185. data/lib/karafka/pro/routing/features/delaying/config.rb +2 -10
  186. data/lib/karafka/pro/routing/features/delaying/contracts/topic.rb +2 -10
  187. data/lib/karafka/pro/routing/features/delaying/topic.rb +2 -10
  188. data/lib/karafka/pro/routing/features/delaying.rb +2 -10
  189. data/lib/karafka/pro/routing/features/direct_assignments/config.rb +2 -10
  190. data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +2 -10
  191. data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +2 -10
  192. data/lib/karafka/pro/routing/features/direct_assignments/subscription_group.rb +2 -10
  193. data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +2 -10
  194. data/lib/karafka/pro/routing/features/direct_assignments.rb +2 -10
  195. data/lib/karafka/pro/routing/features/expiring/config.rb +2 -10
  196. data/lib/karafka/pro/routing/features/expiring/contracts/topic.rb +2 -10
  197. data/lib/karafka/pro/routing/features/expiring/topic.rb +2 -10
  198. data/lib/karafka/pro/routing/features/expiring.rb +2 -10
  199. data/lib/karafka/pro/routing/features/filtering/config.rb +2 -10
  200. data/lib/karafka/pro/routing/features/filtering/contracts/topic.rb +2 -10
  201. data/lib/karafka/pro/routing/features/filtering/topic.rb +2 -10
  202. data/lib/karafka/pro/routing/features/filtering.rb +2 -10
  203. data/lib/karafka/pro/routing/features/inline_insights/config.rb +2 -10
  204. data/lib/karafka/pro/routing/features/inline_insights/contracts/topic.rb +2 -10
  205. data/lib/karafka/pro/routing/features/inline_insights/topic.rb +2 -10
  206. data/lib/karafka/pro/routing/features/inline_insights.rb +2 -10
  207. data/lib/karafka/pro/routing/features/long_running_job/config.rb +2 -10
  208. data/lib/karafka/pro/routing/features/long_running_job/contracts/topic.rb +2 -10
  209. data/lib/karafka/pro/routing/features/long_running_job/topic.rb +2 -10
  210. data/lib/karafka/pro/routing/features/long_running_job.rb +2 -10
  211. data/lib/karafka/pro/routing/features/multiplexing/config.rb +2 -10
  212. data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +2 -10
  213. data/lib/karafka/pro/routing/features/multiplexing/patches/contracts/consumer_group.rb +2 -10
  214. data/lib/karafka/pro/routing/features/multiplexing/proxy.rb +2 -10
  215. data/lib/karafka/pro/routing/features/multiplexing/subscription_group.rb +2 -10
  216. data/lib/karafka/pro/routing/features/multiplexing/subscription_groups_builder.rb +2 -10
  217. data/lib/karafka/pro/routing/features/multiplexing.rb +2 -10
  218. data/lib/karafka/pro/routing/features/non_blocking_job/topic.rb +2 -10
  219. data/lib/karafka/pro/routing/features/non_blocking_job.rb +2 -10
  220. data/lib/karafka/pro/routing/features/offset_metadata/config.rb +2 -10
  221. data/lib/karafka/pro/routing/features/offset_metadata/contracts/topic.rb +2 -10
  222. data/lib/karafka/pro/routing/features/offset_metadata/topic.rb +2 -10
  223. data/lib/karafka/pro/routing/features/offset_metadata.rb +2 -10
  224. data/lib/karafka/pro/routing/features/patterns/builder.rb +2 -10
  225. data/lib/karafka/pro/routing/features/patterns/config.rb +2 -10
  226. data/lib/karafka/pro/routing/features/patterns/consumer_group.rb +2 -10
  227. data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +2 -10
  228. data/lib/karafka/pro/routing/features/patterns/contracts/pattern.rb +2 -10
  229. data/lib/karafka/pro/routing/features/patterns/contracts/topic.rb +2 -10
  230. data/lib/karafka/pro/routing/features/patterns/detector.rb +2 -10
  231. data/lib/karafka/pro/routing/features/patterns/pattern.rb +2 -10
  232. data/lib/karafka/pro/routing/features/patterns/patterns.rb +2 -10
  233. data/lib/karafka/pro/routing/features/patterns/topic.rb +2 -10
  234. data/lib/karafka/pro/routing/features/patterns/topics.rb +2 -10
  235. data/lib/karafka/pro/routing/features/patterns.rb +2 -10
  236. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +2 -10
  237. data/lib/karafka/pro/routing/features/pausing/topic.rb +2 -10
  238. data/lib/karafka/pro/routing/features/pausing.rb +2 -10
  239. data/lib/karafka/pro/routing/features/periodic_job/config.rb +2 -10
  240. data/lib/karafka/pro/routing/features/periodic_job/contracts/topic.rb +2 -10
  241. data/lib/karafka/pro/routing/features/periodic_job/topic.rb +2 -10
  242. data/lib/karafka/pro/routing/features/periodic_job.rb +2 -10
  243. data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +123 -0
  244. data/lib/karafka/pro/routing/features/recurring_tasks/config.rb +20 -0
  245. data/lib/karafka/pro/routing/features/recurring_tasks/contracts/topic.rb +32 -0
  246. data/lib/karafka/pro/routing/features/recurring_tasks/proxy.rb +19 -0
  247. data/lib/karafka/pro/routing/features/recurring_tasks/topic.rb +36 -0
  248. data/lib/karafka/pro/routing/features/recurring_tasks.rb +17 -0
  249. data/lib/karafka/pro/routing/features/scheduled_messages/builder.rb +123 -0
  250. data/lib/karafka/pro/routing/features/scheduled_messages/config.rb +20 -0
  251. data/lib/karafka/pro/routing/features/scheduled_messages/contracts/topic.rb +32 -0
  252. data/lib/karafka/pro/routing/features/scheduled_messages/proxy.rb +19 -0
  253. data/lib/karafka/pro/routing/features/scheduled_messages/topic.rb +36 -0
  254. data/lib/karafka/pro/routing/features/scheduled_messages.rb +16 -0
  255. data/lib/karafka/pro/routing/features/swarm/config.rb +2 -10
  256. data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +2 -10
  257. data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +2 -10
  258. data/lib/karafka/pro/routing/features/swarm/topic.rb +2 -10
  259. data/lib/karafka/pro/routing/features/swarm.rb +2 -10
  260. data/lib/karafka/pro/routing/features/throttling/config.rb +2 -10
  261. data/lib/karafka/pro/routing/features/throttling/contracts/topic.rb +2 -10
  262. data/lib/karafka/pro/routing/features/throttling/topic.rb +2 -10
  263. data/lib/karafka/pro/routing/features/throttling.rb +2 -10
  264. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +3 -10
  265. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +3 -10
  266. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +10 -12
  267. data/lib/karafka/pro/routing/features/virtual_partitions.rb +2 -10
  268. data/lib/karafka/pro/scheduled_messages/consumer.rb +177 -0
  269. data/lib/karafka/pro/scheduled_messages/contracts/config.rb +48 -0
  270. data/lib/karafka/pro/scheduled_messages/contracts/message.rb +88 -0
  271. data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +71 -0
  272. data/lib/karafka/pro/scheduled_messages/day.rb +37 -0
  273. data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +38 -0
  274. data/lib/karafka/pro/scheduled_messages/deserializers/payload.rb +27 -0
  275. data/lib/karafka/pro/scheduled_messages/dispatcher.rb +114 -0
  276. data/lib/karafka/pro/scheduled_messages/errors.rb +20 -0
  277. data/lib/karafka/pro/scheduled_messages/max_epoch.rb +33 -0
  278. data/lib/karafka/pro/scheduled_messages/proxy.rb +177 -0
  279. data/lib/karafka/pro/scheduled_messages/schema_validator.rb +29 -0
  280. data/lib/karafka/pro/scheduled_messages/serializer.rb +47 -0
  281. data/lib/karafka/pro/scheduled_messages/setup/config.rb +52 -0
  282. data/lib/karafka/pro/scheduled_messages/state.rb +54 -0
  283. data/lib/karafka/pro/scheduled_messages/tracker.rb +56 -0
  284. data/lib/karafka/pro/scheduled_messages.rb +59 -0
  285. data/lib/karafka/pro/swarm/liveness_listener.rb +2 -10
  286. data/lib/karafka/processing/coordinator.rb +14 -0
  287. data/lib/karafka/processing/executor.rb +29 -1
  288. data/lib/karafka/processing/jobs/base.rb +13 -0
  289. data/lib/karafka/processing/jobs/consume.rb +2 -0
  290. data/lib/karafka/processing/jobs/eofed.rb +29 -0
  291. data/lib/karafka/processing/jobs/idle.rb +2 -0
  292. data/lib/karafka/processing/jobs/revoked.rb +2 -0
  293. data/lib/karafka/processing/jobs/shutdown.rb +2 -0
  294. data/lib/karafka/processing/jobs_builder.rb +6 -0
  295. data/lib/karafka/processing/schedulers/default.rb +1 -0
  296. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +1 -1
  297. data/lib/karafka/processing/strategies/default.rb +45 -13
  298. data/lib/karafka/processing/strategies/dlq.rb +19 -5
  299. data/lib/karafka/processing/strategies/dlq_mom.rb +27 -8
  300. data/lib/karafka/processing/worker.rb +26 -13
  301. data/lib/karafka/railtie.rb +11 -42
  302. data/lib/karafka/routing/builder.rb +19 -1
  303. data/lib/karafka/routing/consumer_group.rb +9 -14
  304. data/lib/karafka/routing/features/dead_letter_queue/config.rb +3 -0
  305. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
  306. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +7 -2
  307. data/lib/karafka/routing/features/eofed/config.rb +15 -0
  308. data/lib/karafka/routing/features/eofed/contracts/topic.rb +39 -0
  309. data/lib/karafka/routing/features/eofed/topic.rb +31 -0
  310. data/lib/karafka/routing/features/eofed.rb +14 -0
  311. data/lib/karafka/routing/subscription_group.rb +29 -1
  312. data/lib/karafka/routing/topic.rb +24 -1
  313. data/lib/karafka/runner.rb +10 -9
  314. data/lib/karafka/server.rb +37 -1
  315. data/lib/karafka/setup/attributes_map.rb +11 -4
  316. data/lib/karafka/setup/config.rb +11 -52
  317. data/lib/karafka/setup/defaults_injector.rb +64 -0
  318. data/lib/karafka/swarm/node.rb +2 -0
  319. data/lib/karafka/swarm/supervisor.rb +11 -2
  320. data/lib/karafka/templates/karafka.rb.erb +2 -2
  321. data/lib/karafka/version.rb +1 -1
  322. data/lib/karafka.rb +47 -7
  323. data.tar.gz.sig +0 -0
  324. metadata +116 -33
  325. metadata.gz.sig +0 -0
  326. data/certs/cert_chain.pem +0 -26
@@ -41,22 +41,38 @@ module Karafka
41
41
  class << self
42
42
  # Loads proper environment with what is needed to run the CLI
43
43
  def load
44
+ rails_env_rb = File.join(Dir.pwd, 'config/environment.rb')
45
+ is_rails = Kernel.const_defined?(:Rails) && File.exist?(rails_env_rb)
46
+
47
+ # If the boot file is disabled and this is a Rails app, we assume that user moved the
48
+ # karafka app configuration to initializers or other Rails loading related place.
49
+ # It is not recommended but some users tend to do this. In such cases we just try to load
50
+ # the Rails stuff hoping that it will also load Karafka stuff
51
+ if Karafka.boot_file.to_s == 'false' && is_rails
52
+ require rails_env_rb
53
+
54
+ return
55
+ end
56
+
44
57
  # If there is a boot file, we need to require it as we expect it to contain
45
58
  # Karafka app setup, routes, etc
46
59
  if File.exist?(::Karafka.boot_file)
47
- rails_env_rb = File.join(Dir.pwd, 'config/environment.rb')
48
-
49
60
  # Load Rails environment file that starts Rails, so we can reference consumers and
50
61
  # other things from `karafka.rb` file. This will work only for Rails, for non-rails
51
62
  # a manual setup is needed
52
- require rails_env_rb if Kernel.const_defined?(:Rails) && File.exist?(rails_env_rb)
53
-
63
+ require rails_env_rb if is_rails
54
64
  require Karafka.boot_file.to_s
65
+
66
+ return
67
+ end
68
+
55
69
  # However when it is unavailable, we still want to be able to run help command
56
70
  # and install command as they don't require configured app itself to run
57
- elsif %w[-h install].none? { |cmd| cmd == ARGV[0] }
58
- raise ::Karafka::Errors::MissingBootFileError, ::Karafka.boot_file
59
- end
71
+ return if %w[-h install].any? { |cmd| cmd == ARGV[0] }
72
+
73
+ # All other commands except help and install do require an existing boot file if it was
74
+ # declared
75
+ raise ::Karafka::Errors::MissingBootFileError, ::Karafka.boot_file
60
76
  end
61
77
 
62
78
  # Allows to set options for Thor cli
@@ -96,7 +112,7 @@ module Karafka
96
112
  *[names, option[2], option[1]].flatten
97
113
  ) { |value| options[option[0]] = value }
98
114
  end
99
- end.parse!
115
+ end.parse(ARGV)
100
116
 
101
117
  options
102
118
  end
@@ -21,7 +21,7 @@ module Karafka
21
21
 
22
22
  # Where should we map proper files from templates
23
23
  INSTALL_FILES_MAP = {
24
- 'karafka.rb.erb' => Karafka.boot_file.basename,
24
+ 'karafka.rb.erb' => Karafka.boot_file,
25
25
  'application_consumer.rb.erb' => 'app/consumers/application_consumer.rb',
26
26
  'example_consumer.rb.erb' => 'app/consumers/example_consumer.rb'
27
27
  }.freeze
@@ -51,6 +51,7 @@ module Karafka
51
51
 
52
52
  INSTALL_FILES_MAP.each do |source, target|
53
53
  pathed_target = Karafka.root.join(target)
54
+ FileUtils.mkdir_p File.dirname(pathed_target)
54
55
 
55
56
  template = File.read(Karafka.core_root.join("templates/#{source}"))
56
57
  render = ::ERB.new(template, trim_mode: '-').result(binding)
@@ -89,6 +89,7 @@ module Karafka
89
89
  register_inclusions
90
90
  register_exclusions
91
91
 
92
+ Karafka::Server.execution_mode = :standalone
92
93
  Karafka::Server.run
93
94
  end
94
95
 
@@ -23,6 +23,7 @@ module Karafka
23
23
  server.register_inclusions
24
24
  server.register_exclusions
25
25
 
26
+ Karafka::Server.execution_mode = :supervisor
26
27
  Karafka::Swarm::Supervisor.new.run
27
28
  end
28
29
  end
@@ -89,9 +89,19 @@ module Karafka
89
89
  desired_configs.transform_keys!(&:to_s)
90
90
 
91
91
  topic_with_configs.configs.each do |config|
92
- next unless desired_configs.key?(config.name)
92
+ names = config.synonyms.map(&:name) << config.name
93
93
 
94
- desired_config = desired_configs.fetch(config.name)
94
+ # We move forward only if given topic config is for altering
95
+ next if (desired_configs.keys & names).empty?
96
+
97
+ desired_config = nil
98
+
99
+ # We then find last defined value in our configs for a given attribute
100
+ # Since attributes can have synonyms, we select last one, which will represent the
101
+ # last defined value in case someone defined same multiple times
102
+ desired_configs.each do |name, value|
103
+ desired_config = value if names.include?(name)
104
+ end
95
105
 
96
106
  # Do not migrate if existing and desired values are the same
97
107
  next if desired_config == config.value
@@ -16,7 +16,10 @@ module Karafka
16
16
  return false
17
17
  end
18
18
 
19
+ changes = false
20
+
19
21
  unless topics_to_create.empty?
22
+ changes = true
20
23
  puts 'Following topics will be created:'
21
24
  puts
22
25
 
@@ -33,20 +36,54 @@ module Karafka
33
36
  end
34
37
 
35
38
  unless topics_to_repartition.empty?
36
- puts 'Following topics will be repartitioned:'
37
- puts
39
+ upscale = {}
40
+ downscale = {}
38
41
 
39
42
  topics_to_repartition.each do |topic, partitions|
40
43
  from = partitions
41
44
  to = topic.declaratives.partitions
42
45
 
43
- puts " #{yellow('~')} #{topic.name}:"
44
- puts " #{yellow('~')} partitions: \"#{red(from)}\" #{grey('=>')} \"#{green(to)}\""
46
+ if from < to
47
+ upscale[topic] = partitions
48
+ else
49
+ downscale[topic] = partitions
50
+ end
51
+ end
52
+
53
+ unless upscale.empty?
54
+ changes = true
55
+ puts 'Following topics will be repartitioned:'
56
+ puts
57
+
58
+ upscale.each do |topic, partitions|
59
+ from = partitions
60
+ to = topic.declaratives.partitions
61
+ y = yellow('~')
62
+ puts " #{y} #{topic.name}:"
63
+ puts " #{y} partitions: \"#{red(from)}\" #{grey('=>')} \"#{green(to)}\""
64
+ puts
65
+ end
66
+ end
67
+
68
+ unless downscale.empty?
69
+ puts(
70
+ 'Following topics repartitioning will be ignored as downscaling is not supported:'
71
+ )
45
72
  puts
73
+
74
+ downscale.each do |topic, partitions|
75
+ from = partitions
76
+ to = topic.declaratives.partitions
77
+
78
+ puts " #{grey('~')} #{topic.name}:"
79
+ puts " #{grey('~')} partitions: \"#{grey(from)}\" #{grey('=>')} \"#{grey(to)}\""
80
+ puts
81
+ end
46
82
  end
47
83
  end
48
84
 
49
85
  unless topics_to_alter.empty?
86
+ changes = true
50
87
  puts 'Following topics will have configuration changes:'
51
88
  puts
52
89
 
@@ -65,7 +102,7 @@ module Karafka
65
102
  end
66
103
  end
67
104
 
68
- true
105
+ changes
69
106
  end
70
107
 
71
108
  private
@@ -143,14 +180,25 @@ module Karafka
143
180
  }
144
181
 
145
182
  scoped = @topics_to_alter[declarative][declarative_name]
183
+ # declarative name can be a synonym. In such cases we remap it during the discovery
184
+ # below
185
+ final_name = declarative_name
146
186
 
147
187
  topic_c.configs.each do |config|
148
- next unless declarative_name == config.name
188
+ names = config.synonyms.map(&:name) << config.name
149
189
 
190
+ next unless names.include?(declarative_name)
191
+
192
+ # Always use a non-synonym name if differs
193
+ final_name = config.name
150
194
  scoped[:action] = :change
151
195
  scoped[:from] = config.value
152
196
  end
153
197
 
198
+ # Aligns the name in case synonym was used
199
+ target = @topics_to_alter[declarative].delete(declarative_name)
200
+ @topics_to_alter[declarative][final_name] = target
201
+
154
202
  # Remove change definitions that would migrate to the same value as present
155
203
  @topics_to_alter[declarative].delete_if do |_name, details|
156
204
  details[:from] == details[:to]
@@ -10,26 +10,53 @@ module Karafka
10
10
  )
11
11
 
12
12
  desc 'Allows for the topics management'
13
+
14
+ option(
15
+ :detailed_exitcode,
16
+ 'Exists with 0 when no changes, 1 when error and 2 when changes present or applied',
17
+ TrueClass,
18
+ %w[
19
+ --detailed_exitcode
20
+ ]
21
+ )
22
+
23
+ # We exit with 0 if no changes happened
24
+ NO_CHANGES_EXIT_CODE = 0
25
+
26
+ # When any changes happened (or could happen) we return 2 because 1 is default when Ruby
27
+ # crashes
28
+ CHANGES_EXIT_CODE = 2
29
+
30
+ private_constant :NO_CHANGES_EXIT_CODE, :CHANGES_EXIT_CODE
31
+
13
32
  # @param action [String] action we want to take
14
33
  def call(action = 'missing')
15
- case action
16
- when 'create'
17
- Topics::Create.new.call
18
- when 'delete'
19
- Topics::Delete.new.call
20
- when 'reset'
21
- Topics::Reset.new.call
22
- when 'repartition'
23
- Topics::Repartition.new.call
24
- when 'migrate'
25
- Topics::Migrate.new.call
26
- when 'align'
27
- Topics::Align.new.call
28
- when 'plan'
29
- Topics::Plan.new.call
30
- else
31
- raise ::ArgumentError, "Invalid topics action: #{action}"
32
- end
34
+ detailed_exit_code = options.fetch(:detailed_exitcode, false)
35
+
36
+ command = case action
37
+ when 'create'
38
+ Topics::Create
39
+ when 'delete'
40
+ Topics::Delete
41
+ when 'reset'
42
+ Topics::Reset
43
+ when 'repartition'
44
+ Topics::Repartition
45
+ when 'migrate'
46
+ Topics::Migrate
47
+ when 'align'
48
+ Topics::Align
49
+ when 'plan'
50
+ Topics::Plan
51
+ else
52
+ raise ::ArgumentError, "Invalid topics action: #{action}"
53
+ end
54
+
55
+ changes = command.new.call
56
+
57
+ return unless detailed_exit_code
58
+
59
+ changes ? exit(CHANGES_EXIT_CODE) : exit(NO_CHANGES_EXIT_CODE)
33
60
  end
34
61
  end
35
62
  end
@@ -8,6 +8,8 @@ module Karafka
8
8
  # It is threadsafe and provides some security measures so we won't end up operating on a
9
9
  # closed consumer instance as it causes Ruby VM process to crash.
10
10
  class Client
11
+ include ::Karafka::Core::Helpers::Time
12
+
11
13
  attr_reader :rebalance_manager
12
14
 
13
15
  # @return [Karafka::Routing::SubscriptionGroup] subscription group to which this client
@@ -24,7 +26,29 @@ module Karafka
24
26
  # How many times should we retry polling in case of a failure
25
27
  MAX_POLL_RETRIES = 20
26
28
 
27
- private_constant :MAX_POLL_RETRIES
29
+ # How much time of the total shutdown time can we wait for our manual unsubscribe before
30
+ # attempting to close without unsubscribe. We try to wait for 50% of the shutdown time
31
+ # before we move to a regular unsubscribe.
32
+ COOP_UNSUBSCRIBE_FACTOR = 0.5
33
+
34
+ # Errors upon which we early report that something is off without retrying prior to the
35
+ # report
36
+ EARLY_REPORT_ERRORS = [
37
+ :inconsistent_group_protocol, # 23
38
+ :max_poll_exceeded, # -147
39
+ :network_exception, # 13
40
+ :transport, # -195
41
+ :topic_authorization_failed, # 29
42
+ :group_authorization_failed, # 30
43
+ :cluster_authorization_failed, # 31
44
+ :illegal_generation,
45
+ # this will not recover as fencing is permanent
46
+ :fenced, # -144
47
+ # This can happen for many reasons, including issues with static membership being fenced
48
+ :fatal # -150
49
+ ].freeze
50
+
51
+ private_constant :MAX_POLL_RETRIES, :COOP_UNSUBSCRIBE_FACTOR, :EARLY_REPORT_ERRORS
28
52
 
29
53
  # Creates a new consumer instance.
30
54
  #
@@ -42,7 +66,7 @@ module Karafka
42
66
  @subscription_group = subscription_group
43
67
  @buffer = RawMessagesBuffer.new
44
68
  @tick_interval = ::Karafka::App.config.internal.tick_interval
45
- @rebalance_manager = RebalanceManager.new(@subscription_group.id)
69
+ @rebalance_manager = RebalanceManager.new(@subscription_group.id, @buffer)
46
70
  @rebalance_callback = Instrumentation::Callbacks::Rebalance.new(@subscription_group)
47
71
 
48
72
  @interval_runner = Helpers::IntervalRunner.new do
@@ -91,8 +115,21 @@ module Karafka
91
115
  # Fetch message within our time boundaries
92
116
  response = poll(time_poll.remaining)
93
117
 
94
- # Put a message to the buffer if there is one
95
- @buffer << response if response && response != :tick_time
118
+ # We track when last polling happened so we can provide means to detect upcoming
119
+ # `max.poll.interval.ms` limit
120
+ @buffer.polled
121
+
122
+ case response
123
+ when :tick_time
124
+ nil
125
+ # We get a hash only in case of eof error
126
+ when Hash
127
+ @buffer.eof(response[:topic], response[:partition])
128
+ when nil
129
+ nil
130
+ else
131
+ @buffer << response
132
+ end
96
133
 
97
134
  # Upon polling rebalance manager might have been updated.
98
135
  # If partition revocation happens, we need to remove messages from revoked partitions
@@ -104,7 +141,7 @@ module Karafka
104
141
  # Since rebalances do not occur often, we can run events polling as well without
105
142
  # any throttling
106
143
  events_poll
107
- remove_revoked_and_duplicated_messages
144
+
108
145
  break
109
146
  end
110
147
 
@@ -115,10 +152,11 @@ module Karafka
115
152
  time_poll.checkpoint
116
153
 
117
154
  # Finally once we've (potentially) removed revoked, etc, if no messages were returned
118
- # and it was not an early poll exist, we can break.
155
+ # and it was not an early poll exist, we can break. We also break if we got the eof
156
+ # signaling to propagate it asap
119
157
  # Worth keeping in mind, that the rebalance manager might have been updated despite no
120
158
  # messages being returned during a poll
121
- break unless response
159
+ break if response.nil? || response.is_a?(Hash)
122
160
  end
123
161
 
124
162
  @buffer
@@ -257,14 +295,36 @@ module Karafka
257
295
 
258
296
  # Gracefully stops topic consumption.
259
297
  def stop
260
- # In case of cooperative-sticky, there is a bug in librdkafka that may hang it.
261
- # To mitigate it we first need to unsubscribe so we will not receive any assignments and
262
- # only then we should be good to go.
298
+ # librdkafka has several constant issues when shutting down during rebalance. This is
299
+ # an issue that gets back every few versions of librdkafka in a limited scope, for example
300
+ # for cooperative-sticky or in a general scope. This is why we unsubscribe and wait until
301
+ # we no longer have any assignments. That way librdkafka consumer shutdown should never
302
+ # happen with rebalance associated with the given consumer instance. Since we do not want
303
+ # to wait forever, we also impose a limit on how long should we wait. This prioritizes
304
+ # shutdown stability over endless wait.
305
+ #
306
+ # The `@unsubscribing` ensures that when there would be a direct close attempt, it
307
+ # won't get into this loop again. This can happen when supervision decides it should close
308
+ # things faster
309
+ #
310
+ # @see https://github.com/confluentinc/librdkafka/issues/4792
263
311
  # @see https://github.com/confluentinc/librdkafka/issues/4527
264
- if @subscription_group.kafka[:'partition.assignment.strategy'] == 'cooperative-sticky'
312
+ if unsubscribe?
313
+ @unsubscribing = true
314
+
315
+ # Give 50% of time for the final close before we reach the forceful
316
+ max_wait = ::Karafka::App.config.shutdown_timeout * COOP_UNSUBSCRIBE_FACTOR
317
+ used = 0
318
+ stopped_at = monotonic_now
319
+
265
320
  unsubscribe
266
321
 
267
322
  until assignment.empty?
323
+ used += monotonic_now - stopped_at
324
+ stopped_at = monotonic_now
325
+
326
+ break if used >= max_wait
327
+
268
328
  sleep(0.1)
269
329
 
270
330
  ping
@@ -402,7 +462,9 @@ module Karafka
402
462
  # then seek to the appropriate message
403
463
  # We set the timeout to 2_000 to make sure that remote clusters handle this well
404
464
  real_offsets = @wrapped_kafka.offsets_for_times(tpl)
405
- detected_partition = real_offsets.to_h.dig(message.topic, message.partition)
465
+ # We always ask for one partition, so result will contain array with only one element
466
+ # that is the partition we were interested it regardless its number
467
+ detected_partition = real_offsets.to_h.dig(message.topic, 0)
406
468
 
407
469
  # There always needs to be an offset. In case we seek into the future, where there
408
470
  # are no offsets yet, we get -1 which indicates the most recent offset
@@ -545,11 +607,7 @@ module Karafka
545
607
  # We want to report early on max poll interval exceeding because it may mean that the
546
608
  # underlying processing is taking too much time and it is not LRJ
547
609
  case e.code
548
- when :max_poll_exceeded # -147
549
- early_report = true
550
- when :network_exception # 13
551
- early_report = true
552
- when :transport # -195
610
+ when *EARLY_REPORT_ERRORS
553
611
  early_report = true
554
612
  # @see
555
613
  # https://github.com/confluentinc/confluent-kafka-dotnet/issues/1366#issuecomment-821842990
@@ -563,11 +621,12 @@ module Karafka
563
621
  # No sense in retrying when no topic/partition and we're no longer running
564
622
  retryable = false unless Karafka::App.running?
565
623
  # If we detect the end of partition which can happen if `enable.partition.eof` is set to
566
- # true, we can just return nil fast. This will fast yield whatever set of messages we
624
+ # true, we can just return fast. This will fast yield whatever set of messages we
567
625
  # already have instead of waiting. This can be used for better latency control when we do
568
626
  # not expect a lof of lag and want to quickly move to processing.
627
+ # We can also pass the eof notion to the consumers for improved decision making.
569
628
  when :partition_eof
570
- return nil
629
+ return e.details
571
630
  end
572
631
 
573
632
  if early_report || !retryable
@@ -647,24 +706,15 @@ module Karafka
647
706
  subscriptions = @subscription_group.subscriptions
648
707
  assignments = @subscription_group.assignments(consumer)
649
708
 
650
- consumer.subscribe(*subscriptions) if subscriptions
651
- consumer.assign(assignments) if assignments
652
-
653
- consumer
654
- end
655
-
656
- # We may have a case where in the middle of data polling, we've lost a partition.
657
- # In a case like this we should remove all the pre-buffered messages from list partitions as
658
- # we are no longer responsible in a given process for processing those messages and they
659
- # should have been picked up by a different process.
660
- def remove_revoked_and_duplicated_messages
661
- @rebalance_manager.lost_partitions.each do |topic, partitions|
662
- partitions.each do |partition|
663
- @buffer.delete(topic, partition)
664
- end
709
+ if subscriptions
710
+ consumer.subscribe(*subscriptions)
711
+ @mode = :subscribe
712
+ elsif assignments
713
+ consumer.assign(assignments)
714
+ @mode = :assign
665
715
  end
666
716
 
667
- @buffer.uniq!
717
+ consumer
668
718
  end
669
719
 
670
720
  # @return [Rdkafka::Consumer] librdkafka consumer instance
@@ -679,6 +729,23 @@ module Karafka
679
729
  @kafka.start
680
730
  @kafka
681
731
  end
732
+
733
+ # Decides whether or not we should unsubscribe prior to closing.
734
+ #
735
+ # We cannot do it when there is a static group membership assignment as it would be
736
+ # reassigned.
737
+ # We cannot do it also for assign mode because then there are no subscriptions
738
+ # We also do not do it if there are no assignments at all as it does not make sense
739
+ #
740
+ # @return [Boolean] should we unsubscribe prior to shutdown
741
+ def unsubscribe?
742
+ return false if @unsubscribing
743
+ return false if @subscription_group.kafka.key?(:'group.instance.id')
744
+ return false if @mode != :subscribe
745
+ return false if assignment.empty?
746
+
747
+ true
748
+ end
682
749
  end
683
750
  end
684
751
  end
@@ -253,7 +253,9 @@ module Karafka
253
253
 
254
254
  reset
255
255
 
256
- sleep(1) && retry
256
+ # Ruby sleep is in seconds
257
+ sleep_time = ::Karafka::App.config.internal.connection.reset_backoff / 10_000.0
258
+ sleep(sleep_time) && retry
257
259
  end
258
260
 
259
261
  # Resumes processing of partitions that were paused due to an error.
@@ -330,28 +332,58 @@ module Karafka
330
332
  # given scheduler. It also handles the idle jobs when filtering API removed all messages
331
333
  # and we need to run house-keeping
332
334
  def build_and_schedule_flow_jobs
333
- return if @messages_buffer.empty?
334
-
335
335
  consume_jobs = []
336
336
  idle_jobs = []
337
+ eofed_jobs = []
338
+
339
+ @messages_buffer.each do |topic, partition, messages, eof, last_polled_at|
340
+ # In case we did not receive any new messages without eof we skip.
341
+ # We may yield empty array here in case we have reached eof without new messages but in
342
+ # such cases, we can run an eof job
343
+ next if messages.empty? && !eof
337
344
 
338
- @messages_buffer.each do |topic, partition, messages|
339
345
  coordinator = @coordinators.find_or_create(topic, partition)
340
- # Start work coordination for this topic partition
346
+ coordinator.eofed = eof
347
+ coordinator.last_polled_at = last_polled_at
348
+
349
+ # If we did not receive any messages and we did receive eof signal, we run the eofed
350
+ # jobs so user can take actions on reaching eof
351
+ if messages.empty? && eof
352
+ # If user wants to run the eofed jobs on eof we do it. Otherwise we just allow it to
353
+ # pass through. This allows to configure if user actually wants to have `#eofed`
354
+ # logic or if he wants to only use fast eof work yield
355
+ if coordinator.topic.eofed?
356
+ @executors.find_all_or_create(topic, partition, coordinator).each do |executor|
357
+ coordinator.increment(:eofed)
358
+ eofed_jobs << @jobs_builder.eofed(executor)
359
+ end
360
+ end
361
+
362
+ next
363
+ end
364
+
341
365
  coordinator.start(messages)
342
366
 
367
+ # If it is not an eof and there are no new messages, we just run house-keeping
368
+ #
343
369
  # We do not increment coordinator for idle job because it's not a user related one
344
370
  # and it will not go through a standard lifecycle. Same applies to revoked and shutdown
345
371
  if messages.empty?
372
+ # Start work coordination for this topic partition
346
373
  coordinator.increment(:idle)
347
374
  executor = @executors.find_or_create(topic, partition, 0, coordinator)
348
375
  idle_jobs << @jobs_builder.idle(executor)
349
- else
350
- @partitioner.call(topic, messages, coordinator) do |group_id, partition_messages|
351
- coordinator.increment(:consume)
352
- executor = @executors.find_or_create(topic, partition, group_id, coordinator)
353
- consume_jobs << @jobs_builder.consume(executor, partition_messages)
354
- end
376
+
377
+ next
378
+ end
379
+
380
+ # If there are messages, it is irrelevant if eof or not as consumption needs to happen
381
+ #
382
+ # Start work coordination for this topic partition
383
+ @partitioner.call(topic, messages, coordinator) do |group_id, partition_messages|
384
+ coordinator.increment(:consume)
385
+ executor = @executors.find_or_create(topic, partition, group_id, coordinator)
386
+ consume_jobs << @jobs_builder.consume(executor, partition_messages)
355
387
  end
356
388
  end
357
389
 
@@ -367,6 +399,11 @@ module Karafka
367
399
  consume_jobs.each(&:before_schedule)
368
400
  @scheduler.on_schedule_consumption(consume_jobs)
369
401
  end
402
+
403
+ unless eofed_jobs.empty?
404
+ eofed_jobs.each(&:before_schedule)
405
+ @scheduler.on_schedule_eofed(eofed_jobs)
406
+ end
370
407
  end
371
408
 
372
409
  # Builds and schedules periodic jobs for topics partitions for which no messages were
@@ -23,9 +23,13 @@ module Karafka
23
23
  def initialize(subscription_group)
24
24
  @subscription_group = subscription_group
25
25
  @size = 0
26
+
26
27
  @groups = Hash.new do |topic_groups, topic|
27
28
  topic_groups[topic] = Hash.new do |partition_groups, partition|
28
- partition_groups[partition] = []
29
+ partition_groups[partition] = {
30
+ eof: false,
31
+ messages: []
32
+ }
29
33
  end
30
34
  end
31
35
  end
@@ -33,24 +37,31 @@ module Karafka
33
37
  # Remaps raw messages from the raw messages buffer to Karafka messages
34
38
  # @param raw_messages_buffer [RawMessagesBuffer] buffer with raw messages
35
39
  def remap(raw_messages_buffer)
36
- clear unless @size.zero?
40
+ clear
37
41
 
38
42
  # Since it happens "right after" we've received the messages, it is close enough it time
39
43
  # to be used as the moment we received messages.
40
44
  received_at = Time.now
45
+ last_polled_at = raw_messages_buffer.last_polled_at
41
46
 
42
- raw_messages_buffer.each do |topic, partition, messages|
47
+ raw_messages_buffer.each do |topic, partition, messages, eof|
43
48
  @size += messages.count
44
49
 
45
50
  ktopic = @subscription_group.topics.find(topic)
46
51
 
47
- @groups[topic][partition] = messages.map do |message|
52
+ built_messages = messages.map do |message|
48
53
  Messages::Builders::Message.call(
49
54
  message,
50
55
  ktopic,
51
56
  received_at
52
57
  )
53
58
  end
59
+
60
+ @groups[topic][partition] = {
61
+ eof: eof,
62
+ messages: built_messages,
63
+ last_polled_at: last_polled_at
64
+ }
54
65
  end
55
66
  end
56
67
 
@@ -59,10 +70,12 @@ module Karafka
59
70
  # @yieldparam [String] topic name
60
71
  # @yieldparam [Integer] partition number
61
72
  # @yieldparam [Array<Karafka::Messages::Message>] messages from a given topic partition
73
+ # @yieldparam [Boolean] true if eof, false otherwise
74
+ # @yieldparam [Float] last polled at monotonic clock time
62
75
  def each
63
76
  @groups.each do |topic, partitions|
64
- partitions.each do |partition, messages|
65
- yield(topic, partition, messages)
77
+ partitions.each do |partition, details|
78
+ yield(topic, partition, details[:messages], details[:eof], details[:last_polled_at])
66
79
  end
67
80
  end
68
81
  end