diff options
| author | Apollon Oikonomopoulos <apoikos@debian.org> | 2016-01-14 00:10:06 +0200 |
|---|---|---|
| committer | Apollon Oikonomopoulos <apollon@skroutz.gr> | 2016-01-14 00:10:06 +0200 |
| commit | 374e1947abcd3e127a2a613aff73ecffdb9199ea (patch) | |
| tree | d83973c3c9802450acd5b5e86fe0d4e8e60a3a1b /src/mongo/s/dbclient_multi_command.cpp | |
| parent | 65585c90b12d6523bea75a2aebaae2a2fdf9e641 (diff) | |
Imported Upstream version 2.6.11upstream/2.6.11
Diffstat (limited to 'src/mongo/s/dbclient_multi_command.cpp')
| -rw-r--r-- | src/mongo/s/dbclient_multi_command.cpp | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/src/mongo/s/dbclient_multi_command.cpp b/src/mongo/s/dbclient_multi_command.cpp new file mode 100644 index 00000000000..a6251f311cf --- /dev/null +++ b/src/mongo/s/dbclient_multi_command.cpp @@ -0,0 +1,277 @@ +/** + * Copyright (C) 2013 MongoDB 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 <http://www.gnu.org/licenses/>. + * + * 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/s/dbclient_multi_command.h" + +#include "mongo/bson/mutable/document.h" +#include "mongo/db/audit.h" +#include "mongo/db/client_basic.h" +#include "mongo/db/dbmessage.h" +#include "mongo/db/wire_version.h" +#include "mongo/s/shard.h" +#include "mongo/db/server_parameters.h" +#include "mongo/s/write_ops/batch_downconvert.h" +#include "mongo/s/write_ops/dbclient_safe_writer.h" +#include "mongo/util/net/message.h" + +namespace mongo { + + // Can force the write mode used for the shards to not uses commands, even if avail. + MONGO_EXPORT_SERVER_PARAMETER( _forceLegacyShardWriteMode, bool, false ); + + DBClientMultiCommand::PendingCommand::PendingCommand( const ConnectionString& endpoint, + const StringData& dbName, + const BSONObj& cmdObj ) : + endpoint( endpoint ), + dbName( dbName.toString() ), + cmdObj( cmdObj ), + conn( NULL ), + status( Status::OK() ) { + } + + void DBClientMultiCommand::addCommand( const ConnectionString& endpoint, + const StringData& dbName, + const BSONSerializable& request ) { + PendingCommand* command = new PendingCommand( endpoint, dbName, request.toBSON() ); + _pendingCommands.push_back( command ); + } + + namespace { + + // + // Stuff we need for batch downconversion + // TODO: Remove post-2.6 + // + + BatchedCommandRequest::BatchType getBatchWriteType( const BSONObj& cmdObj ) { + string cmdName = cmdObj.firstElement().fieldName(); + if ( cmdName == "insert" ) return BatchedCommandRequest::BatchType_Insert; + if ( cmdName == "update" ) return BatchedCommandRequest::BatchType_Update; + if ( cmdName == "delete" ) return BatchedCommandRequest::BatchType_Delete; + return BatchedCommandRequest::BatchType_Unknown; + } + + bool isBatchWriteCommand( const BSONObj& cmdObj ) { + return getBatchWriteType( cmdObj ) != BatchedCommandRequest::BatchType_Unknown; + } + + bool hasBatchWriteFeature( DBClientBase* conn ) { + return !_forceLegacyShardWriteMode + && conn->getMinWireVersion() <= BATCH_COMMANDS + && conn->getMaxWireVersion() >= BATCH_COMMANDS; + } + + /** + * Parses and re-BSON's a batch write command in order to send it as a set of safe writes. + */ + void legacySafeWrite( DBClientBase* conn, + const StringData& dbName, + const BSONObj& cmdRequest, + BSONObj* cmdResponse ) { + + // Translate from BSON + BatchedCommandRequest request( getBatchWriteType( cmdRequest ) ); + + // This should *always* parse correctly + bool parsed = request.parseBSON( cmdRequest, NULL ); + (void) parsed; // for non-debug compile + dassert( parsed && request.isValid( NULL ) ); + + // Collection name is sent without db to the dispatcher + request.setNS( dbName.toString() + "." + request.getNS() ); + + DBClientSafeWriter safeWriter; + BatchSafeWriter batchSafeWriter( &safeWriter ); + BatchedCommandResponse response; + batchSafeWriter.safeWriteBatch( conn, request, &response ); + + // Back to BSON + dassert( response.isValid( NULL ) ); + *cmdResponse = response.toBSON(); + } + } + + // THROWS + static void sayAsCmd( DBClientBase* conn, const StringData& dbName, const BSONObj& cmdObj ) { + Message toSend; + BSONObjBuilder usersBuilder; + usersBuilder.appendElements(cmdObj); + audit::appendImpersonatedUsers(&usersBuilder); + + // see query.h for the protocol we are using here. + BufBuilder bufB; + bufB.appendNum( 0 ); // command/query options + bufB.appendStr( dbName.toString() + ".$cmd" ); // write command ns + bufB.appendNum( 0 ); // ntoskip (0 for command) + bufB.appendNum( 1 ); // ntoreturn (1 for command) + usersBuilder.obj().appendSelfToBufBuilder( bufB ); + toSend.setData( dbQuery, bufB.buf(), bufB.len() ); + + // Send our command + conn->say( toSend ); + } + + // THROWS + static void recvAsCmd( DBClientBase* conn, Message* toRecv, BSONObj* result ) { + + if ( !conn->recv( *toRecv ) ) { + // Confusingly, socket exceptions here are written to the log, not thrown. + uasserted( 17255, "error receiving write command response, " + "possible socket exception - see logs" ); + } + + // A query result is returned from commands + QueryResult* recvdQuery = reinterpret_cast<QueryResult*>( toRecv->singleData() ); + *result = BSONObj( recvdQuery->data() ); + } + + void DBClientMultiCommand::sendAll() { + + for ( deque<PendingCommand*>::iterator it = _pendingCommands.begin(); + it != _pendingCommands.end(); ++it ) { + + PendingCommand* command = *it; + dassert( NULL == command->conn ); + + try { + dassert( command->endpoint.type() == ConnectionString::MASTER || + command->endpoint.type() == ConnectionString::CUSTOM ); + + // TODO: Fix the pool up to take millis directly + int timeoutSecs = _timeoutMillis / 1000; + command->conn = shardConnectionPool.get( command->endpoint, timeoutSecs ); + + if ( hasBatchWriteFeature( command->conn ) + || !isBatchWriteCommand( command->cmdObj ) ) { + // Do normal command dispatch + sayAsCmd( command->conn, command->dbName, command->cmdObj ); + } + else { + // Sending a batch as safe writes necessarily blocks, so we can't do anything + // here. Instead we do the safe writes in recvAny(), which can block. + } + } + catch ( const DBException& ex ) { + command->status = ex.toStatus(); + + if ( NULL != command->conn ) { + + // Confusingly, the pool needs to know about failed connections so that it can + // invalidate other connections which might be bad. But if the connection + // doesn't seem bad, don't send it back, because we don't want to reuse it. + if ( !command->conn->isFailed() ) { + delete command->conn; + } + else { + shardConnectionPool.release( command->endpoint.toString(), command->conn ); + } + + command->conn = NULL; + } + } + } + } + + int DBClientMultiCommand::numPending() const { + return static_cast<int>( _pendingCommands.size() ); + } + + Status DBClientMultiCommand::recvAny( ConnectionString* endpoint, BSONSerializable* response ) { + + scoped_ptr<PendingCommand> command( _pendingCommands.front() ); + _pendingCommands.pop_front(); + + *endpoint = command->endpoint; + if ( !command->status.isOK() ) return command->status; + + dassert( NULL != command->conn ); + + try { + + // Holds the data and BSONObj for the command result + Message toRecv; + BSONObj result; + + if ( hasBatchWriteFeature( command->conn ) + || !isBatchWriteCommand( command->cmdObj ) ) { + // Recv data from command sent earlier + recvAsCmd( command->conn, &toRecv, &result ); + } + else { + // We can safely block in recvAny, so dispatch writes as safe writes for hosts + // that don't understand batch write commands. + legacySafeWrite( command->conn, command->dbName, command->cmdObj, &result ); + } + + shardConnectionPool.release( command->endpoint.toString(), command->conn ); + command->conn = NULL; + + string errMsg; + if ( !response->parseBSON( result, &errMsg ) || !response->isValid( &errMsg ) ) { + return Status( ErrorCodes::FailedToParse, errMsg ); + } + } + catch ( const DBException& ex ) { + + // Confusingly, the pool needs to know about failed connections so that it can + // invalidate other connections which might be bad. But if the connection doesn't seem + // bad, don't send it back, because we don't want to reuse it. + if ( !command->conn->isFailed() ) { + delete command->conn; + } + else { + shardConnectionPool.release( command->endpoint.toString(), command->conn ); + } + command->conn = NULL; + + return ex.toStatus(); + } + + return Status::OK(); + } + + DBClientMultiCommand::~DBClientMultiCommand() { + + // Cleanup anything outstanding, do *not* return stuff to the pool, that might error + for ( deque<PendingCommand*>::iterator it = _pendingCommands.begin(); + it != _pendingCommands.end(); ++it ) { + + PendingCommand* command = *it; + + if ( NULL != command->conn ) delete command->conn; + delete command; + command = NULL; + } + + _pendingCommands.clear(); + } + + void DBClientMultiCommand::setTimeoutMillis( int milliSecs ) { + _timeoutMillis = milliSecs; + } +} |
