@@ -2,6 +2,7 @@ const { hrtime } = require('process');
22const {
33 partitionKey,
44} = require ( './_common' ) ;
5+ const { Heap } = require ( './_heap' ) ;
56
67/**
78 * A PerPartitionMessageCache is a cache for messages for a single partition.
@@ -62,13 +63,13 @@ class PerPartitionMessageCache {
6263 */
6364class MessageCache {
6465
65- constructor ( expiryDurationMs ) {
66+ constructor ( expiryDurationMs , maxConcurrency ) {
6667 /* Per partition cache list containing non-empty PPCs */
6768 this . ppcList = [ ] ;
6869 /* Map of topic+partition to PerPartitionMessageCache. */
6970 this . tpToPpc = new Map ( ) ;
7071 /* Index of the current PPC in the ppcList. */
71- this . currentPpc = 0 ;
72+ this . currentPpcTODO_remove_this = 0 ;
7273 /* Maximum size of the cache. (Capacity) */
7374 this . maxSize = 1 ;
7475 /* Number of times the size has been increased in a row, used for accounting for maxSize. */
@@ -81,6 +82,15 @@ class MessageCache {
8182 this . expiryDurationMs = expiryDurationMs ;
8283 /* A list of caches which have been marked stale since the last call to popLocallyStale or addMessages. */
8384 this . locallyStaleCaches = [ ] ;
85+ /* Max allowed concurrency */
86+ this . maxConcurrency = maxConcurrency ;
87+ /* Contains a list of indices of ppcList from which we are allowed to consume. */
88+ this . indices = new Heap ( ) ;
89+ /* Largest ppc index we are allowed to consume from (inclusive). */
90+ this . maxIndicesIndex = 0 ;
91+ /* Contains a list of indices of ppcList from which we have sent a message returned through next, but
92+ * the user has not returned the index back to us via next(idx) */
93+ this . pendingIndices = new Set ( ) ;
8494 }
8595
8696 /**
@@ -226,7 +236,6 @@ class MessageCache {
226236 * run out of messages. We need to clear them, else #add() will not add
227237 * them back to the ppcList since they're not empty. */
228238 this . ppcList . forEach ( cache => cache . clear ( ) ) ;
229- this . currentPpc = 0 ;
230239 this . ppcList = [ ] ;
231240
232241 if ( this . locallyStaleCaches . length !== 0 && this . locallyStaleCaches . some ( tp => {
@@ -245,34 +254,72 @@ class MessageCache {
245254
246255 // TODO: add ppcList sort step.
247256 // Rationale: ideally it's best to consume in the ascending order of timestamps.
257+
258+ /* Reset the indices and pendingIndices because ppcList is being created newly. */
259+ this . indices . clear ( ) ;
260+ if ( this . pendingIndices . size > 0 ) console . error ( 'addMessages: pendingIndices = ' , this . pendingIndices , console . trace ( ) ) ;
261+ this . pendingIndices . clear ( ) ;
262+ this . maxIndicesIndex = Math . min ( this . maxConcurrency , this . ppcList . length - 1 ) ;
263+ for ( let i = 0 ; i <= this . maxIndicesIndex ; i ++ ) {
264+ this . indices . push ( i ) ;
265+ }
248266 }
249267
250268 /**
251269 * Returns the next element in the cache, or null if none exists.
252270 *
253271 * If the current PPC is exhausted, it moves to the next PPC.
254272 * If all PPCs are exhausted, it returns null.
273+ * @param {number } idx - after a consumer has consumed a message, it must return the index back to us via this parameter.
274+ * otherwise, no messages from that topic partition will be consumed.
275+ * @returns {Object } - the next message in the cache, or null if none exists. An `index` field is added to the message.
255276 * @warning Does not check for global staleness. That is left up to the user.
256277 * Skips locally stale messages.
278+ * The topicPartition, if provided, MUST be one such that the user has fetched
279+ * the message from the same topicPartition earlier.
280+ * @note Whenever making changes to this function, ensure that you benchmark perf.
257281 */
258- next ( ) {
259- if ( this . currentPpc >= this . ppcList . length ) {
260- return null ;
282+ next ( idx = - 1 ) {
283+ let index = idx ;
284+ if ( ! this . pendingIndices . has ( index ) ) {
285+ /* The user is behaving well by returning the index to us, but in the meanwhile, it's possible
286+ * that we ran out of messages and fetched a new batch. So we just discard what the user is
287+ * returning to us. */
288+ index = - 1 ;
289+ } else {
290+ this . pendingIndices . delete ( index ) ;
261291 }
262292
263- let next = null ;
264- while ( next === null && this . currentPpc < this . ppcList . length ) {
265- if ( this . ppcList [ this . currentPpc ] . isStale ( ) ) {
266- this . currentPpc ++ ;
293+ if ( index === - 1 ) {
294+ if ( this . indices . size ( ) === 0 )
295+ return null ;
296+ index = this . indices . pop ( ) ; // index cannot be undefined here since indices.size > 0
297+ }
298+
299+ while ( true ) {
300+ const next = this . ppcList [ index ] . next ( ) ;
301+ if ( this . ppcList [ index ] . isStale ( ) || next === null ) {
302+ /* If the current PPC is stale or empty, then we move on to the next one.
303+ * It is equally valid to choose any PPC available within this.indices, or else
304+ * move on to the next PPC (maxIndicesIndex + 1) if available.
305+ * We prefer the second option a bit more since we don't have to do a heap operation. */
306+ const toAdd = this . maxIndicesIndex + 1 ;
307+ if ( toAdd < this . ppcList . length ) {
308+ this . maxIndicesIndex = toAdd ;
309+ index = toAdd ;
310+ } else if ( ! this . indices . isEmpty ( ) ) {
311+ index = this . indices . pop ( )
312+ } else {
313+ break ; // nothing left.
314+ }
267315 continue ;
268316 }
269317
270- next = this . ppcList [ this . currentPpc ] . next ( ) ;
271- if ( next !== null )
272- break ;
273- this . currentPpc ++ ;
318+ this . pendingIndices . add ( index ) ;
319+ next . index = index ;
320+ return next ;
274321 }
275- return next ; // Caller is responsible for triggering fetch logic here if next == null.
322+ return null ; // Caller is responsible for triggering fetch logic here if next == null.
276323 }
277324
278325 /**
@@ -286,12 +333,15 @@ class MessageCache {
286333 cache . clear ( ) ;
287334 }
288335 this . ppcList = [ ] ;
289- this . currentPpc = 0 ;
290336 this . maxSize = 1 ;
291337 this . increaseCount = 0 ;
292338 this . stale = false ;
293339 this . cachedTime = hrtime ( ) ;
294340 this . locallyStaleCaches = [ ] ;
341+ this . indices . clear ( ) ;
342+ // if (this.pendingIndices.size > 0) console.log('clear: pendingIndices = ', this.pendingIndices, console.);
343+ this . pendingIndices . clear ( ) ;
344+ this . currentIndex = 0 ;
295345 }
296346}
297347
0 commit comments