|
25 | 25 | "Returns opts with final :ketu.apache.consumer/config entry" |
26 | 26 | [opts] |
27 | 27 | (let [original-config (:ketu.apache.consumer/config opts) |
28 | | - config (util/set-ketu-to-apache-opts original-config opts)] |
| 28 | + config (util/set-ketu-to-apache-opts original-config opts)] |
29 | 29 | (assoc opts :ketu.apache.consumer/config config))) |
30 | 30 |
|
31 | 31 | (defn default-consumer-supplier [opts] |
32 | | - (let [key-type (:ketu.source/key-type opts) |
33 | | - key-deserializer (when (instance? Deserializer key-type) key-type) |
34 | | - value-type (:ketu.source/value-type opts) |
| 32 | + (let [key-type (:ketu.source/key-type opts) |
| 33 | + key-deserializer (when (instance? Deserializer key-type) key-type) |
| 34 | + value-type (:ketu.source/value-type opts) |
35 | 35 | value-deserializer (when (instance? Deserializer value-type) value-type)] |
36 | 36 | (consumer/consumer (:ketu.apache.consumer/config opts) key-deserializer value-deserializer))) |
37 | 37 |
|
38 | 38 | (defn default-opts [] |
39 | | - {:ketu.source/consumer-supplier default-consumer-supplier |
40 | | - :ketu.source/key-type :byte-array |
41 | | - :ketu.source/value-type :byte-array |
42 | | - :ketu.source/poll-timeout-ms 100 |
43 | | - :ketu.source/done-putting-timeout-ms 60000 |
44 | | - :ketu.source/consumer-close-timeout-ms 60000 |
| 39 | + {:ketu.source/consumer-supplier default-consumer-supplier |
| 40 | + :ketu.source/key-type :byte-array |
| 41 | + :ketu.source/value-type :byte-array |
| 42 | + :ketu.source/poll-timeout-ms 100 |
| 43 | + :ketu.source/done-putting-timeout-ms 60000 |
| 44 | + :ketu.source/consumer-close-timeout-ms 60000 |
45 | 45 | :ketu.source/consumer-thread-timeout-ms 60000 |
46 | | - :ketu.source/close-out-chan? true |
47 | | - :ketu.source/close-consumer? true}) |
| 46 | + :ketu.source/close-out-chan? true |
| 47 | + :ketu.source/close-consumer? true}) |
48 | 48 |
|
49 | 49 | (defn- finalize-opts [opts] |
50 | 50 | (-> (default-opts) |
|
54 | 54 | (defn- subscribe-fn |
55 | 55 | "Returns a function that takes a consumer and subscribes to either a topic list or a pattern according to opts." |
56 | 56 | [opts] |
57 | | - (let [subscribe (cond |
58 | | - (:ketu.source/topic opts) consumer/subscribe-to-topic! |
59 | | - (:ketu.source/topic-list opts) consumer/subscribe-to-list! |
60 | | - (:ketu.source/topic-pattern opts) consumer/subscribe-to-pattern!) |
61 | | - topic (or (:ketu.source/topic opts) |
62 | | - (:ketu.source/topic-list opts) |
63 | | - (:ketu.source/topic-pattern opts)) |
| 57 | + (let [subscribe (cond |
| 58 | + (:ketu.source/topic opts) consumer/subscribe-to-topic! |
| 59 | + (:ketu.source/topic-list opts) consumer/subscribe-to-list! |
| 60 | + (:ketu.source/topic-pattern opts) consumer/subscribe-to-pattern!) |
| 61 | + topic (or (:ketu.source/topic opts) |
| 62 | + (:ketu.source/topic-list opts) |
| 63 | + (:ketu.source/topic-pattern opts)) |
64 | 64 | create-listener (:ketu.source/create-rebalance-listener-obj opts)] |
65 | 65 | (if create-listener |
66 | 66 | (fn [consumer] (subscribe consumer topic (create-listener {:ketu.source/consumer consumer}))) |
|
70 | 70 | "Returns a function that takes a consumer and assigns specific partitions according to opts." |
71 | 71 | [opts] |
72 | 72 | (when-let [assign-tps (:ketu.source/assign-single-topic-partitions opts)] |
73 | | - (let [topic (:ketu.source.assign/topic assign-tps) |
| 73 | + (let [topic (:ketu.source.assign/topic assign-tps) |
74 | 74 | partitions (:ketu.source.assign/partition-nums assign-tps)] |
75 | 75 | (fn [consumer] |
76 | 76 | (consumer/assign! consumer (consumer/topic-partitions topic partitions)))))) |
|
96 | 96 | ([^Consumer consumer source-name opts records-to-skip] |
97 | 97 | (try |
98 | 98 | (let [assigned-partitions (consumer/assignment consumer) |
99 | | - records-to-skip (or records-to-skip (get-max-poll-records-from-opts opts))] |
| 99 | + records-to-skip (or records-to-skip (get-max-poll-records-from-opts opts))] |
100 | 100 | (doseq [^TopicPartition partition assigned-partitions] |
101 | 101 | (try |
102 | 102 | (let [current-position (consumer/position consumer partition) |
|
111 | 111 | (log/error logger "[source={}] Failed to get assigned partitions for offset increment" |
112 | 112 | source-name e))))) |
113 | 113 |
|
114 | | -(defn default-poll-error-handler [consumer opts] |
| 114 | +(defn- default-poll-error-handler [consumer opts] |
115 | 115 | (let [records-to-skip (:ketu.source/error-skip-offset-amount opts) |
116 | | - source-name (:ketu/name opts)] |
| 116 | + source-name (:ketu/name opts)] |
117 | 117 | (increment-offsets-for-assigned-partitions! consumer source-name opts records-to-skip) |
118 | 118 | [])) |
119 | 119 |
|
| 120 | +(defn- get-custom-error-handler [opts] |
| 121 | + (let [provided-catch-fn (:ketu.source/custom-catch-fn opts) |
| 122 | + custom-catch-fn |
| 123 | + (cond |
| 124 | + (nil? provided-catch-fn) |
| 125 | + nil |
| 126 | + |
| 127 | + (fn? provided-catch-fn) |
| 128 | + provided-catch-fn |
| 129 | + |
| 130 | + :else |
| 131 | + (do |
| 132 | + (log/error logger "[source={}] Invalid :ketu.source/custom-catch-fn (must be fn [consumer opts] -> coll), got: %s. Using default error handler." |
| 133 | + (type provided-catch-fn)) |
| 134 | + nil))] |
| 135 | + custom-catch-fn)) |
| 136 | + |
120 | 137 | (defn- poll-fn [^Consumer consumer should-poll? opts] |
121 | 138 | (when @should-poll? |
122 | 139 | (let [source-name (:ketu/name opts) |
123 | | - error-handler (:ketu.source/poll-error-handler opts (default-poll-error-handler consumer opts)) |
| 140 | + custom-error-handler (get-custom-error-handler opts) |
| 141 | + error-handler (or custom-error-handler (default-poll-error-handler consumer opts)) |
124 | 142 | poll-timeout-duration (Duration/ofMillis (:ketu.source/poll-timeout-ms opts))] |
125 | 143 | (fn [] |
126 | 144 | (try |
|
139 | 157 |
|
140 | 158 | (defn- source-existing-consumer |
141 | 159 | [^Consumer consumer out-chan opts] |
142 | | - (let [source-name (:ketu/name opts) |
143 | | - ^String thread-name (str "ketu-source-" source-name) |
144 | | - close-out-chan? (:ketu.source/close-out-chan? opts) |
145 | | - ^long close-consumer? (:ketu.source/close-consumer? opts) |
| 160 | + (let [source-name (:ketu/name opts) |
| 161 | + ^String thread-name (str "ketu-source-" source-name) |
| 162 | + close-out-chan? (:ketu.source/close-out-chan? opts) |
| 163 | + ^long close-consumer? (:ketu.source/close-consumer? opts) |
146 | 164 | consumer-close-timeout-ms (:ketu.source/consumer-close-timeout-ms opts) |
147 | | - should-poll? (volatile! true) |
148 | | - abort-pending-put (async/chan) |
149 | | - done-putting (async/chan) |
150 | | - subscribe! (or (subscribe-fn opts) (assign-fn opts)) |
151 | | - poll-impl (poll-fn consumer should-poll? opts) |
152 | | - poll! (if (some? (:ketu.source/consumer-decorator opts)) |
153 | | - (consumer-decorator/decorate-poll-fn {:ketu.source/consumer consumer} poll-impl opts) |
154 | | - poll-impl) |
155 | | - ->data (->data-fn opts) |
156 | | - put! (fn [record] (put-or-abort-pending! out-chan (->data record) abort-pending-put)) |
| 165 | + should-poll? (volatile! true) |
| 166 | + abort-pending-put (async/chan) |
| 167 | + done-putting (async/chan) |
| 168 | + subscribe! (or (subscribe-fn opts) (assign-fn opts)) |
| 169 | + poll-impl (poll-fn consumer should-poll? opts) |
| 170 | + poll! (if (some? (:ketu.source/consumer-decorator opts)) |
| 171 | + (consumer-decorator/decorate-poll-fn {:ketu.source/consumer consumer} poll-impl opts) |
| 172 | + poll-impl) |
| 173 | + ->data (->data-fn opts) |
| 174 | + put! (fn [record] (put-or-abort-pending! out-chan (->data record) abort-pending-put)) |
157 | 175 |
|
158 | 176 | consumer-thread |
159 | | - (async/thread |
160 | | - (try |
161 | | - (.info logger "[source={}] Start consumer thread" source-name) |
162 | | - (.setName (Thread/currentThread) thread-name) |
163 | | - |
164 | | - (subscribe! consumer) |
165 | | - |
166 | | - (loop [] |
167 | | - (when-let [records (poll!)] |
168 | | - (run! put! records) |
169 | | - (recur))) |
170 | | - |
171 | | - (catch WakeupException e |
172 | | - ; We wakeup the consumer on graceful shutdown after should-poll? is false. |
173 | | - ; If it's not false, somebody else woke the consumer up unexpectedly. |
174 | | - (when @should-poll? |
175 | | - (log/error logger "[source={}] Unexpected consumer wakeup" source-name e))) |
176 | | - (catch Exception e |
177 | | - (log/error logger "[source={}] Unrecoverable consumer error" source-name e)) |
178 | | - (finally |
179 | | - (log/info logger "[source={}] Done consuming" source-name) |
180 | | - (async/close! done-putting) |
181 | | - (when close-out-chan? |
182 | | - (log/info logger "[source={}] Close out channel" source-name) |
183 | | - (async/close! out-chan)) |
184 | | - (when close-consumer? |
185 | | - (log/info logger "[source={}] Close consumer" source-name) |
186 | | - (consumer/close! consumer consumer-close-timeout-ms)) |
187 | | - (log/info logger "[source={}] Exit consumer thread" source-name)))) |
188 | | - |
189 | | - |
190 | | - state {:ketu/source-opts opts |
191 | | - :ketu.source/out-chan out-chan |
192 | | - :ketu.source/consumer consumer |
193 | | - :ketu.source/should-poll? should-poll? |
194 | | - :ketu.source/abort-pending-put abort-pending-put |
195 | | - :ketu.source/done-putting done-putting |
196 | | - :ketu.source/consumer-thread consumer-thread}] |
| 177 | + (async/thread |
| 178 | + (try |
| 179 | + (.info logger "[source={}] Start consumer thread" source-name) |
| 180 | + (.setName (Thread/currentThread) thread-name) |
| 181 | + |
| 182 | + (subscribe! consumer) |
| 183 | + |
| 184 | + (loop [] |
| 185 | + (when-let [records (poll!)] |
| 186 | + (run! put! records) |
| 187 | + (recur))) |
| 188 | + |
| 189 | + (catch WakeupException e |
| 190 | + ; We wakeup the consumer on graceful shutdown after should-poll? is false. |
| 191 | + ; If it's not false, somebody else woke the consumer up unexpectedly. |
| 192 | + (when @should-poll? |
| 193 | + (log/error logger "[source={}] Unexpected consumer wakeup" source-name e))) |
| 194 | + (catch Exception e |
| 195 | + (log/error logger "[source={}] Unrecoverable consumer error" source-name e)) |
| 196 | + (finally |
| 197 | + (log/info logger "[source={}] Done consuming" source-name) |
| 198 | + (async/close! done-putting) |
| 199 | + (when close-out-chan? |
| 200 | + (log/info logger "[source={}] Close out channel" source-name) |
| 201 | + (async/close! out-chan)) |
| 202 | + (when close-consumer? |
| 203 | + (log/info logger "[source={}] Close consumer" source-name) |
| 204 | + (consumer/close! consumer consumer-close-timeout-ms)) |
| 205 | + (log/info logger "[source={}] Exit consumer thread" source-name)))) |
| 206 | + |
| 207 | + |
| 208 | + state {:ketu/source-opts opts |
| 209 | + :ketu.source/out-chan out-chan |
| 210 | + :ketu.source/consumer consumer |
| 211 | + :ketu.source/should-poll? should-poll? |
| 212 | + :ketu.source/abort-pending-put abort-pending-put |
| 213 | + :ketu.source/done-putting done-putting |
| 214 | + :ketu.source/consumer-thread consumer-thread}] |
197 | 215 |
|
198 | 216 | state)) |
199 | 217 |
|
200 | 218 | (defn source |
201 | 219 | "Starts consuming into a channel. |
202 | 220 | Returns a state map including out-chan. Pass it to the `stop!` function when done." |
203 | 221 | [ch opts] |
204 | | - (let [opts (ketu.spec/assert-and-conform :ketu/public-source-opts opts) |
205 | | - opts (finalize-opts opts) |
206 | | - source-name (:ketu/name opts) |
| 222 | + (let [opts (ketu.spec/assert-and-conform :ketu/public-source-opts opts) |
| 223 | + opts (finalize-opts opts) |
| 224 | + source-name (:ketu/name opts) |
207 | 225 | consumer-supplier (:ketu.source/consumer-supplier opts) |
208 | | - consumer (try |
209 | | - (consumer-supplier opts) |
210 | | - (catch Exception e |
211 | | - (log/error logger "[source={}] Error creating consumer-source" source-name e) |
212 | | - (when (opts :ketu.source/close-out-chan?) |
213 | | - (log/warn logger "[source={}] Close consumer channel" source-name) |
214 | | - (async/close! ch)) |
215 | | - (throw e)))] |
| 226 | + consumer (try |
| 227 | + (consumer-supplier opts) |
| 228 | + (catch Exception e |
| 229 | + (log/error logger "[source={}] Error creating consumer-source" source-name e) |
| 230 | + (when (opts :ketu.source/close-out-chan?) |
| 231 | + (log/warn logger "[source={}] Close consumer channel" source-name) |
| 232 | + (async/close! ch)) |
| 233 | + (throw e)))] |
216 | 234 | (try |
217 | 235 | (source-existing-consumer consumer ch opts) |
218 | 236 | (catch Exception e |
|
234 | 252 | (consumer/wakeup! consumer))) |
235 | 253 |
|
236 | 254 | (defn- wait-for-put! [state] |
237 | | - (let [done-putting (:ketu.source/done-putting state) |
| 255 | + (let [done-putting (:ketu.source/done-putting state) |
238 | 256 | done-putting-timeout-ms (-> state :ketu/source-opts :ketu.source/done-putting-timeout-ms) |
239 | | - timeout (async/timeout done-putting-timeout-ms)] |
| 257 | + timeout (async/timeout done-putting-timeout-ms)] |
240 | 258 | (async/alts!! [done-putting timeout] :priority true))) |
241 | 259 |
|
242 | 260 | (defn- abort-pending-put! [state] |
|
252 | 270 | (:ketu.source/consumer-thread state)) |
253 | 271 |
|
254 | 272 | (defn- wait-for-the-thread! [state] |
255 | | - (let [consumer-thread (:ketu.source/consumer-thread state) |
| 273 | + (let [consumer-thread (:ketu.source/consumer-thread state) |
256 | 274 | consumer-thread-timeout-ms (-> state :ketu/source-opts :ketu.source/consumer-thread-timeout-ms) |
257 | | - timeout (async/timeout consumer-thread-timeout-ms)] |
| 275 | + timeout (async/timeout consumer-thread-timeout-ms)] |
258 | 276 | (async/alts!! [consumer-thread timeout] :priority true))) |
259 | 277 |
|
260 | 278 | (defn stop! [state] |
|
0 commit comments