From e028d6213acc86e623d4fbc1e0b772db5be806ee Mon Sep 17 00:00:00 2001 From: Ian Boros <87138302+borosaurus@users.noreply.github.com> Date: Wed, 20 May 2026 10:27:14 -0400 Subject: [PATCH] SERVER-111641 Update SBE open()/close() behavior documentation (#53972) GitOrigin-RevId: 73ca58a2e60ea1220e249f3094f338f1da60b806 --- src/mongo/db/exec/sbe/README.md | 14 ++++++++++++-- src/mongo/db/exec/sbe/stages/stages.h | 12 +++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/mongo/db/exec/sbe/README.md b/src/mongo/db/exec/sbe/README.md index ad1f70cd3c4..54c939f5fbe 100644 --- a/src/mongo/db/exec/sbe/README.md +++ b/src/mongo/db/exec/sbe/README.md @@ -227,8 +227,18 @@ acquired during `open()`. Acquiring resources for query execution can include ac needs to sort all of the values produced by its children (either in memory or on disk) before it can produce results in sorted order. -It is only legal to call `close()` on PlanStages that have called `open()`, and to call `open()` on -PlanStages that are closed. In some cases (such as in +`close()` may be called at any time after `prepare()` — it is idempotent, so calling it multiple +times is always safe. Callers are not required to call `close()` before destroying the plan tree, +but are encouraged to do so as soon as execution is complete and the plan tree is only needed for +its accumulated statistics (for example, call `close()` before collecting `explain` output to +release runtime resources while keeping the plan tree alive for inspection). + +`open()` is not all-or-nothing. If `open()` throws an exception, the plan tree may be left in a +partially open state: some child stages may have already acquired resources while others have not. +In that case, the only legal operations are to destroy the plan or to call `close()` followed by +destruction. + +In some cases (such as in [`LoopJoinStage`](https://github.com/mongodb/mongo/blob/06a931ffadd7ce62c32288d03e5a38933bd522d3/src/mongo/db/exec/sbe/stages/loop_join.cpp#L158)), a parent stage may `open()` and `close()` a child stage repeatedly. However, doing so may be expensive and ultimately redundant. This is where the `reOpen` parameter of `open()` comes in: when diff --git a/src/mongo/db/exec/sbe/stages/stages.h b/src/mongo/db/exec/sbe/stages/stages.h index 680ad9ca6d5..87592ca89be 100644 --- a/src/mongo/db/exec/sbe/stages/stages.h +++ b/src/mongo/db/exec/sbe/stages/stages.h @@ -732,6 +732,10 @@ public: * When reOpen flag is true then the plan stage should reinitizalize already acquired resources * (e.g. re-hash, re-sort, re-seek, etc), but it can avoid reinitializing things that do not * contain state and are not destroyed by close(), since close() is not called before a reopen. + * + * open() is not all-or-nothing. If open() throws, the plan stage may be left in a "partially + * open" state. The only legal operations on a partially-open stage are to destroy it (let the + * destructor run), or to call close() and then destroy it. */ virtual void open(bool reOpen) = 0; @@ -757,7 +761,13 @@ public: virtual PlanState getNext() = 0; /** - * The mirror method to open(). It releases any acquired resources. + * Releases any resources acquired by open(). This method is idempotent: it is safe to call + * close() multiple times, and safe to call close() on a stage that was never opened. Callers + * are not required to call close() before destroying a plan stage, but are encouraged to do so + * whenever they have a plan that is no longer actively executing and want to release runtime + * resources (e.g. storage-engine cursors, memory buffers) without destroying the plan tree + * itself (for example, call close() before collecting explain output so that runtime resources + * are released while the plan tree remains available for inspection). */ virtual void close() = 0;