/** * Confirms that a parent operation correctly inherits 'numYields' from each of its child operations * as the latter are popped off the CurOp stack. */ (function() { "use strict"; load("jstests/libs/curop_helpers.js"); // For waitForCurOpByFailPoint(). // Start a single mongoD using MongoRunner. const conn = MongoRunner.runMongod({}); assert.neq(null, conn, "mongod was unable to start up"); // Create the test DB and collection. const testDB = conn.getDB("currentop_yield"); const adminDB = conn.getDB("admin"); const testColl = testDB.test; // Executes a bulk remove using the specified 'docsToRemove' array, captures the 'numYields' // metrics from each child op, and confirms that the parent op's 'numYields' total is equivalent // to the sum of the child ops. function runYieldTest(docsToRemove) { // Sets parameters such that all operations will yield & the operation hangs on the server // when we need to test. assert.commandWorked( testDB.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 1})); assert.commandWorked(testDB.adminCommand( {configureFailPoint: "hangBeforeChildRemoveOpFinishes", mode: "alwaysOn"})); assert.commandWorked(testDB.adminCommand( {configureFailPoint: "hangAfterAllChildRemoveOpsArePopped", mode: "alwaysOn"})); // Starts parallel shell to run the command that will hang. const awaitShell = startParallelShell(`{ const testDB = db.getSiblingDB("currentop_yield"); const bulkRemove = testDB.test.initializeOrderedBulkOp(); for(let doc of ${tojsononeline(docsToRemove)}) { bulkRemove.find(doc).removeOne(); } bulkRemove.execute(); }`, testDB.getMongo().port); let childOpId = null; let childYields = 0; // Get child operations and sum yields. Each child op encounters two failpoints while // running: 'hangBeforeChildRemoveOpFinishes' followed by 'hangBeforeChildRemoveOpIsPopped'. // We use these two failpoints as an 'airlock', hanging at the first while we enable the // second, then hanging at the second while we enable the first, to ensure that each child // op is caught and their individual 'numYields' recorded. for (let childCount = 0; childCount < docsToRemove.length; childCount++) { // Wait for the child op to hit the first of two failpoints. let childCurOp = waitForCurOpByFailPoint( testDB, testColl.getFullName(), "hangBeforeChildRemoveOpFinishes")[0]; // Add the child's yield count to the running total, and record the opid. assert(childOpId === null || childOpId === childCurOp.opid); assert.gt(childCurOp.numYields, 0); childYields += childCurOp.numYields; childOpId = childCurOp.opid; // Enable the subsequent 'hangBeforeChildRemoveOpIsPopped' failpoint, just after the // child op finishes but before it is popped from the stack. assert.commandWorked(testDB.adminCommand( {configureFailPoint: "hangBeforeChildRemoveOpIsPopped", mode: "alwaysOn"})); // Let the operation proceed to the 'hangBeforeChildRemoveOpIsPopped' failpoint. assert.commandWorked(testDB.adminCommand( {configureFailPoint: "hangBeforeChildRemoveOpFinishes", mode: "off"})); waitForCurOpByFailPoint(testDB, testColl.getFullName(), "hangBeforeChildRemoveOpIsPopped"); // If this is not the final child op, re-enable the 'hangBeforeChildRemoveOpFinishes' // failpoint from earlier so that we don't miss the next child. if (childCount + 1 < docsToRemove.length) { assert.commandWorked(testDB.adminCommand( {configureFailPoint: "hangBeforeChildRemoveOpFinishes", mode: "alwaysOn"})); } // Finally, allow the operation to continue. assert.commandWorked(testDB.adminCommand( {configureFailPoint: "hangBeforeChildRemoveOpIsPopped", mode: "off"})); } // Wait for the operation to hit the 'hangAfterAllChildRemoveOpsArePopped' failpoint, then // take the total number of yields recorded by the parent op. const parentCurOp = waitForCurOpByFailPointNoNS( testDB, "hangAfterAllChildRemoveOpsArePopped", {opid: childOpId})[0]; // Verify that the parent's yield count equals the sum of the child ops' yields. assert.eq(parentCurOp.numYields, childYields); assert.eq(parentCurOp.opid, childOpId); // Allow the parent operation to complete. assert.commandWorked(testDB.adminCommand( {configureFailPoint: "hangAfterAllChildRemoveOpsArePopped", mode: "off"})); // Wait for the parallel shell to complete. awaitShell(); } // Test that a parent remove op inherits the sum of its children's yields for a single remove. assert.commandWorked(testDB.test.insert({a: 2})); runYieldTest([{a: 2}]); // Test that a parent remove op inherits the sum of its children's yields for multiple removes. const docsToTest = [{a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}]; assert.commandWorked(testDB.test.insert(docsToTest)); runYieldTest(docsToTest); MongoRunner.stopMongod(conn); })();