@platformatic/kafka 1.32.1 → 1.33.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -425,6 +425,7 @@ export class MessagesStream extends Readable {
425
425
  const headerKeyDeserializer = this.#headerKeyDeserializer;
426
426
  const headerValueDeserializer = this.#headerValueDeserializer;
427
427
  let diagnosticContext;
428
+ let canPush = true;
428
429
  const messageMetadata = {
429
430
  consumer: {
430
431
  groupId: this.#consumer.groupId,
@@ -507,7 +508,7 @@ export class MessagesStream extends Readable {
507
508
  };
508
509
  diagnosticContext.result = message;
509
510
  consumerReceivesChannel.asyncStart.publish(diagnosticContext);
510
- this.push(message);
511
+ canPush = this.push(message);
511
512
  consumerReceivesChannel.asyncEnd.publish(diagnosticContext);
512
513
  }
513
514
  catch (error) {
@@ -529,19 +530,27 @@ export class MessagesStream extends Readable {
529
530
  if (this.#autocommitEnabled && !this.#autocommitInterval) {
530
531
  this[kAutocommit]();
531
532
  }
532
- // Always schedule the next fetch, even when push() returned false.
533
+ // Schedule the next fetch only when the readable buffer still has room.
534
+ // When push() returns false, the buffer is above highWaterMark —
535
+ // continuing to fetch would defeat Node.js backpressure and cause
536
+ // unbounded memory growth (see #260).
533
537
  //
534
- // In pull mode, _read() would restart the loop once the buffer drains.
535
- // In flowing mode with pipeline(), however, _read() is not reliably called
536
- // again when the buffer is already empty — Node.js considers the stream
537
- // "already flowing" and does not re-invoke _read(). The fetch loop dies
538
- // and unconsumed messages remain in Kafka.
538
+ // When canPush is false, the fetch loop restarts via two mechanisms:
539
+ // 1. _read() called by Node.js when the buffer drains below
540
+ // highWaterMark in both pull and flowing modes.
541
+ // 2. resume() when pipeline()/pipe() transitions from paused to
542
+ // unpaused after downstream backpressure releases, resume()
543
+ // explicitly schedules process.nextTick(#fetch) (see #254).
539
544
  //
540
- // Unconditionally scheduling is safe because #fetch() checks #paused,
541
- // #closed, and other guards before issuing a Kafka fetch request.
542
- process.nextTick(() => {
543
- this.#fetch();
544
- });
545
+ // process.nextTick yields control back to the event loop between fetch
546
+ // cycles, ensuring heartbeats, commits, and other I/O are not starved
547
+ // by a tight fetch loop. It also avoids growing the call stack if
548
+ // metadata/fetch callbacks fire synchronously.
549
+ if (canPush) {
550
+ process.nextTick(() => {
551
+ this.#fetch();
552
+ });
553
+ }
545
554
  if (this.#maxFetches > 0 && ++this.#fetches >= this.#maxFetches) {
546
555
  this.push(null);
547
556
  }
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = "@platformatic/kafka";
2
- export const version = "1.32.1";
2
+ export const version = "1.33.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/kafka",
3
- "version": "1.32.1",
3
+ "version": "1.33.2",
4
4
  "description": "Modern and performant client for Apache Kafka",
5
5
  "homepage": "https://github.com/platformatic/kafka",
6
6
  "author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",