mongodb/node-mongodb-native · error · MongoAPIError

Cannot call abort() on a stream twice

Error message

Cannot call abort() on a stream twice

What it means

GridFSBucketWriteStream.abort() (src/gridfs/upload.ts:207) throws MongoAPIError 'Cannot call abort() on a stream twice' if abort has already been invoked (state.aborted === true). Abort is a one-time operation that deletes already-written chunks; calling it again is a logic error.

Solutions

  1. Track whether abort has been called with a boolean flag and skip subsequent calls.
  2. Use a single owner for the abort decision (either the AbortSignal handler or the finally block, not both).
  3. Catch this specific error and ignore it if defensive double-abort is unavoidable.

Example fix

// before
await s.abort();
await s.abort(); // throws
// after
let aborted = false;
async function safeAbort() { if (!aborted) { aborted = true; await s.abort(); } }
await safeAbort();
await safeAbort();
Defensive patterns

Strategy: validation

Validate before calling

function onceAbort(stream) {
  let called = false;
  return async () => {
    if (called) return;
    called = true;
    return stream.abort();
  };
}

Type guard

function isStreamAborted(stream: GridFSBucketWriteStream): boolean {
  return (stream as any).state?.aborted === true;
}

Try / catch

try {
  await stream.abort();
} catch (e) {
  if (e instanceof MongoAPIError && /abort\(\) on a stream twice/.test(e.message)) {
    // no-op: already aborted
  } else throw e;
}

Prevention

When it happens

Trigger: Calling uploadStream.abort() two or more times. Multiple cleanup handlers (e.g. signal listener + finally) both invoking abort on the same stream.

Common situations: An AbortSignal listener and a try/finally both calling abort. Retry/cancellation logic that does not track whether abort already fired.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/b89587543a1068bb. Report an issue: GitHub.

Appendix: source

Thrown at src/gridfs/upload.ts:209

      return queueMicrotask(callback);
    }
    this.state.streamEnd = true;
    writeRemnant(this, callback);
  }

  /**
   * Places this write stream into an aborted state (all future writes fail)
   * and deletes all chunks that have already been written.
   */
  async abort(): Promise<void> {
    if (this.state.streamEnd) {
      // TODO(NODE-3485): Replace with MongoGridFSStreamClosed
      throw new MongoAPIError('Cannot abort a stream that has already completed');
    }

    if (this.state.aborted) {
      // TODO(NODE-3485): Replace with MongoGridFSStreamClosed
      throw new MongoAPIError('Cannot call abort() on a stream twice');
    }

    this.state.aborted = true;
    const remainingTimeMS = this.timeoutContext?.getRemainingTimeMSOrThrow(
      `Upload timed out after ${this.timeoutContext?.timeoutMS}ms`
    );

    await this.chunks.deleteMany({ files_id: this.id }, { timeoutMS: remainingTimeMS });
  }
}

function handleError(stream: GridFSBucketWriteStream, error: Error, callback: Callback): void {
  if (stream.state.errored) {
    queueMicrotask(callback);
    return;
  }
  stream.state.errored = true;
  queueMicrotask(() => callback(error));

View on GitHub (pinned to dce7939f86)