mongos segfaults disposing a sharded merge cursor with a detached OpCtx

XMLWordPrintableJSON

    • Query Integration
    • Fully Compatible
    • ALL
    • v9.0, v8.3
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      A sharded aggregation crashes mongos during cursor teardown: MergeCursorsStage::doDispose() calls BlockingResultsMerger::kill() with a null OperationContext.

      Invalid access at address: 0x204
      Got signal: 11 (Segmentation fault).
      mongo::exec::agg::MergeCursorsStage::doDispose()
      BlockingResultsMerger::kill(mongo::OperationContext*)
      

      Trigger
      mongos fills a batch with a count-bounded loop:
      for (long long objCount = 0; objCount < batchSize; ++objCount)

      { next = ccc->next(); ... }

      The crash needs the results to exactly fill the final batch (count == batchSize) with the cursor then at EOF. The loop stops on the count bound without ever pulling the trailing EOF, so the exec pipeline is never disposed while attached. The cursor is marked exhausted by a follow-up isEOF() peek, is not registered, and is destroyed at function end – its destructor disposes the pipeline after the opCtx was already detached.

      Minimal example (sharded):

      db.coll.aggregate([<deterministic source>, {$limit: 1}], {cursor: {batchSize: 1}})
       

      1 result, batchSize 1 -> the loop runs once, appends the doc, exits at objCount == 1 without pulling EOF -> the isEOF() peek marks the cursor exhausted -> it is not registered -> disposed from its destructor after detach -> crash.

      Safe cases: fewer results than batchSize pull the EOF (pipeline disposed while attached). More results register the cursor for a getMore. Only the exactly-full terminal batch crashes.

      Root cause
      establishMergingMongosCursor() (cluster_aggregation_planner.cpp) called ccc->detachFromOperationContext() unconditionally, before the exhausted check. An exhausted cursor is not registered, so its pipeline is disposed from the destructor, which needs a valid opCtx to kill the remote cursors.

      Fix

      • Detach only inside the "register a cursor" branch, so an exhausted cursor is disposed while still
        attached.
      • Backstop: null-guard the opCtx in MergeCursorsStage::doDispose() (mirrors
        ~DocumentSourceMergeCursors), skipping the kill instead of dereferencing null.

      Why now ?
      Latent on master since git commit e76af41017f (2025-12-09). Not specific to any stage. $extensionMultiStream is used in the regression test only because it emits a deterministic doc count, making the count == batchSize alignment reproducible. Coverage: DisposeAfterDetachDoesNotCrash an jstests/extensions/document_results_and_metadata_sharded_teardown.js .

            Assignee:
            Aditya Deshpande
            Reporter:
            Aditya Deshpande
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: