The PlanExecutor created by the mapReduce command here is managed by the collection over which the mapReduce is running. This means that a lock on the collection must be held while the PlanExecutor is being disposed, so that deregistration of the executor is synchronized correctly with catalog-level events such as collection drops. The following dassert() checks that the correct locks are held in debug builds:
The mapReduce code holds the proper locks for most of its execution, but these locks are temporarily released in order to perform the reduce step. It is possible for an exception to be thrown by
State::reduceAndSpillInMemoryStateIfNeeded(), in which case the PlanExecutor will be incorrectly disposed out of the lock.
Our continuous integration testing has caught this specifically in the case of a mapReduce running while the mongod is shutting down, since this causes State::reduceAndSpillInMemoryStateIfNeeded() to throw with an InterruptedAtShutdown error. The result is an invariant() failure in a debug build, though the system could also crash in a non-debug build due to using a Collection object that has been freed.