BSONObj getObj() const { if (!WorkingSetCommon::fetchIfUnfetched(_txn, _ws, _id, _recordCursor)) throw DocumentDeletedException(); WorkingSetMember* member = _ws->get(_id); // Make it owned since we are buffering results. member->makeObjOwned(); return member->obj.value(); }
void WorkingSetCommon::prepareForSnapshotChange(WorkingSet* workingSet) { dassert(supportsDocLocking()); for (auto id : workingSet->getAndClearYieldSensitiveIds()) { if (workingSet->isFree(id)) { continue; } // We may see the same member twice, so anything we do here should be idempotent. WorkingSetMember* member = workingSet->get(id); if (member->getState() == WorkingSetMember::LOC_AND_IDX) { member->isSuspicious = true; } else if (member->getState() == WorkingSetMember::LOC_AND_OBJ) { // Need to make sure that the data is owned, as underlying storage can change during a // yield. member->makeObjOwned(); } } }
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) { // Ensure that the BSONObj underlying the WorkingSetMember is owned because it may // be freed when we yield. member->makeObjOwned(); _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 MergeSortStage::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; } if (!_noResultToMerge.empty()) { // We have some child that we don't have a result from. Each child must have a result // in order to pick the minimum result among all our children. Work a child. PlanStage* child = _noResultToMerge.front(); WorkingSetID id = WorkingSet::INVALID_ID; StageState code = child->work(&id); if (PlanStage::ADVANCED == code) { WorkingSetMember* member = _ws->get(id); // If we're deduping... if (_dedup) { if (!member->hasLoc()) { // Can't dedup data unless there's a RecordId. We go ahead and use its // result. _noResultToMerge.pop(); } else { ++_specificStats.dupsTested; // ...and there's a diskloc and and we've seen the RecordId before if (_seen.end() != _seen.find(member->loc)) { // ...drop it. _ws->free(id); ++_commonStats.needTime; ++_specificStats.dupsDropped; return PlanStage::NEED_TIME; } else { // Otherwise, note that we've seen it. _seen.insert(member->loc); // We're going to use the result from the child, so we remove it from // the queue of children without a result. _noResultToMerge.pop(); } } } else { // Not deduping. We use any result we get from the child. Remove the child // from the queue of things without a result. _noResultToMerge.pop(); } // Store the result in our list. StageWithValue value; value.id = id; value.stage = child; // Ensure that the BSONObj underlying the WorkingSetMember is owned in case we yield. member->makeObjOwned(); _mergingData.push_front(value); // Insert the result (indirectly) into our priority queue. _merging.push(_mergingData.begin()); ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (PlanStage::IS_EOF == code) { // There are no more results possible from this child. Don't bother with it // anymore. _noResultToMerge.pop(); ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (PlanStage::FAILURE == code || PlanStage::DEAD == code) { *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 << "merge sort stage failed to read in results from child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember(_ws, status); } return code; } else { if (PlanStage::NEED_TIME == code) { ++_commonStats.needTime; } else if (PlanStage::NEED_YIELD == code) { *out = id; ++_commonStats.needYield; } return code; } } // If we're here, for each non-EOF child, we have a valid WSID. verify(!_merging.empty()); // Get the 'min' WSID. _merging is a priority queue so its top is the smallest. MergingRef top = _merging.top(); _merging.pop(); // Since we're returning the WSID that came from top->stage, we need to work(...) it again // to get a new result. _noResultToMerge.push(top->stage); // Save the ID that we're returning and remove the returned result from our data. WorkingSetID idToTest = top->id; _mergingData.erase(top); // Return the min. *out = idToTest; ++_commonStats.advanced; // But don't return it if it's flagged. if (_ws->isFlagged(*out)) { return PlanStage::NEED_TIME; } return PlanStage::ADVANCED; }
PlanStage::StageState TextOrStage::addTerm(WorkingSetID wsid, WorkingSetID* out) { WorkingSetMember* wsm = _ws->get(wsid); invariant(wsm->getState() == WorkingSetMember::LOC_AND_IDX); invariant(1 == wsm->keyData.size()); const IndexKeyDatum newKeyData = wsm->keyData.back(); // copy to keep it around. TextRecordData* textRecordData = &_scores[wsm->loc]; double* documentAggregateScore = &textRecordData->score; if (WorkingSet::INVALID_ID == textRecordData->wsid) { // We haven't seen this RecordId before. Keep the working set member around (it may be // force-fetched on saveState()). textRecordData->wsid = wsid; if (_filter) { // We have not seen this document before and need to apply a filter. bool shouldKeep; bool wasDeleted = false; try { TextMatchableDocument tdoc(getOpCtx(), newKeyData.indexKeyPattern, newKeyData.keyData, _ws, wsid, _recordCursor); shouldKeep = _filter->matches(&tdoc); } catch (const WriteConflictException& wce) { // Ensure that the BSONObj underlying the WorkingSetMember is owned because it may // be freed when we yield. wsm->makeObjOwned(); _idRetrying = wsid; *out = WorkingSet::INVALID_ID; return NEED_YIELD; } catch (const TextMatchableDocument::DocumentDeletedException&) { // We attempted to fetch the document but decided it should be excluded from the // result set. shouldKeep = false; wasDeleted = true; } if (!shouldKeep) { if (wasDeleted || wsm->hasObj()) { // We had to fetch but we're not going to return it. ++_specificStats.fetches; } _ws->free(textRecordData->wsid); textRecordData->wsid = WorkingSet::INVALID_ID; *documentAggregateScore = -1; return NEED_TIME; } } } else { // We already have a working set member for this RecordId. Free the new WSM and retrieve the // old one. Note that since we don't keep all index keys, we could get a score that doesn't // match the document, but this has always been a problem. // TODO something to improve the situation. invariant(wsid != textRecordData->wsid); _ws->free(wsid); wsm = _ws->get(textRecordData->wsid); } if (*documentAggregateScore < 0) { // We have already rejected this document for not matching the filter. return NEED_TIME; } // Locate score within possibly compound key: {prefix,term,score,suffix}. BSONObjIterator keyIt(newKeyData.keyData); for (unsigned i = 0; i < _ftsSpec.numExtraBefore(); i++) { keyIt.next(); } keyIt.next(); // Skip past 'term'. BSONElement scoreElement = keyIt.next(); double documentTermScore = scoreElement.number(); // Aggregate relevance score, term keys. *documentAggregateScore += documentTermScore; return NEED_TIME; }
PlanStage::StageState AndSortedStage::getTargetLoc(WorkingSetID* out) { verify(numeric_limits<size_t>::max() == _targetNode); verify(WorkingSet::INVALID_ID == _targetId); verify(RecordId() == _targetLoc); // Pick one, and get a loc to work toward. WorkingSetID id = WorkingSet::INVALID_ID; StageState state = _children[0]->work(&id); if (PlanStage::ADVANCED == state) { WorkingSetMember* member = _ws->get(id); // Maybe the child had an invalidation. We intersect RecordId(s) so we can't do anything // with this WSM. if (!member->hasLoc()) { _ws->flagForReview(id); return PlanStage::NEED_TIME; } verify(member->hasLoc()); // We have a value from one child to AND with. _targetNode = 0; _targetId = id; _targetLoc = member->loc; // Ensure that the BSONObj underlying the WorkingSetMember is owned in case we yield. member->makeObjOwned(); // We have to AND with all other children. for (size_t i = 1; i < _children.size(); ++i) { _workingTowardRep.push(i); } ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (PlanStage::IS_EOF == state) { _isEOF = true; return state; } else if (PlanStage::FAILURE == state) { *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 << "sorted AND stage failed to read in results from first child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember(_ws, status); } _isEOF = true; return state; } else { if (PlanStage::NEED_TIME == state) { ++_commonStats.needTime; } else if (PlanStage::NEED_YIELD == state) { ++_commonStats.needYield; *out = id; } // NEED_TIME, NEED_YIELD. return state; } }
PlanStage::StageState AndSortedStage::moveTowardTargetLoc(WorkingSetID* out) { verify(numeric_limits<size_t>::max() != _targetNode); verify(WorkingSet::INVALID_ID != _targetId); // We have nodes that haven't hit _targetLoc yet. size_t workingChildNumber = _workingTowardRep.front(); auto& next = _children[workingChildNumber]; WorkingSetID id = WorkingSet::INVALID_ID; StageState state = next->work(&id); if (PlanStage::ADVANCED == state) { WorkingSetMember* member = _ws->get(id); // Maybe the child had an invalidation. We intersect RecordId(s) so we can't do anything // with this WSM. if (!member->hasLoc()) { _ws->flagForReview(id); return PlanStage::NEED_TIME; } verify(member->hasLoc()); if (member->loc == _targetLoc) { // The front element has hit _targetLoc. Don't move it forward anymore/work on // another element. _workingTowardRep.pop(); AndCommon::mergeFrom(_ws, _targetId, *member); _ws->free(id); if (0 == _workingTowardRep.size()) { WorkingSetID toReturn = _targetId; _targetNode = numeric_limits<size_t>::max(); _targetId = WorkingSet::INVALID_ID; _targetLoc = RecordId(); *out = toReturn; ++_commonStats.advanced; return PlanStage::ADVANCED; } // More children need to be advanced to _targetLoc. ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (member->loc < _targetLoc) { // The front element of _workingTowardRep hasn't hit the thing we're AND-ing with // yet. Try again later. _ws->free(id); ++_commonStats.needTime; return PlanStage::NEED_TIME; } else { // member->loc > _targetLoc. // _targetLoc wasn't successfully AND-ed with the other sub-plans. We toss it and // try AND-ing with the next value. _specificStats.failedAnd[_targetNode]++; _ws->free(_targetId); _targetNode = workingChildNumber; _targetLoc = member->loc; _targetId = id; // Ensure that the BSONObj underlying the WorkingSetMember is owned in case we yield. member->makeObjOwned(); _workingTowardRep = std::queue<size_t>(); for (size_t i = 0; i < _children.size(); ++i) { if (workingChildNumber != i) { _workingTowardRep.push(i); } } // Need time to chase after the new _targetLoc. ++_commonStats.needTime; return PlanStage::NEED_TIME; } } else if (PlanStage::IS_EOF == state) { _isEOF = true; _ws->free(_targetId); return state; } else if (PlanStage::FAILURE == state || PlanStage::DEAD == state) { *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 << "sorted AND stage failed to read in results from child " << workingChildNumber; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember(_ws, status); } _isEOF = true; _ws->free(_targetId); return state; } else { if (PlanStage::NEED_TIME == state) { ++_commonStats.needTime; } else if (PlanStage::NEED_YIELD == state) { ++_commonStats.needYield; *out = id; } return state; } }