Cursors remain open when batchSize equals limit with in-memory sort

    • Type: Bug
    • Resolution: Unresolved
    • Priority: Major - P3
    • None
    • Affects Version/s: 4.4.9, 7.0.11, 8.0.12
    • Component/s: Query Execution
    • None
    • Query Execution
    • ALL
    • Hide

      This is consistently reproduces when sorting on an unindexed field and setting limit equal to batchSize. A detailed reproduction script is attached (cursor_leak_tests.js).

      This behavior has been confirmed on server versions 8.0.12, 7.0.11, and 4.4.9.

       

      Command:

      mongosh -f cursor_leak_tests.js

       

      Output:
      ```json
      [

      { "scenario": "(1) hasSortStage == true, limit == batchSize == nreturned == 1, COLSCAN", "result": "❌ Cursor Leak!", "planSummary": "COLLSCAN", "openCursorsBefore": 0, "openCursorsAfter": 1, "hasSortStage": true, "limit": 1, "nreturned": 1, "batchSize": 1 }

      ,
      {
      "scenario": "(2) hasSortStage == true, limit == batchSize == nreturned == 1, IXSCAN",
      "result": "❌ Cursor Leak!",
      "planSummary": "IXSCAN

      { category: 1, price: 1 }

      ",
      "openCursorsBefore": 0,
      "openCursorsAfter": 1,
      "hasSortStage": true,
      "limit": 1,
      "nreturned": 1,
      "batchSize": 1
      },
      {
      "scenario": "(3) hasSortStage == true, limit == nreturned == 1, batchSize == 2, IXSCAN",
      "result": "✅ No cursor leak.",
      "planSummary": "IXSCAN

      { category: 1, price: 1 }

      ",
      "openCursorsBefore": 0,
      "openCursorsAfter": 0,
      "hasSortStage": true,
      "limit": 1,
      "nreturned": 1,
      "batchSize": 2
      },
      {
      "scenario": "(4) hasSortStage == false, limit == batchSize == nreturned == 1, IXSCAN",
      "result": "✅ No cursor leak.",
      "planSummary": "IXSCAN

      { category: 1, price: 1 }

      ",
      "openCursorsBefore": 0,
      "openCursorsAfter": 0,
      "hasSortStage": false,
      "limit": 1,
      "nreturned": 1,
      "batchSize": 1
      }
      ]
      ```

      Show
      This is consistently reproduces when sorting on an unindexed field and setting limit equal to batchSize . A detailed reproduction script is attached ( cursor_leak_tests.js ). This behavior has been confirmed on server versions 8.0.12, 7.0.11, and 4.4.9.   Command: mongosh -f cursor_leak_tests.js   Output: ```json [ { "scenario": "(1) hasSortStage == true, limit == batchSize == nreturned == 1, COLSCAN", "result": "❌ Cursor Leak!", "planSummary": "COLLSCAN", "openCursorsBefore": 0, "openCursorsAfter": 1, "hasSortStage": true, "limit": 1, "nreturned": 1, "batchSize": 1 } , { "scenario": "(2) hasSortStage == true, limit == batchSize == nreturned == 1, IXSCAN", "result": "❌ Cursor Leak!", "planSummary": "IXSCAN { category: 1, price: 1 } ", "openCursorsBefore": 0, "openCursorsAfter": 1, "hasSortStage": true, "limit": 1, "nreturned": 1, "batchSize": 1 }, { "scenario": "(3) hasSortStage == true, limit == nreturned == 1, batchSize == 2, IXSCAN", "result": "✅ No cursor leak.", "planSummary": "IXSCAN { category: 1, price: 1 } ", "openCursorsBefore": 0, "openCursorsAfter": 0, "hasSortStage": true, "limit": 1, "nreturned": 1, "batchSize": 2 }, { "scenario": "(4) hasSortStage == false, limit == batchSize == nreturned == 1, IXSCAN", "result": "✅ No cursor leak.", "planSummary": "IXSCAN { category: 1, price: 1 } ", "openCursorsBefore": 0, "openCursorsAfter": 0, "hasSortStage": false, "limit": 1, "nreturned": 1, "batchSize": 1 } ] ```
    • None
    • 3
    • TBD
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      MongoDB leaves a server-side cursor open (returning a non-zero cursor ID) when all the following conditions are met:

      • A find command is issued with limit and batchSize set to the same value.
      • The query requires an in-memory sort stage (i.e., explain() output shows hasSortStage: true).
      • The number of documents returned (nreturned) equals the limit.

       

      Expected Behavior:

      The server should automatically close the cursor, since the query's limit has been fully satisfied.

       

      Actual Behavior:

      A cursor is opened on the server and its ID is returned to the client. This is unexpected and requires clients to explicitly kill the cursor for a query they would assume is complete. If the cursor is not explicitly closed, it remains active on the server until it times out or the client disconnects, causing a resource leak.

              Assignee:
              Unassigned
              Reporter:
              Jon Mellman
              Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

                Created:
                Updated: