Skip to content

fix(NODE-7893): keep getMore timeout propagation protocol-valid - #5076

Open
philkunz wants to merge 1 commit into
mongodb:mainfrom
philkunz:fix/getmore-maxtimems-transaction
Open

philkunz wants to merge 1 commit into
mongodb:mainfrom
philkunz:fix/getmore-maxtimems-transaction

Conversation

@philkunz

@philkunz philkunz commented Oct 9, 2026

Copy link
Copy Markdown

Description

Summary of Changes

Enforce the getMore timeout contract in GetMoreOperation, independent of whether
CSOT comes from the cursor, client, session default, or withTransaction:

  • Always omit CSOT-derived maxTimeMS from getMore command options while retaining
    the same client-side timeout context.
  • Emit explicit maxAwaitTimeMS as command maxTimeMS only for a cursor that is
    both tailable and awaitData. Change streams already supply both flags.
  • Add unit coverage beside the existing getMore operation tests and integration
    coverage for find/aggregation cursors inheriting both transaction and session
    deadlines.
Notes for Reviewers

We could not open a NODE Jira ticket. GitHub issues are disabled for this
repository, so this PR includes the complete motivation and reproduction below.
Please associate a NODE ticket and add its scope to the title if required.
No new TODOs, dependencies, package metadata, build scripts, generated files, or
vendored specification fixtures are changed.

The CSOT specification for non-tailable cursors
requires retaining the remaining client timeout without appending maxTimeMS to
getMore. The correction preserves server-selection, checkout, round-trip and
transaction deadline enforcement; it does not disable CSOT or relax server
validation.

What is the motivation for this change?

A bounded transaction cannot read a normal cursor past its initial batch when
the deadline is inherited from the session. Even an ordinary 250-document read
then fails on the first getMore. This affects find and aggregation cursors and
occurs against MongoDB itself, without any application persistence abstraction.

For bug fixes

Current (incorrect) behavior:

withTransaction stores its deadline on ClientSession.timeoutContext. A cursor
without a local timeoutMS sets omitMaxTimeMS to false in
AbstractCursor.cursorInit(). The operation inherits the session timeout and the
connection consequently appends the remaining budget to a normal getMore.
MongoDB rejects the command with code 2 (BadValue):
cannot set maxTimeMS on getMore command for a non-awaitData cursor.

Expected behavior:

All documents are returned and the transaction commits within its client-side
deadline. A non-tailable getMore never carries maxTimeMS; a tailable awaitData
cursor retains its explicit await timeout.

How to reproduce:

Use a disposable MongoDB 8.0.26 single-member replica set and set MONGODB_URI to
its connection string. Install the official driver with pnpm add mongodb@7.6.0
and run this driver-only CommonJS script:

const { MongoClient } = require('mongodb');

async function main() {
  const client = new MongoClient(process.env.MONGODB_URI, { monitorCommands: true });
  client.on('commandStarted', event => {
    if (event.commandName === 'getMore') {
      console.log({ command: event.commandName, maxTimeMS: event.command.maxTimeMS });
    }
  });
  await client.connect();
  const collection = client.db('transaction_cursor_repro').collection('records');
  const session = client.startSession();
  try {
    await collection.deleteMany({});
    await collection.insertMany(Array.from({ length: 250 }, (_, index) => ({ _id: index })));
    const records = await session.withTransaction(
      () => collection.find({}, { session }).toArray(),
      { timeoutMS: 30_000 }
    );
    console.log(records.length);
  } finally {
    await session.endSession();
    await collection.drop();
    await client.close();
  }
}

main().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

The first getMore carries approximately 29998 ms and fails with the error above.
Removing the transaction's timeout makes the same read succeed. With this fix,
the read returns 250 documents and command monitoring shows a getMore without
maxTimeMS.

Affected versions/environment:

  • Reproduced with official driver 7.6.0 and the unchanged upstream-main source at
    16136bb34; the published 7.7.0 source retains the same omission condition.
  • Node.js 25.9.0, MongoDB 8.0.26, single-member replica set, authentication disabled
    only for the disposable qualification fixture.
  • The branch is based on upstream main, not on a temporary-fork release.

Tests

  • Nine native getMore/cursor/session/timeout/killCursors unit files, run separately:
    126 passing, 1 existing pending.
  • New transaction cursor integration: 4 passing (find and aggregate, each
    with a withTransaction timeout and a session-default timeout). These assert
    complete results, legal getMore commands, bounded initial commands and a
    committed transactional write.
  • Existing node_csot.test.ts: 39 passing, including stalled-operation and
    transaction deadline behavior.
  • Driver-only reproduction: 250 documents, one legal getMore. The same
    reproduction on the unchanged upstream-main build fails with the expected
    server BadValue.
  • Production typecheck with --skipLibCheck false, source/build/declaration
    extraction, native tsd (39 type-test files), generated-declaration check,
    full source/test ESLint and changed-file Prettier all pass.
  • The extra broad test/tsconfig.json check with --skipLibCheck false exhausts
    Node's default 4-GiB heap on both this branch and an independently installed
    pristine upstream-main baseline. The driver's native public-type test workflow
    passes; no typecheck or lint configuration is changed to mask this limitation.

All commands use pnpm. Scripts that nest npm were expanded into their documented
underlying commands rather than changing the repository's scripts. Integration
checks use a disposable MongoDB 8.0.26 fixture; no full topology/server-version
matrix is claimed.

Release Highlight

Left for the Node driver maintainers, as requested by the PR template.

Double check the following

  • Lint is passing (pnpm equivalents of the native build/declaration/type/lint commands).
  • Self-review completed using the CONTRIBUTING reviewer guidelines.
  • Suggested title uses Conventional Commits; NODE scope awaits a maintainer-created ticket.
  • Changes are covered by tests.
  • No new TODOs are introduced.

@philkunz
philkunz requested a review from a team as a code owner October 9, 2026 23:33
@PavelSafronov PavelSafronov changed the title fix: keep getMore timeout propagation protocol-valid fix(NODE-7893): keep getMore timeout propagation protocol-valid Oct 10, 2026
@PavelSafronov PavelSafronov added tracked-in-jira Ticket filed in MongoDB's Jira system External Submission PR submitted from outside the team labels Oct 10, 2026
@PavelSafronov

Copy link
Copy Markdown
Contributor

@philkunz thanks for the detailed write-up and the reproduction script! It helps us to have full motivation context and makes the PR made easier to follow. I’ve linked this to NODE-7893 and updated the title.
The team triages incoming tickets weekly, and a maintainer will follow up with review feedback once it’s been prioritized.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Submission PR submitted from outside the team tracked-in-jira Ticket filed in MongoDB's Jira system

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants