@@ -248,45 +248,67 @@ class Consumer {
248248 err = LibrdKafkaError . create ( err ) ;
249249 const userSpecifiedRebalanceCb = this . #userConfig[ 'rebalance_cb' ] ;
250250
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+ }
252265
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. */
256288 let assignmentModified = false ;
257289 if ( typeof userSpecifiedRebalanceCb === 'function' ) {
258290 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 => {
261301 if ( alternateAssignment ) {
262302 assignment = alternateAssignment ;
263303 assignmentModified = true ;
264304 }
265305 resolve ( ) ;
266- } catch ( e ) {
267- reject ( e ) ;
268- }
306+ } ) . catch ( reject ) ;
269307 } ) ;
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+ } ) ;
290312 }
291313
292314 call
@@ -311,16 +333,10 @@ class Consumer {
311333 if ( err . code === LibrdKafkaError . codes . ERR__ASSIGN_PARTITIONS ) {
312334
313335 const checkPendingSeeks = this . #pendingSeeks. size !== 0 ;
314- if ( checkPendingSeeks && ! assignmentModified )
336+ if ( checkPendingSeeks && ! assignmentModified && ! assignmentFnCalled )
315337 assignment = this . #assignAsPerSeekedOffsets( assignment ) ;
316338
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 ) ;
324340
325341 if ( checkPendingSeeks ) {
326342 const offsetsToCommit = assignment
@@ -342,15 +358,7 @@ class Consumer {
342358 this . #messageCache. addTopicPartitions ( assignment ) ;
343359
344360 } 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 ) ;
354362 }
355363 } catch ( e ) {
356364 // Ignore exceptions if we are not connected
@@ -522,16 +530,10 @@ class Consumer {
522530
523531 /* Delete properties which are already processed, or cannot be passed to node-rdkafka */
524532 delete rdKafkaConfig . kafkaJS ;
525- delete rdKafkaConfig . rebalanceListener ;
526533
527534 /* Certain properties that the user has set are overridden. We use trampolines to accommodate the user's callbacks.
528535 * TODO: add trampoline method for offset commit callback. */
529536 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- }
535537 rdKafkaConfig [ 'rebalance_cb' ] = this . #rebalanceCallback. bind ( this ) ;
536538
537539 /* Offset management is different from case to case.
@@ -1587,6 +1589,7 @@ class Consumer {
15871589 if ( ! topic . partitions ) {
15881590 toppar . partitions = this . #getAllAssignedPartition( topic . topic ) ;
15891591 } else {
1592+ /* TODO: add a check here to make sure we own each partition */
15901593 toppar . partitions = [ ...topic . partitions ] ;
15911594 }
15921595
@@ -1597,6 +1600,8 @@ class Consumer {
15971600 if ( flattenedToppars . length === 0 ) {
15981601 return ;
15991602 }
1603+
1604+ /* TODO: error handling is lacking for pause, including partition level errors. */
16001605 this . #internalClient. pause ( flattenedToppars ) ;
16011606
16021607 /* Mark the messages in the cache as stale, runInternal* will deal with
@@ -1608,7 +1613,7 @@ class Consumer {
16081613 . filter ( key => this . #topicPartitionToBatchPayload. has ( key ) )
16091614 . forEach ( key => this . #topicPartitionToBatchPayload. get ( key ) . _stale = true ) ;
16101615
1611- flattenedToppars . map ( JSON . stringify ) . forEach ( topicPartition => this . #pausedPartitions. add ( topicPartition ) ) ;
1616+ flattenedToppars . map ( JSON . stringify ) . forEach ( topicPartition => this . #pausedPartitions. add ( topicPartition ) ) ;
16121617
16131618 /* Note: we don't use flattenedToppars here because resume flattens them again. */
16141619 return ( ) => this . resume ( toppars ) ;
0 commit comments