PlanStage::StageState MultiIteratorStage::work(WorkingSetID* out) { if (_collection == NULL) return PlanStage::DEAD; // The RecordId we're about to look at it might not be in memory. In this case // we request a yield while we fetch the document. if (!_iterators.empty()) { RecordId curr = _iterators.back()->curr(); if (!curr.isNull()) { std::auto_ptr<RecordFetcher> fetcher(_collection->documentNeedsFetch(_txn, curr)); if (NULL != fetcher.get()) { WorkingSetMember* member = _ws->get(_wsidForFetch); member->loc = curr; // Pass the RecordFetcher off to the WSM on which we're performing the fetch. member->setFetcher(fetcher.release()); *out = _wsidForFetch; return NEED_FETCH; } } } RecordId next = _advance(); if (next.isNull()) return PlanStage::IS_EOF; *out = _ws->allocate(); WorkingSetMember* member = _ws->get(*out); member->loc = next; member->obj = _collection->docFor(_txn, next); member->state = WorkingSetMember::LOC_AND_OBJ; return PlanStage::ADVANCED; }
PlanStage::StageState MultiIteratorStage::work(WorkingSetID* out) { if (_collection == NULL) { Status status(ErrorCodes::InternalError, "MultiIteratorStage died on null collection"); *out = WorkingSetCommon::allocateStatusMember(_ws, status); return PlanStage::DEAD; } boost::optional<Record> record; try { while (!_iterators.empty()) { if (auto fetcher = _iterators.back()->fetcherForNext()) { // Pass the RecordFetcher off up. WorkingSetMember* member = _ws->get(_wsidForFetch); member->setFetcher(fetcher.release()); *out = _wsidForFetch; return NEED_YIELD; } record = _iterators.back()->next(); if (record) break; _iterators.pop_back(); } } catch (const WriteConflictException& wce) { // If _advance throws a WCE we shouldn't have moved. invariant(!_iterators.empty()); *out = WorkingSet::INVALID_ID; return NEED_YIELD; } if (!record) return IS_EOF; *out = _ws->allocate(); WorkingSetMember* member = _ws->get(*out); member->loc = record->id; member->obj = {_txn->recoveryUnit()->getSnapshotId(), record->data.releaseToBson()}; member->state = WorkingSetMember::LOC_AND_UNOWNED_OBJ; return PlanStage::ADVANCED; }
PlanStage::StageState CollectionScan::doWork(WorkingSetID* out) { if (_isDead) { Status status( ErrorCodes::CappedPositionLost, str::stream() << "CollectionScan died due to position in capped collection being deleted. " << "Last seen record id: " << _lastSeenId); *out = WorkingSetCommon::allocateStatusMember(_workingSet, status); return PlanStage::DEAD; } if ((0 != _params.maxScan) && (_specificStats.docsTested >= _params.maxScan)) { _commonStats.isEOF = true; } if (_commonStats.isEOF) { return PlanStage::IS_EOF; } boost::optional<Record> record; const bool needToMakeCursor = !_cursor; try { if (needToMakeCursor) { const bool forward = _params.direction == CollectionScanParams::FORWARD; _cursor = _params.collection->getCursor(getOpCtx(), forward); if (!_lastSeenId.isNull()) { invariant(_params.tailable); // Seek to where we were last time. If it no longer exists, mark us as dead // since we want to signal an error rather than silently dropping data from the // stream. This is related to the _lastSeenId handling in invalidate. Note that // we want to return the record *after* this one since we have already returned // this one. This is only possible in the tailing case because that is the only // time we'd need to create a cursor after already getting a record out of it. if (!_cursor->seekExact(_lastSeenId)) { _isDead = true; Status status(ErrorCodes::CappedPositionLost, str::stream() << "CollectionScan died due to failure to restore " << "tailable cursor position. " << "Last seen record id: " << _lastSeenId); *out = WorkingSetCommon::allocateStatusMember(_workingSet, status); return PlanStage::DEAD; } } return PlanStage::NEED_TIME; } if (_lastSeenId.isNull() && !_params.start.isNull()) { record = _cursor->seekExact(_params.start); } else { // See if the record we're about to access is in memory. If not, pass a fetch // request up. if (auto fetcher = _cursor->fetcherForNext()) { // Pass the RecordFetcher up. WorkingSetMember* member = _workingSet->get(_wsidForFetch); member->setFetcher(fetcher.release()); *out = _wsidForFetch; return PlanStage::NEED_YIELD; } record = _cursor->next(); } } catch (const WriteConflictException& wce) { // Leave us in a state to try again next time. if (needToMakeCursor) _cursor.reset(); *out = WorkingSet::INVALID_ID; return PlanStage::NEED_YIELD; } if (!record) { // We just hit EOF. If we are tailable and have already returned data, leave us in a // state to pick up where we left off on the next call to work(). Otherwise EOF is // permanent. if (_params.tailable && !_lastSeenId.isNull()) { _cursor.reset(); } else { _commonStats.isEOF = true; } return PlanStage::IS_EOF; } _lastSeenId = record->id; WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->recordId = record->id; member->obj = {getOpCtx()->recoveryUnit()->getSnapshotId(), record->data.releaseToBson()}; _workingSet->transitionToRecordIdAndObj(id); return returnIfMatches(member, id, out); }
PlanStage::StageState FetchStage::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (isEOF()) { return PlanStage::IS_EOF; } // Either retry the last WSM we worked on or get a new one from our child. WorkingSetID id; StageState status; if (_idRetrying == WorkingSet::INVALID_ID) { status = child()->work(&id); } else { status = ADVANCED; id = _idRetrying; _idRetrying = WorkingSet::INVALID_ID; } if (PlanStage::ADVANCED == status) { WorkingSetMember* member = _ws->get(id); // If there's an obj there, there is no fetching to perform. if (member->hasObj()) { ++_specificStats.alreadyHasObj; } else { // We need a valid loc to fetch from and this is the only state that has one. verify(WorkingSetMember::LOC_AND_IDX == member->getState()); verify(member->hasLoc()); try { if (!_cursor) _cursor = _collection->getCursor(getOpCtx()); if (auto fetcher = _cursor->fetcherForId(member->loc)) { // There's something to fetch. Hand the fetcher off to the WSM, and pass up // a fetch request. _idRetrying = id; member->setFetcher(fetcher.release()); *out = id; _commonStats.needYield++; return NEED_YIELD; } // The doc is already in memory, so go ahead and grab it. Now we have a RecordId // as well as an unowned object if (!WorkingSetCommon::fetch(getOpCtx(), _ws, id, _cursor)) { _ws->free(id); _commonStats.needTime++; return NEED_TIME; } } catch (const WriteConflictException& wce) { _idRetrying = id; *out = WorkingSet::INVALID_ID; _commonStats.needYield++; return NEED_YIELD; } } return returnIfMatches(member, id, out); } else if (PlanStage::FAILURE == status || PlanStage::DEAD == status) { *out = id; // If a stage fails, it may create a status WSM to indicate why it // failed, in which case 'id' is valid. If ID is invalid, we // create our own error message. if (WorkingSet::INVALID_ID == id) { mongoutils::str::stream ss; ss << "fetch stage failed to read in results from child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember(_ws, status); } return status; } else if (PlanStage::NEED_TIME == status) { ++_commonStats.needTime; } else if (PlanStage::NEED_YIELD == status) { ++_commonStats.needYield; *out = id; } return status; }
PlanStage::StageState IDHackStage::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (_killed) { return PlanStage::DEAD; } if (_done) { return PlanStage::IS_EOF; } if (WorkingSet::INVALID_ID != _idBeingPagedIn) { WorkingSetID id = _idBeingPagedIn; _idBeingPagedIn = WorkingSet::INVALID_ID; WorkingSetMember* member = _workingSet->get(id); WorkingSetCommon::completeFetch(_txn, member, _collection); return advance(id, member, out); } // Use the index catalog to get the id index. const IndexCatalog* catalog = _collection->getIndexCatalog(); // Find the index we use. IndexDescriptor* idDesc = catalog->findIdIndex(_txn); if (NULL == idDesc) { _done = true; return PlanStage::IS_EOF; } // This may not be valid always. See SERVER-12397. const BtreeBasedAccessMethod* accessMethod = static_cast<const BtreeBasedAccessMethod*>(catalog->getIndex(idDesc)); // Look up the key by going directly to the Btree. DiskLoc loc = accessMethod->findSingle(_txn, _key); // Key not found. if (loc.isNull()) { _done = true; return PlanStage::IS_EOF; } ++_specificStats.keysExamined; ++_specificStats.docsExamined; // Create a new WSM for the result document. WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->loc = loc; member->state = WorkingSetMember::LOC_AND_UNOWNED_OBJ; // We may need to request a yield while we fetch the document. std::auto_ptr<RecordFetcher> fetcher(_collection->documentNeedsFetch(_txn, loc)); if (NULL != fetcher.get()) { // There's something to fetch. Hand the fetcher off to the WSM, and pass up a // fetch request. _idBeingPagedIn = id; member->setFetcher(fetcher.release()); *out = id; _commonStats.needFetch++; return NEED_FETCH; } // The doc was already in memory, so we go ahead and return it. member->obj = _collection->docFor(_txn, member->loc); return advance(id, member, out); }
PlanStage::StageState IDHackStage::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (_done) { return PlanStage::IS_EOF; } if (WorkingSet::INVALID_ID != _idBeingPagedIn) { invariant(_recordCursor); WorkingSetID id = _idBeingPagedIn; _idBeingPagedIn = WorkingSet::INVALID_ID; WorkingSetMember* member = _workingSet->get(id); invariant(WorkingSetCommon::fetchIfUnfetched(_txn, member, _recordCursor)); return advance(id, member, out); } WorkingSetID id = WorkingSet::INVALID_ID; try { // Use the index catalog to get the id index. const IndexCatalog* catalog = _collection->getIndexCatalog(); // Find the index we use. IndexDescriptor* idDesc = catalog->findIdIndex(_txn); if (NULL == idDesc) { _done = true; return PlanStage::IS_EOF; } // Look up the key by going directly to the index. RecordId loc = catalog->getIndex(idDesc)->findSingle(_txn, _key); // Key not found. if (loc.isNull()) { _done = true; return PlanStage::IS_EOF; } ++_specificStats.keysExamined; ++_specificStats.docsExamined; // Create a new WSM for the result document. id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->state = WorkingSetMember::LOC_AND_IDX; member->loc = loc; if (!_recordCursor) _recordCursor = _collection->getCursor(_txn); // We may need to request a yield while we fetch the document. if (auto fetcher = _recordCursor->fetcherForId(loc)) { // There's something to fetch. Hand the fetcher off to the WSM, and pass up a // fetch request. _idBeingPagedIn = id; member->setFetcher(fetcher.release()); *out = id; _commonStats.needYield++; return NEED_YIELD; } // The doc was already in memory, so we go ahead and return it. if (!WorkingSetCommon::fetch(_txn, member, _recordCursor)) { // _id is immutable so the index would return the only record that could // possibly match the query. _workingSet->free(id); _commonStats.isEOF = true; _done = true; return IS_EOF; } return advance(id, member, out); } catch (const WriteConflictException& wce) { // Restart at the beginning on retry. _recordCursor.reset(); if (id != WorkingSet::INVALID_ID) _workingSet->free(id); *out = WorkingSet::INVALID_ID; _commonStats.needYield++; return NEED_YIELD; } }
PlanStage::StageState IDHackStage::doWork(WorkingSetID* out) { if (_done) { return PlanStage::IS_EOF; } if (WorkingSet::INVALID_ID != _idBeingPagedIn) { invariant(_recordCursor); WorkingSetID id = _idBeingPagedIn; _idBeingPagedIn = WorkingSet::INVALID_ID; invariant(WorkingSetCommon::fetchIfUnfetched(getOpCtx(), _workingSet, id, _recordCursor)); WorkingSetMember* member = _workingSet->get(id); return advance(id, member, out); } WorkingSetID id = WorkingSet::INVALID_ID; try { // Look up the key by going directly to the index. RecordId recordId = _accessMethod->findSingle(getOpCtx(), _key); // Key not found. if (recordId.isNull()) { _done = true; return PlanStage::IS_EOF; } ++_specificStats.keysExamined; ++_specificStats.docsExamined; // Create a new WSM for the result document. id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->recordId = recordId; _workingSet->transitionToRecordIdAndIdx(id); if (!_recordCursor) _recordCursor = _collection->getCursor(getOpCtx()); // We may need to request a yield while we fetch the document. if (auto fetcher = _recordCursor->fetcherForId(recordId)) { // There's something to fetch. Hand the fetcher off to the WSM, and pass up a // fetch request. _idBeingPagedIn = id; member->setFetcher(fetcher.release()); *out = id; return NEED_YIELD; } // The doc was already in memory, so we go ahead and return it. if (!WorkingSetCommon::fetch(getOpCtx(), _workingSet, id, _recordCursor)) { // _id is immutable so the index would return the only record that could // possibly match the query. _workingSet->free(id); _commonStats.isEOF = true; _done = true; return IS_EOF; } return advance(id, member, out); } catch (const WriteConflictException& wce) { // Restart at the beginning on retry. _recordCursor.reset(); if (id != WorkingSet::INVALID_ID) _workingSet->free(id); *out = WorkingSet::INVALID_ID; return NEED_YIELD; } }
PlanStage::StageState CollectionScan::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (_isDead) { return PlanStage::DEAD; } // Do some init if we haven't already. if (NULL == _iter) { if ( _params.collection == NULL ) { _isDead = true; return PlanStage::DEAD; } try { if (_lastSeenLoc.isNull()) { _iter.reset( _params.collection->getIterator( _txn, _params.start, _params.direction ) ); } else { invariant(_params.tailable); _iter.reset( _params.collection->getIterator( _txn, _lastSeenLoc, _params.direction ) ); // Advance _iter past where we were last time. If it returns something else, // mark us as dead since we want to signal an error rather than silently // dropping data from the stream. This is related to the _lastSeenLock handling // in invalidate. if (_iter->getNext() != _lastSeenLoc) { _isDead = true; return PlanStage::DEAD; } } } catch (const WriteConflictException& wce) { // Leave us in a state to try again next time. _iter.reset(); *out = WorkingSet::INVALID_ID; return PlanStage::NEED_YIELD; } ++_commonStats.needTime; return PlanStage::NEED_TIME; } // Should we try getNext() on the underlying _iter? if (isEOF()) return PlanStage::IS_EOF; const RecordId curr = _iter->curr(); if (curr.isNull()) { // We just hit EOF if (_params.tailable) _iter.reset(); // pick up where we left off on the next call to work() return PlanStage::IS_EOF; } _lastSeenLoc = curr; // See if the record we're about to access is in memory. If not, pass a fetch request up. // Note that curr() does not touch the record. This way, we are able to yield before // fetching the record. { std::auto_ptr<RecordFetcher> fetcher( _params.collection->documentNeedsFetch(_txn, curr)); if (NULL != fetcher.get()) { WorkingSetMember* member = _workingSet->get(_wsidForFetch); member->loc = curr; // Pass the RecordFetcher off to the WSM. member->setFetcher(fetcher.release()); *out = _wsidForFetch; _commonStats.needYield++; return NEED_YIELD; } } // Do this before advancing because it is more efficient while the iterator is still on this // document. const Snapshotted<BSONObj> obj = Snapshotted<BSONObj>(_txn->recoveryUnit()->getSnapshotId(), _iter->dataFor(curr).releaseToBson()); // Advance the iterator. try { invariant(_iter->getNext() == curr); } catch (const WriteConflictException& wce) { // If getNext thows, it leaves us on the original document. invariant(_iter->curr() == curr); *out = WorkingSet::INVALID_ID; return PlanStage::NEED_YIELD; } WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->loc = curr; member->obj = obj; member->state = WorkingSetMember::LOC_AND_UNOWNED_OBJ; return returnIfMatches(member, id, out); }
PlanStage::StageState FetchStage::doWork(WorkingSetID* out) { if (isEOF()) { return PlanStage::IS_EOF; } // Either retry the last WSM we worked on or get a new one from our child. WorkingSetID id; StageState status; if (_idRetrying == WorkingSet::INVALID_ID) { status = child()->work(&id); } else { status = ADVANCED; id = _idRetrying; _idRetrying = WorkingSet::INVALID_ID; } if (PlanStage::ADVANCED == status) { WorkingSetMember* member = _ws->get(id); // If there's an obj there, there is no fetching to perform. if (member->hasObj()) { ++_specificStats.alreadyHasObj; } else { // We need a valid RecordId to fetch from and this is the only state that has one. verify(WorkingSetMember::RID_AND_IDX == member->getState()); verify(member->hasRecordId()); try { if (!_cursor) _cursor = _collection->getCursor(getOpCtx()); if (auto fetcher = _cursor->fetcherForId(member->recordId)) { // There's something to fetch. Hand the fetcher off to the WSM, and pass up // a fetch request. _idRetrying = id; member->setFetcher(fetcher.release()); *out = id; return NEED_YIELD; } // The doc is already in memory, so go ahead and grab it. Now we have a RecordId // as well as an unowned object if (!WorkingSetCommon::fetch(getOpCtx(), _ws, id, _cursor)) { _ws->free(id); return NEED_TIME; } } catch (const WriteConflictException&) { // Ensure that the BSONObj underlying the WorkingSetMember is owned because it may // be freed when we yield. member->makeObjOwnedIfNeeded(); _idRetrying = id; *out = WorkingSet::INVALID_ID; return NEED_YIELD; } } return returnIfMatches(member, id, out); } else if (PlanStage::FAILURE == status || PlanStage::DEAD == status) { // The stage which produces a failure is responsible for allocating a working set member // with error details. invariant(WorkingSet::INVALID_ID != id); *out = id; return status; } else if (PlanStage::NEED_YIELD == status) { *out = id; } return status; }
PlanStage::StageState CollectionScan::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (_isDead) { return PlanStage::DEAD; } // Do some init if we haven't already. if (NULL == _iter) { if ( _params.collection == NULL ) { _isDead = true; return PlanStage::DEAD; } if (_lastSeenLoc.isNull()) { _iter.reset( _params.collection->getIterator( _txn, _params.start, _params.direction ) ); } else { invariant(_params.tailable); _iter.reset( _params.collection->getIterator( _txn, _lastSeenLoc, _params.direction ) ); // Advance _iter past where we were last time. If it returns something else, mark us // as dead since we want to signal an error rather than silently dropping data from // the stream. This is related to the _lastSeenLock handling in invalidate. if (_iter->getNext() != _lastSeenLoc) { _isDead = true; return PlanStage::DEAD; } } ++_commonStats.needTime; return PlanStage::NEED_TIME; } // Should we try getNext() on the underlying _iter? if (isEOF()) return PlanStage::IS_EOF; const DiskLoc curr = _iter->curr(); if (curr.isNull()) { // We just hit EOF if (_params.tailable) _iter.reset(); // pick up where we left off on the next call to work() return PlanStage::IS_EOF; } _lastSeenLoc = curr; // See if the record we're about to access is in memory. If not, pass a fetch request up. // Note that curr() does not touch the record (on MMAPv1 which is the only place we use // NEED_FETCH) so we are able to yield before touching the record, as long as we do so // before calling getNext(). { std::auto_ptr<RecordFetcher> fetcher( _params.collection->documentNeedsFetch(_txn, curr)); if (NULL != fetcher.get()) { WorkingSetMember* member = _workingSet->get(_wsidForFetch); member->loc = curr; // Pass the RecordFetcher off to the WSM. member->setFetcher(fetcher.release()); *out = _wsidForFetch; _commonStats.needFetch++; return NEED_FETCH; } } WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->loc = curr; member->obj = _iter->dataFor(member->loc).releaseToBson(); member->state = WorkingSetMember::LOC_AND_UNOWNED_OBJ; // Advance the iterator. invariant(_iter->getNext() == curr); return returnIfMatches(member, id, out); }
PlanStage::StageState CollectionScan::work(WorkingSetID* out) { ++_commonStats.works; // Adds the amount of time taken by work() to executionTimeMillis. ScopedTimer timer(&_commonStats.executionTimeMillis); if (_isDead) { return PlanStage::DEAD; } // Do some init if we haven't already. if (NULL == _iter) { if ( _params.collection == NULL ) { _isDead = true; return PlanStage::DEAD; } if (_lastSeenLoc.isNull()) { _iter.reset( _params.collection->getIterator( _txn, _params.start, _params.direction ) ); } else { invariant(_params.tailable); _iter.reset( _params.collection->getIterator( _txn, _lastSeenLoc, _params.direction ) ); // Advance _iter past where we were last time. If it returns something else, mark us // as dead since we want to signal an error rather than silently dropping data from // the stream. This is related to the _lastSeenLock handling in invalidate. if (_iter->getNext() != _lastSeenLoc) { _isDead = true; return PlanStage::DEAD; } } ++_commonStats.needTime; return PlanStage::NEED_TIME; } // Should we try getNext() on the underlying _iter? if (isEOF()) return PlanStage::IS_EOF; // See if the record we're about to access is in memory. If not, pass a fetch request up. // Note that curr() returns the same thing as getNext() will, except without advancing the // iterator or touching the DiskLoc. This means that we can use curr() to check whether we // need to fetch on the DiskLoc prior to touching it with getNext(). DiskLoc curr = _iter->curr(); if (!curr.isNull()) { std::auto_ptr<RecordFetcher> fetcher( _params.collection->documentNeedsFetch(_txn, curr)); if (NULL != fetcher.get()) { WorkingSetMember* member = _workingSet->get(_wsidForFetch); member->loc = curr; // Pass the RecordFetcher off to the WSM. member->setFetcher(fetcher.release()); *out = _wsidForFetch; _commonStats.needFetch++; return NEED_FETCH; } } // What we'll return to the user. DiskLoc nextLoc; // See if _iter gives us anything new. nextLoc = _iter->getNext(); if (nextLoc.isNull()) { if (_params.tailable) _iter.reset(); // pick up where we left off on the next call to work() return PlanStage::IS_EOF; } _lastSeenLoc = nextLoc; WorkingSetID id = _workingSet->allocate(); WorkingSetMember* member = _workingSet->get(id); member->loc = nextLoc; member->obj = _iter->dataFor(member->loc).releaseToBson(); member->state = WorkingSetMember::LOC_AND_UNOWNED_OBJ; return returnIfMatches(member, id, out); }