@@ -248,45 +248,67 @@ class Consumer {
248
248
err = LibrdKafkaError . create ( err ) ;
249
249
const userSpecifiedRebalanceCb = this . #userConfig[ 'rebalance_cb' ] ;
250
250
251
- let call ;
251
+ let assignmentFnCalled = false ;
252
+ function assignmentFn ( userAssignment ) {
253
+ if ( assignmentFnCalled )
254
+ return ;
255
+ assignmentFnCalled = true ;
256
+
257
+ if ( this . #internalClient. rebalanceProtocol ( ) === "EAGER" ) {
258
+ this . #internalClient. assign ( userAssignment ) ;
259
+ this . #partitionCount = userAssignment . length ;
260
+ } else {
261
+ this . #internalClient. incrementalAssign ( userAssignment ) ;
262
+ this . #partitionCount += userAssignment . length ;
263
+ }
264
+ }
252
265
253
- /* Since we don't expose assign() or incremental_assign() methods, we allow the user
254
- * to modify the assignment by returning it. If a truthy value is returned, we use that
255
- * and do not apply any pending seeks to it either. */
266
+ function unassignmentFn ( userAssignment ) {
267
+ if ( assignmentFnCalled )
268
+ return ;
269
+
270
+ assignmentFnCalled = true ;
271
+ if ( this . #internalClient. rebalanceProtocol ( ) === "EAGER" ) {
272
+ this . #internalClient. unassign ( ) ;
273
+ this . #messageCache. removeTopicPartitions ( ) ;
274
+ this . #partitionCount = 0 ;
275
+ } else {
276
+ this . #internalClient. incrementalUnassign ( userAssignment ) ;
277
+ this . #messageCache. removeTopicPartitions ( userAssignment ) ;
278
+ this . #partitionCount -= userAssignment . length ;
279
+ }
280
+ }
281
+
282
+ let call = Promise . resolve ( ) ;
283
+
284
+ /* We allow the user to modify the assignment by returning it. If a truthy
285
+ * value is returned, we use that and do not apply any pending seeks to it either.
286
+ * The user can alternatively use the assignmentFns argument.
287
+ * Precedence is given to the calling of functions within assignmentFns. */
256
288
let assignmentModified = false ;
257
289
if ( typeof userSpecifiedRebalanceCb === 'function' ) {
258
290
call = new Promise ( ( resolve , reject ) => {
259
- try {
260
- const alternateAssignment = userSpecifiedRebalanceCb ( err , assignment ) ;
291
+ const assignmentFns = {
292
+ assign : assignmentFn . bind ( this ) ,
293
+ unassign : unassignmentFn . bind ( this ) ,
294
+ } ;
295
+
296
+ /* The user specified callback may be async, or sync. Wrapping it in a
297
+ * Promise.resolve ensures that we always get a promise back. */
298
+ return Promise . resolve (
299
+ userSpecifiedRebalanceCb ( err , assignment , assignmentFns )
300
+ ) . then ( alternateAssignment => {
261
301
if ( alternateAssignment ) {
262
302
assignment = alternateAssignment ;
263
303
assignmentModified = true ;
264
304
}
265
305
resolve ( ) ;
266
- } catch ( e ) {
267
- reject ( e ) ;
268
- }
306
+ } ) . catch ( reject ) ;
269
307
} ) ;
270
- } else {
271
- switch ( err . code ) {
272
- // TODO: is this the right way to handle this error?
273
- // We might just be able to throw, because the error is something the user has caused.
274
- case LibrdKafkaError . codes . ERR__ASSIGN_PARTITIONS :
275
- call = ( this . #userConfig. rebalanceListener . onPartitionsAssigned ?
276
- this . #userConfig. rebalanceListener . onPartitionsAssigned ( assignment ) :
277
- Promise . resolve ( ) ) . catch ( e => this . #logger. error ( e ) ) ;
278
- break ;
279
- case LibrdKafkaError . codes . ERR__REVOKE_PARTITIONS :
280
- call = ( this . #userConfig. rebalanceListener . onPartitionsRevoked ?
281
- this . #userConfig. rebalanceListener . onPartitionsRevoked ( assignment ) :
282
- Promise . resolve ( ) ) . catch ( e => this . #logger. error ( e ) ) ;
283
- break ;
284
- default :
285
- call = Promise . reject ( `Unexpected rebalanceListener error code ${ err . code } ` ) . catch ( ( e ) => {
286
- this . #logger. error ( e ) ;
287
- } ) ;
288
- break ;
289
- }
308
+ } else if ( err . code !== LibrdKafkaError . codes . ERR__ASSIGN_PARTITIONS && err . code !== LibrdKafkaError . codes . ERR__REVOKE_PARTITIONS ) {
309
+ call = Promise . reject ( `Unexpected rebalance_cb error code ${ err . code } ` ) . catch ( ( e ) => {
310
+ this . #logger. error ( e ) ;
311
+ } ) ;
290
312
}
291
313
292
314
call
@@ -311,16 +333,10 @@ class Consumer {
311
333
if ( err . code === LibrdKafkaError . codes . ERR__ASSIGN_PARTITIONS ) {
312
334
313
335
const checkPendingSeeks = this . #pendingSeeks. size !== 0 ;
314
- if ( checkPendingSeeks && ! assignmentModified )
336
+ if ( checkPendingSeeks && ! assignmentModified && ! assignmentFnCalled )
315
337
assignment = this . #assignAsPerSeekedOffsets( assignment ) ;
316
338
317
- if ( this . #internalClient. rebalanceProtocol ( ) === "EAGER" ) {
318
- this . #internalClient. assign ( assignment ) ;
319
- this . #partitionCount = assignment . length ;
320
- } else {
321
- this . #internalClient. incrementalAssign ( assignment ) ;
322
- this . #partitionCount += assignment . length ;
323
- }
339
+ assignmentFn . call ( this , assignment ) ;
324
340
325
341
if ( checkPendingSeeks ) {
326
342
const offsetsToCommit = assignment
@@ -342,15 +358,7 @@ class Consumer {
342
358
this . #messageCache. addTopicPartitions ( assignment ) ;
343
359
344
360
} else {
345
- if ( this . #internalClient. rebalanceProtocol ( ) === "EAGER" ) {
346
- this . #internalClient. unassign ( ) ;
347
- this . #messageCache. removeTopicPartitions ( ) ;
348
- this . #partitionCount = 0 ;
349
- } else {
350
- this . #internalClient. incrementalUnassign ( assignment ) ;
351
- this . #messageCache. removeTopicPartitions ( assignment ) ;
352
- this . #partitionCount -= assignment . length ;
353
- }
361
+ unassignmentFn . call ( this , assignment ) ;
354
362
}
355
363
} catch ( e ) {
356
364
// Ignore exceptions if we are not connected
@@ -522,16 +530,10 @@ class Consumer {
522
530
523
531
/* Delete properties which are already processed, or cannot be passed to node-rdkafka */
524
532
delete rdKafkaConfig . kafkaJS ;
525
- delete rdKafkaConfig . rebalanceListener ;
526
533
527
534
/* Certain properties that the user has set are overridden. We use trampolines to accommodate the user's callbacks.
528
535
* TODO: add trampoline method for offset commit callback. */
529
536
rdKafkaConfig [ 'offset_commit_cb' ] = true ;
530
-
531
- if ( ! Object . hasOwn ( this . #userConfig, 'rebalanceListener' ) ) {
532
- /* We might want to do certain things to maintain internal state in rebalance listener, so we need to set it to an empty object. */
533
- this . #userConfig. rebalanceListener = { } ;
534
- }
535
537
rdKafkaConfig [ 'rebalance_cb' ] = this . #rebalanceCallback. bind ( this ) ;
536
538
537
539
/* Offset management is different from case to case.
@@ -1587,6 +1589,7 @@ class Consumer {
1587
1589
if ( ! topic . partitions ) {
1588
1590
toppar . partitions = this . #getAllAssignedPartition( topic . topic ) ;
1589
1591
} else {
1592
+ /* TODO: add a check here to make sure we own each partition */
1590
1593
toppar . partitions = [ ...topic . partitions ] ;
1591
1594
}
1592
1595
@@ -1597,6 +1600,8 @@ class Consumer {
1597
1600
if ( flattenedToppars . length === 0 ) {
1598
1601
return ;
1599
1602
}
1603
+
1604
+ /* TODO: error handling is lacking for pause, including partition level errors. */
1600
1605
this . #internalClient. pause ( flattenedToppars ) ;
1601
1606
1602
1607
/* Mark the messages in the cache as stale, runInternal* will deal with
@@ -1608,7 +1613,7 @@ class Consumer {
1608
1613
. filter ( key => this . #topicPartitionToBatchPayload. has ( key ) )
1609
1614
. forEach ( key => this . #topicPartitionToBatchPayload. get ( key ) . _stale = true ) ;
1610
1615
1611
- flattenedToppars . map ( JSON . stringify ) . forEach ( topicPartition => this . #pausedPartitions. add ( topicPartition ) ) ;
1616
+ flattenedToppars . map ( JSON . stringify ) . forEach ( topicPartition => this . #pausedPartitions. add ( topicPartition ) ) ;
1612
1617
1613
1618
/* Note: we don't use flattenedToppars here because resume flattens them again. */
1614
1619
return ( ) => this . resume ( toppars ) ;
0 commit comments