PlanStage::StageState AndHashStage::readFirstChild(WorkingSetID* out) { verify(_currentChild == 0); WorkingSetID id = WorkingSet::INVALID_ID; StageState childStatus = workChild(0, &id); if (PlanStage::ADVANCED == childStatus) { WorkingSetMember* member = _ws->get(id); // The child must give us a WorkingSetMember with a record id, since we intersect index keys // based on the record id. The planner ensures that the child stage can never produce an WSM // with no record id. invariant(member->hasRecordId()); if (!_dataMap.insert(std::make_pair(member->recordId, id)).second) { // Didn't insert because we already had this RecordId inside the map. This should only // happen if we're seeing a newer copy of the same doc in a more recent snapshot. // Throw out the newer copy of the doc. _ws->free(id); return PlanStage::NEED_TIME; } // Ensure that the BSONObj underlying the WorkingSetMember is owned in case we yield. member->makeObjOwnedIfNeeded(); // Update memory stats. _memUsage += member->getMemUsage(); return PlanStage::NEED_TIME; } else if (PlanStage::IS_EOF == childStatus) { // Done reading child 0. _currentChild = 1; // If our first child was empty, don't scan any others, no possible results. if (_dataMap.empty()) { _hashingChildren = false; return PlanStage::IS_EOF; } _specificStats.mapAfterChild.push_back(_dataMap.size()); return PlanStage::NEED_TIME; } else if (PlanStage::FAILURE == childStatus || PlanStage::DEAD == childStatus) { // 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 childStatus; } else { if (PlanStage::NEED_YIELD == childStatus) { *out = id; } return childStatus; } }
void AndHashStage::invalidate(OperationContext* txn, const RecordId& dl, InvalidationType type) { ++_commonStats.invalidates; if (isEOF()) { return; } for (size_t i = 0; i < _children.size(); ++i) { _children[i]->invalidate(txn, dl, type); } // Invalidation can happen to our warmup results. If that occurs just // flag it and forget about it. for (size_t i = 0; i < _lookAheadResults.size(); ++i) { if (WorkingSet::INVALID_ID != _lookAheadResults[i]) { WorkingSetMember* member = _ws->get(_lookAheadResults[i]); if (member->hasLoc() && member->loc == dl) { WorkingSetCommon::fetchAndInvalidateLoc(txn, member, _collection); _ws->flagForReview(_lookAheadResults[i]); _lookAheadResults[i] = WorkingSet::INVALID_ID; } } } // If it's a deletion, we have to forget about the RecordId, and since the AND-ing is by // RecordId we can't continue processing it even with the object. // // If it's a mutation the predicates implied by the AND-ing may no longer be true. // // So, we flag and try to pick it up later. DataMap::iterator it = _dataMap.find(dl); if (_dataMap.end() != it) { WorkingSetID id = it->second; WorkingSetMember* member = _ws->get(id); verify(member->loc == dl); if (_hashingChildren) { ++_specificStats.flaggedInProgress; } else { ++_specificStats.flaggedButPassed; } // Update memory stats. _memUsage -= member->getMemUsage(); // The loc is about to be invalidated. Fetch it and clear the loc. WorkingSetCommon::fetchAndInvalidateLoc(txn, member, _collection); // Add the WSID to the to-be-reviewed list in the WS. _ws->flagForReview(id); // And don't return it from this stage. _dataMap.erase(it); } }
/** * addToBuffer() and sortBuffer() work differently based on the * configured limit. addToBuffer() is also responsible for * performing some accounting on the overall memory usage to * make sure we're not using too much memory. * * limit == 0: * addToBuffer() - Adds item to vector. * sortBuffer() - Sorts vector. * limit == 1: * addToBuffer() - Replaces first item in vector with max of * current and new item. * Updates memory usage if item was replaced. * sortBuffer() - Does nothing. * limit > 1: * addToBuffer() - Does not update vector. Adds item to set. * If size of set exceeds limit, remove item from set * with lowest key. Updates memory usage accordingly. * sortBuffer() - Copies items from set to vectors. */ void SortStage::addToBuffer(const SortableDataItem& item) { // Holds ID of working set member to be freed at end of this function. WorkingSetID wsidToFree = WorkingSet::INVALID_ID; WorkingSetMember* member = _ws->get(item.wsid); if (_limit == 0) { // Ensure that the BSONObj underlying the WorkingSetMember is owned in case we yield. member->makeObjOwnedIfNeeded(); _data.push_back(item); _memUsage += member->getMemUsage(); } else if (_limit == 1) { if (_data.empty()) { member->makeObjOwnedIfNeeded(); _data.push_back(item); _memUsage = member->getMemUsage(); return; } wsidToFree = item.wsid; const WorkingSetComparator& cmp = *_sortKeyComparator; // Compare new item with existing item in vector. if (cmp(item, _data[0])) { wsidToFree = _data[0].wsid; member->makeObjOwnedIfNeeded(); _data[0] = item; _memUsage = member->getMemUsage(); } } else { // Update data item set instead of vector // Limit not reached - insert and return vector<SortableDataItem>::size_type limit(_limit); if (_dataSet->size() < limit) { member->makeObjOwnedIfNeeded(); _dataSet->insert(item); _memUsage += member->getMemUsage(); return; } // Limit will be exceeded - compare with item with lowest key // If new item does not have a lower key value than last item, // do nothing. wsidToFree = item.wsid; SortableDataItemSet::const_iterator lastItemIt = --(_dataSet->end()); const SortableDataItem& lastItem = *lastItemIt; const WorkingSetComparator& cmp = *_sortKeyComparator; if (cmp(item, lastItem)) { _memUsage -= _ws->get(lastItem.wsid)->getMemUsage(); _memUsage += member->getMemUsage(); wsidToFree = lastItem.wsid; // According to std::set iterator validity rules, // it does not matter which of erase()/insert() happens first. // Here, we choose to erase first to release potential resources // used by the last item and to keep the scope of the iterator to a minimum. _dataSet->erase(lastItemIt); member->makeObjOwnedIfNeeded(); _dataSet->insert(item); } } // If the working set ID is valid, remove from // RecordId invalidation map and free from working set. if (wsidToFree != WorkingSet::INVALID_ID) { WorkingSetMember* member = _ws->get(wsidToFree); if (member->hasLoc()) { _wsidByDiskLoc.erase(member->loc); } _ws->free(wsidToFree); } }
PlanStage::StageState AndHashStage::hashOtherChildren(WorkingSetID* out) { verify(_currentChild > 0); WorkingSetID id = WorkingSet::INVALID_ID; StageState childStatus = workChild(_currentChild, &id); if (PlanStage::ADVANCED == childStatus) { WorkingSetMember* member = _ws->get(id); // The child must give us a WorkingSetMember with a record id, since we intersect index keys // based on the record id. The planner ensures that the child stage can never produce an // WSM with no record id. invariant(member->hasRecordId()); if (_dataMap.end() == _dataMap.find(member->recordId)) { // Ignore. It's not in any previous child. } else { // We have a hit. Copy data into the WSM we already have. _seenMap.insert(member->recordId); WorkingSetID olderMemberID = _dataMap[member->recordId]; WorkingSetMember* olderMember = _ws->get(olderMemberID); size_t memUsageBefore = olderMember->getMemUsage(); AndCommon::mergeFrom(_ws, olderMemberID, *member); // Update memory stats. _memUsage += olderMember->getMemUsage() - memUsageBefore; } _ws->free(id); return PlanStage::NEED_TIME; } else if (PlanStage::IS_EOF == childStatus) { // Finished with a child. ++_currentChild; // Keep elements of _dataMap that are in _seenMap. DataMap::iterator it = _dataMap.begin(); while (it != _dataMap.end()) { if (_seenMap.end() == _seenMap.find(it->first)) { DataMap::iterator toErase = it; ++it; // Update memory stats. WorkingSetMember* member = _ws->get(toErase->second); _memUsage -= member->getMemUsage(); _ws->free(toErase->second); _dataMap.erase(toErase); } else { ++it; } } _specificStats.mapAfterChild.push_back(_dataMap.size()); _seenMap.clear(); // _dataMap is now the intersection of the first _currentChild nodes. // If we have nothing to AND with after finishing any child, stop. if (_dataMap.empty()) { _hashingChildren = false; return PlanStage::IS_EOF; } // We've finished scanning all children. Return results with the next call to work(). if (_currentChild == _children.size()) { _hashingChildren = false; } return PlanStage::NEED_TIME; } else if (PlanStage::FAILURE == childStatus || PlanStage::DEAD == childStatus) { // 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 childStatus; } else { if (PlanStage::NEED_YIELD == childStatus) { *out = id; } return childStatus; } }
PlanStage::StageState AndHashStage::hashOtherChildren(WorkingSetID* out) { verify(_currentChild > 0); WorkingSetID id = WorkingSet::INVALID_ID; StageState childStatus = workChild(_currentChild, &id); if (PlanStage::ADVANCED == childStatus) { 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 (_dataMap.end() == _dataMap.find(member->loc)) { // Ignore. It's not in any previous child. } else { // We have a hit. Copy data into the WSM we already have. _seenMap.insert(member->loc); WorkingSetMember* olderMember = _ws->get(_dataMap[member->loc]); size_t memUsageBefore = olderMember->getMemUsage(); AndCommon::mergeFrom(olderMember, *member); // Update memory stats. _memUsage += olderMember->getMemUsage() - memUsageBefore; } _ws->free(id); ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (PlanStage::IS_EOF == childStatus) { // Finished with a child. ++_currentChild; // Keep elements of _dataMap that are in _seenMap. DataMap::iterator it = _dataMap.begin(); while (it != _dataMap.end()) { if (_seenMap.end() == _seenMap.find(it->first)) { DataMap::iterator toErase = it; ++it; // Update memory stats. WorkingSetMember* member = _ws->get(toErase->second); _memUsage -= member->getMemUsage(); _ws->free(toErase->second); _dataMap.erase(toErase); } else { ++it; } } _specificStats.mapAfterChild.push_back(_dataMap.size()); _seenMap.clear(); // _dataMap is now the intersection of the first _currentChild nodes. // If we have nothing to AND with after finishing any child, stop. if (_dataMap.empty()) { _hashingChildren = false; return PlanStage::IS_EOF; } // We've finished scanning all children. Return results with the next call to work(). if (_currentChild == _children.size()) { _hashingChildren = false; } ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (PlanStage::FAILURE == childStatus) { *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 << "hashed AND stage failed to read in results from other child " << _currentChild; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember( _ws, status); } return childStatus; } else { if (PlanStage::NEED_TIME == childStatus) { ++_commonStats.needTime; } else if (PlanStage::NEED_YIELD == childStatus) { ++_commonStats.needYield; *out = id; } return childStatus; } }
PlanStage::StageState AndHashStage::readFirstChild(WorkingSetID* out) { verify(_currentChild == 0); WorkingSetID id = WorkingSet::INVALID_ID; StageState childStatus = workChild(0, &id); if (PlanStage::ADVANCED == childStatus) { 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; } if (!_dataMap.insert(std::make_pair(member->loc, id)).second) { // Didn't insert because we already had this loc inside the map. This should only // happen if we're seeing a newer copy of the same doc in a more recent snapshot. // Throw out the newer copy of the doc. _ws->free(id); ++_commonStats.needTime; return PlanStage::NEED_TIME; } // Update memory stats. _memUsage += member->getMemUsage(); ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (PlanStage::IS_EOF == childStatus) { // Done reading child 0. _currentChild = 1; // If our first child was empty, don't scan any others, no possible results. if (_dataMap.empty()) { _hashingChildren = false; return PlanStage::IS_EOF; } ++_commonStats.needTime; _specificStats.mapAfterChild.push_back(_dataMap.size()); return PlanStage::NEED_TIME; } else if (PlanStage::FAILURE == childStatus) { *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 << "hashed AND stage failed to read in results to from first child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember( _ws, status); } return childStatus; } else { if (PlanStage::NEED_TIME == childStatus) { ++_commonStats.needTime; } else if (PlanStage::NEED_YIELD == childStatus) { ++_commonStats.needYield; *out = id; } return childStatus; } }
PlanStage::StageState AndHashStage::readFirstChild(WorkingSetID* out) { verify(_currentChild == 0); WorkingSetID id = WorkingSet::INVALID_ID; StageState childStatus = workChild(0, &id); if (PlanStage::ADVANCED == childStatus) { 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()); verify(_dataMap.end() == _dataMap.find(member->loc)); _dataMap[member->loc] = id; // Update memory stats. _memUsage += member->getMemUsage(); ++_commonStats.needTime; return PlanStage::NEED_TIME; } else if (PlanStage::IS_EOF == childStatus) { // Done reading child 0. _currentChild = 1; // If our first child was empty, don't scan any others, no possible results. if (_dataMap.empty()) { _hashingChildren = false; return PlanStage::IS_EOF; } ++_commonStats.needTime; _specificStats.mapAfterChild.push_back(_dataMap.size()); return PlanStage::NEED_TIME; } else if (PlanStage::FAILURE == childStatus) { *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 << "hashed AND stage failed to read in results to from first child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember( _ws, status); } return childStatus; } else { if (PlanStage::NEED_TIME == childStatus) { ++_commonStats.needTime; } else if (PlanStage::NEED_FETCH == childStatus) { ++_commonStats.needFetch; *out = id; } return childStatus; } }