Beispiel #1
0
    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();
    }
Beispiel #2
0
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();
        }
    }
}
Beispiel #3
0
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;
}
Beispiel #4
0
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;
}
Beispiel #5
0
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;
}
Beispiel #6
0
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;
    }
}
Beispiel #7
0
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;
    }
}