@@ -23,6 +23,7 @@ const {
23
23
const { Buffer } = require ( 'buffer' ) ;
24
24
const MessageCache = require ( './_consumer_cache' ) ;
25
25
const { hrtime } = require ( 'process' ) ;
26
+ const { LinkedList } = require ( './_linked-list' ) ;
26
27
27
28
const ConsumerState = Object . freeze ( {
28
29
INIT : 0 ,
@@ -170,9 +171,9 @@ class Consumer {
170
171
#fetchInProgress;
171
172
172
173
/**
173
- * Promise that resolves when there is something we need to poll for (messages, rebalance, etc) .
174
+ * List of DeferredPromises waiting on consumer queue to be non-empty .
174
175
*/
175
- #queueNonEmpty = new DeferredPromise ( ) ;
176
+ #queueWaiters = new LinkedList ( ) ;
176
177
177
178
/**
178
179
* Whether any rebalance callback is in progress.
@@ -1270,8 +1271,9 @@ class Consumer {
1270
1271
}
1271
1272
1272
1273
#queueNonEmptyCb( ) {
1273
- /* Unconditionally resolve the promise - not a problem if it's already resolved. */
1274
- this . #queueNonEmpty. resolve ( ) ;
1274
+ for ( const waiter of this . #queueWaiters) {
1275
+ waiter . resolve ( ) ;
1276
+ }
1275
1277
}
1276
1278
1277
1279
async #nextFetchRetry( ) {
@@ -1280,15 +1282,21 @@ class Consumer {
1280
1282
} else {
1281
1283
/* Backoff a little. If m is null, we might be without messages
1282
1284
* or in available partition starvation, and calling consumeSingleCached
1283
- * in a tight loop will help no one. We still keep it to 1000ms because we
1284
- * want to keep polling, though (ideally) we could increase it all the way
1285
- * up to max.poll.interval.ms.
1285
+ * in a tight loop will help no one.
1286
1286
* In case there is any message in the queue, we'll be woken up before the
1287
- * timer expires. */
1288
- await Timer . withTimeout ( 1000 , this . #queueNonEmpty) ;
1289
- if ( this . #queueNonEmpty. resolved ) {
1290
- this . #queueNonEmpty = new DeferredPromise ( ) ;
1291
- }
1287
+ * timer expires.
1288
+ * We have a per-worker promise, otherwise we end up awakening
1289
+ * other workers when they've already looped and just restarted awaiting.
1290
+ * The `Promise` passed to `Timer.withTimeout` cannot be reused
1291
+ * in next call to this method, to avoid memory leaks caused
1292
+ * by `Promise.race`. */
1293
+ const waiter = new DeferredPromise ( ) ;
1294
+ const waiterNode = this . #queueWaiters. addLast ( waiter ) ;
1295
+ await Timer . withTimeout ( 1000 , waiter ) ;
1296
+
1297
+ /* Resolves the "extra" promise that has been spawned when creating the timer. */
1298
+ waiter . resolve ( ) ;
1299
+ this . #queueWaiters. remove ( waiterNode ) ;
1292
1300
}
1293
1301
}
1294
1302
@@ -1374,10 +1382,7 @@ class Consumer {
1374
1382
let interval = Number ( cacheExpiration - now ) / 1e6 ;
1375
1383
if ( interval < 100 )
1376
1384
interval = 100 ;
1377
- const promises = Promise . race ( [ this . #workerTerminationScheduled,
1378
- this . #maxPollIntervalRestart] ) ;
1379
- await Timer . withTimeout ( interval ,
1380
- promises ) ;
1385
+ await Timer . withTimeout ( interval , this . #maxPollIntervalRestart) ;
1381
1386
if ( this . #maxPollIntervalRestart. resolved )
1382
1387
this . #maxPollIntervalRestart = new DeferredPromise ( ) ;
1383
1388
}
0 commit comments