/**
* Copyright (C) 2008, 2013 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/clientcursor.h"
#include
#include
#include
#include "mongo/base/counter.h"
#include "mongo/client/dbclientinterface.h"
#include "mongo/db/audit.h"
#include "mongo/db/auth/action_set.h"
#include "mongo/db/auth/action_type.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/commands.h"
#include "mongo/db/commands/server_status.h"
#include "mongo/db/commands/server_status_metric.h"
#include "mongo/db/db.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/kill_current_op.h"
#include "mongo/db/repl/rs.h"
#include "mongo/db/repl/write_concern.h"
namespace mongo {
static Counter64 cursorStatsOpen; // gauge
static Counter64 cursorStatsOpenPinned; // gauge
static Counter64 cursorStatsOpenNoTimeout; // gauge
static Counter64 cursorStatsTimedOut;
static ServerStatusMetricField dCursorStatsOpen( "cursor.open.total",
&cursorStatsOpen );
static ServerStatusMetricField dCursorStatsOpenPinned( "cursor.open.pinned",
&cursorStatsOpenPinned );
static ServerStatusMetricField dCursorStatsOpenNoTimeout( "cursor.open.noTimeout",
&cursorStatsOpenNoTimeout );
static ServerStatusMetricField dCursorStatusTimedout( "cursor.timedOut",
&cursorStatsTimedOut );
long long ClientCursor::totalOpen() {
return cursorStatsOpen.get();
}
ClientCursor::ClientCursor(const Collection* collection, Runner* runner,
int qopts, const BSONObj query)
: _collection( collection ),
_countedYet( false ) {
_runner.reset(runner);
_ns = runner->ns();
_query = query;
_queryOptions = qopts;
if ( runner->collection() ) {
invariant( collection == runner->collection() );
}
init();
}
ClientCursor::ClientCursor(const Collection* collection)
: _ns(collection->ns().ns()),
_collection(collection),
_countedYet( false ),
_queryOptions(QueryOption_NoCursorTimeout) {
init();
}
void ClientCursor::init() {
invariant( _collection );
isAggCursor = false;
_idleAgeMillis = 0;
_leftoverMaxTimeMicros = 0;
_pinValue = 0;
_pos = 0;
Lock::assertAtLeastReadLocked(_ns);
if (_queryOptions & QueryOption_NoCursorTimeout) {
// cursors normally timeout after an inactivity period to prevent excess memory use
// setting this prevents timeout of the cursor in question.
++_pinValue;
cursorStatsOpenNoTimeout.increment();
}
_cursorid = _collection->cursorCache()->registerCursor( this );
cursorStatsOpen.increment();
_countedYet = true;
}
ClientCursor::~ClientCursor() {
if( _pos == -2 ) {
// defensive: destructor called twice
wassert(false);
return;
}
if ( _countedYet ) {
_countedYet = false;
cursorStatsOpen.decrement();
if ( _pinValue == 1 )
cursorStatsOpenNoTimeout.decrement();
}
if ( _collection ) {
// this could be null if kill() was killed
_collection->cursorCache()->deregisterCursor( this );
}
// defensive:
_collection = NULL;
_cursorid = INVALID_CURSOR_ID;
_pos = -2;
_pinValue = 0;
}
void ClientCursor::kill() {
if ( _runner.get() )
_runner->kill();
_collection = NULL;
}
//
// Timing and timeouts
//
bool ClientCursor::shouldTimeout(unsigned millis) {
_idleAgeMillis += millis;
return _idleAgeMillis > 600000 && _pinValue == 0;
}
void ClientCursor::setIdleTime( unsigned millis ) {
_idleAgeMillis = millis;
}
void ClientCursor::updateSlaveLocation( CurOp& curop ) {
if ( _slaveReadTill.isNull() )
return;
mongo::updateSlaveLocation( curop , _ns.c_str() , _slaveReadTill );
}
//
// Pin methods
// TODO: Simplify when we kill Cursor. In particular, once we've pinned a CC, it won't be
// deleted from underneath us, so we can save the pointer and ignore the ID.
//
ClientCursorPin::ClientCursorPin( const Collection* collection, long long cursorid )
: _cursor( NULL ) {
cursorStatsOpenPinned.increment();
_cursor = collection->cursorCache()->find( cursorid, true );
}
ClientCursorPin::~ClientCursorPin() {
cursorStatsOpenPinned.decrement();
DESTRUCTOR_GUARD( release(); );
}
void ClientCursorPin::release() {
if ( !_cursor )
return;
invariant( _cursor->pinValue() >= 100 );
if ( _cursor->collection() == NULL ) {
// the ClientCursor was killed while we had it
// therefore its our responsibility to kill it
delete _cursor;
_cursor = NULL; // defensive
}
else {
_cursor->collection()->cursorCache()->unpin( _cursor );
}
}
void ClientCursorPin::deleteUnderlying() {
delete _cursor;
_cursor = NULL;
}
ClientCursor* ClientCursorPin::c() const {
return _cursor;
}
//
// ClientCursorMonitor
//
void ClientCursorMonitor::run() {
Client::initThread("clientcursormon");
Client& client = cc();
Timer t;
const int Secs = 4;
while ( ! inShutdown() ) {
cursorStatsTimedOut.increment( CollectionCursorCache::timeoutCursorsGlobal( t.millisReset() ) );
sleepsecs(Secs);
}
client.shutdown();
}
namespace {
ClientCursorMonitor clientCursorMonitor;
void _appendCursorStats( BSONObjBuilder& b ) {
b.append( "note" , "deprecated, use server status metrics" );
b.appendNumber("clientCursors_size", cursorStatsOpen.get() );
b.appendNumber("totalOpen", cursorStatsOpen.get() );
b.appendNumber("pinned", cursorStatsOpenPinned.get() );
b.appendNumber("totalNoTimeout", cursorStatsOpenNoTimeout.get() );
b.appendNumber("timedOut" , cursorStatsTimedOut.get());
}
}
// QUESTION: Restrict to the namespace from which this command was issued?
// Alternatively, make this command admin-only?
// TODO: remove this for 2.8
class CmdCursorInfo : public Command {
public:
CmdCursorInfo() : Command( "cursorInfo", true ) {}
virtual bool slaveOk() const { return true; }
virtual void help( stringstream& help ) const {
help << " example: { cursorInfo : 1 }, deprecated";
}
virtual bool isWriteCommandForConfigServer() const { return false; }
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
std::vector* out) {
ActionSet actions;
actions.addAction(ActionType::cursorInfo);
out->push_back(Privilege(ResourcePattern::forClusterResource(), actions));
}
bool run(OperationContext* txn, const string& dbname, BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& result,
bool fromRepl ) {
_appendCursorStats( result );
return true;
}
} cmdCursorInfo;
//
// cursors stats.
//
class CursorServerStats : public ServerStatusSection {
public:
CursorServerStats() : ServerStatusSection( "cursors" ){}
virtual bool includeByDefault() const { return true; }
BSONObj generateSection(const BSONElement& configElement) const {
BSONObjBuilder b;
_appendCursorStats( b );
return b.obj();
}
} cursorServerStats;
} // namespace mongo