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;
}
Beispiel #2
0
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;
}
Beispiel #3
0
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);
}
Beispiel #4
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) {
                _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 #5
0
    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);
    }
Beispiel #6
0
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;
    }
}
Beispiel #7
0
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);
    }
Beispiel #9
0
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;
}
Beispiel #10
0
    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);
    }
Beispiel #11
0
    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);
    }