/** * Copyright (C) 2015 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 . * * 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kASIO #include "mongo/platform/basic.h" #include "mongo/executor/network_interface_asio.h" #include #include #include "mongo/base/static_assert.h" #include "mongo/db/dbmessage.h" #include "mongo/db/jsobj.h" #include "mongo/executor/async_stream_interface.h" #include "mongo/executor/async_stream_interface.h" #include "mongo/executor/connection_pool_asio.h" #include "mongo/rpc/factory.h" #include "mongo/rpc/metadata/metadata_hook.h" #include "mongo/rpc/protocol.h" #include "mongo/rpc/reply_interface.h" #include "mongo/rpc/request_builder_interface.h" #include "mongo/stdx/memory.h" #include "mongo/util/assert_util.h" #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" namespace mongo { namespace executor { /** * The following send - receive utility functions are "stateless" in that they exist * apart from the AsyncOp state machine. */ namespace { MONGO_FP_DECLARE(NetworkInterfaceASIOasyncRunCommandFail); using asio::ip::tcp; using ResponseStatus = TaskExecutor::ResponseStatus; // A type conforms to the NetworkHandler concept if it is a callable type that takes a // std::error_code and std::size_t and returns void. The std::error_code parameter is used // to inform the handler if the asynchronous operation it was waiting on succeeded, and the // size_t parameter conveys how many bytes were read or written. template using IsNetworkHandler = std::is_convertible>; template void asyncSendMessage(AsyncStreamInterface& stream, Message* m, Handler&& handler) { MONGO_STATIC_ASSERT_MSG( IsNetworkHandler::value, "Handler passed to asyncSendMessage does not conform to NetworkHandler concept"); m->header().setResponseToMsgId(0); m->header().setId(nextMessageId()); // TODO: Some day we may need to support vector messages. fassert(28708, m->buf() != 0); stream.write(asio::buffer(m->buf(), m->size()), std::forward(handler)); } template void asyncRecvMessageHeader(AsyncStreamInterface& stream, MSGHEADER::Value* header, Handler&& handler) { MONGO_STATIC_ASSERT_MSG( IsNetworkHandler::value, "Handler passed to asyncRecvMessageHeader does not conform to NetworkHandler concept"); stream.read(asio::buffer(header->view().view2ptr(), sizeof(decltype(*header))), std::forward(handler)); } template void asyncRecvMessageBody(AsyncStreamInterface& stream, MSGHEADER::Value* header, Message* m, Handler&& handler) { MONGO_STATIC_ASSERT_MSG( IsNetworkHandler::value, "Handler passed to asyncRecvMessageBody does not conform to NetworkHandler concept"); // validate message length int len = header->constView().getMessageLength(); if (len == 542393671) { LOG(3) << "attempt to access MongoDB over HTTP on the native driver port."; return handler(make_error_code(ErrorCodes::ProtocolError), 0); } else if (static_cast(len) < sizeof(MSGHEADER::Value) || static_cast(len) > MaxMessageSizeBytes) { warning() << "recv(): message len " << len << " is invalid. " << "Min " << sizeof(MSGHEADER::Value) << " Max: " << MaxMessageSizeBytes; return handler(make_error_code(ErrorCodes::InvalidLength), 0); } int z = (len + 1023) & 0xfffffc00; invariant(z >= len); m->setData(SharedBuffer::allocate(z)); MsgData::View mdView = m->buf(); // copy header data into master buffer int headerLen = sizeof(MSGHEADER::Value); memcpy(mdView.view2ptr(), header, headerLen); int bodyLength = len - headerLen; invariant(bodyLength >= 0); // receive remaining data into md->data stream.read(asio::buffer(mdView.data(), bodyLength), std::forward(handler)); } ResponseStatus decodeRPC(Message* received, rpc::Protocol protocol, Milliseconds elapsed, const HostAndPort& source, rpc::EgressMetadataHook* metadataHook) { try { // makeReply will throw if the reply is invalid auto reply = rpc::makeReply(received); if (reply->getProtocol() != protocol) { auto requestProtocol = rpc::toString(static_cast(protocol)); if (!requestProtocol.isOK()) return {requestProtocol.getStatus(), elapsed}; return {ErrorCodes::RPCProtocolNegotiationFailed, str::stream() << "Mismatched RPC protocols - request was '" << requestProtocol.getValue().toString() << "' '" << " but reply was '" << networkOpToString(received->operation()) << "'", elapsed}; } auto commandReply = reply->getCommandReply(); auto replyMetadata = reply->getMetadata(); // Handle incoming reply metadata. if (metadataHook) { auto listenStatus = callNoexcept( *metadataHook, &rpc::EgressMetadataHook::readReplyMetadata, source, replyMetadata); if (!listenStatus.isOK()) { return {listenStatus, elapsed}; } } return {RemoteCommandResponse( std::move(*received), std::move(commandReply), std::move(replyMetadata), elapsed)}; } catch (...) { return {exceptionToStatus(), elapsed}; } } } // namespace NetworkInterfaceASIO::AsyncCommand::AsyncCommand(AsyncConnection* conn, Message&& command, Date_t now, const HostAndPort& target) : _conn(conn), _toSend(std::move(command)), _start(now), _target(target) { _toSend.header().setResponseToMsgId(0); } NetworkInterfaceASIO::AsyncConnection& NetworkInterfaceASIO::AsyncCommand::conn() { return *_conn; } Message& NetworkInterfaceASIO::AsyncCommand::toSend() { return _toSend; } Message& NetworkInterfaceASIO::AsyncCommand::toRecv() { return _toRecv; } MSGHEADER::Value& NetworkInterfaceASIO::AsyncCommand::header() { return _header; } ResponseStatus NetworkInterfaceASIO::AsyncCommand::response(AsyncOp* op, rpc::Protocol protocol, Date_t now, rpc::EgressMetadataHook* metadataHook) { auto& received = _toRecv; if (received.operation() == dbCompressed) { auto swm = conn().getCompressorManager().decompressMessage(received); if (!swm.isOK()) { return swm.getStatus(); } received = std::move(swm.getValue()); } auto rs = decodeRPC(&received, protocol, now - _start, _target, metadataHook); if (rs.isOK()) op->setResponseMetadata(rs.metadata); return rs; } void NetworkInterfaceASIO::_startCommand(AsyncOp* op) { LOG(3) << "running command " << redact(op->request().cmdObj) << " against database " << op->request().dbname << " across network to " << op->request().target.toString(); if (inShutdown()) { return; } // _connect() will continue the state machine. _connect(op); } void NetworkInterfaceASIO::_beginCommunication(AsyncOp* op) { // The way that we connect connections for the connection pool is by // starting the callback chain with connect(), but getting off at the first // _beginCommunication. I.e. all AsyncOp's start off with _inSetup == true // and arrive here as they're connected and authed. Once they hit here, we // return to the connection pool's get() callback with _inSetup == false, // so we can proceed with user operations after they return to this // codepath. if (op->_inSetup) { auto host = op->request().target; auto getConnectionDuration = now() - op->start(); log() << "Successfully connected to " << host << ", took " << getConnectionDuration << " (" << _connectionPool.getNumConnectionsPerHost(host) << " connections now open to " << host << ")"; op->_inSetup = false; op->finish(RemoteCommandResponse()); return; } LOG(3) << "Initiating asynchronous command: " << redact(op->request().toString()); auto beginStatus = op->beginCommand(op->request()); if (!beginStatus.isOK()) { return _completeOperation(op, beginStatus); } _asyncRunCommand(op, [this, op](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, op]() { _completedOpCallback(op); }); }); } void NetworkInterfaceASIO::_completedOpCallback(AsyncOp* op) { auto response = op->command()->response(op, op->operationProtocol(), now(), _metadataHook.get()); _completeOperation(op, response); } void NetworkInterfaceASIO::_networkErrorCallback(AsyncOp* op, const std::error_code& ec) { ErrorCodes::Error errorCode = (ec.category() == mongoErrorCategory()) ? ErrorCodes::fromInt(ec.value()) : ErrorCodes::HostUnreachable; _completeOperation(op, {errorCode, ec.message(), Milliseconds(now() - op->_start)}); } // NOTE: This method may only be called by ASIO threads // (do not call from methods entered by TaskExecutor threads) void NetworkInterfaceASIO::_completeOperation(AsyncOp* op, ResponseStatus resp) { auto metadata = op->getResponseMetadata(); if (!metadata.isEmpty()) { resp.metadata = metadata; } // Cancel this operation's timeout. Note that the timeout callback may already be running, // may have run, or may have already been scheduled to run in the near future. if (op->_timeoutAlarm) { op->_timeoutAlarm->cancel(); } if (resp.status.code() == ErrorCodes::ExceededTimeLimit || resp.status.code() == ErrorCodes::NetworkInterfaceExceededTimeLimit) { _numTimedOutOps.fetchAndAdd(1); } if (op->_inSetup) { // If we are in setup we should only be here if we failed to connect. MONGO_ASIO_INVARIANT(!resp.isOK(), "Failed to connect in setup", op); // If we fail during connection, we won't be able to access any of op's members after // calling finish(), so we return here. log() << "Failed to connect to " << op->request().target << " - " << resp.status; op->finish(std::move(resp)); return; } if (op->_inRefresh) { // If we are in refresh we should only be here if we failed to heartbeat. MONGO_ASIO_INVARIANT(!resp.isOK(), "In refresh, but did not fail to heartbeat", op); // If we fail during heartbeating, we won't be able to access any of op's members after // calling finish(), so we return here. log() << "Failed asio heartbeat to " << op->request().target << " - " << redact(resp.status); _numFailedOps.fetchAndAdd(1); op->finish(std::move(resp)); return; } if (!resp.isOK()) { // In the case that resp is not OK, but _inSetup is false, we are using a connection // that // we got from the pool to execute a command, but it failed for some reason. LOG(2) << "Failed to execute command: " << redact(op->request().toString()) << " reason: " << redact(resp.status); if (resp.status.code() != ErrorCodes::CallbackCanceled) { _numFailedOps.fetchAndAdd(1); } } else { _numSucceededOps.fetchAndAdd(1); } std::unique_ptr ownedOp; { stdx::lock_guard lk(_inProgressMutex); auto iter = _inProgress.find(op); MONGO_ASIO_INVARIANT_INLOCK( iter != _inProgress.end(), "Could not find AsyncOp in _inProgress", op); ownedOp = std::move(iter->second); _inProgress.erase(iter); } op->finish(std::move(resp)); MONGO_ASIO_INVARIANT(static_cast(ownedOp), "Invalid AsyncOp", op); auto conn = std::move(op->_connectionPoolHandle); auto asioConn = static_cast(conn.get()); // Prevent any other threads or callbacks from accessing this op so we may safely complete // and destroy it. It is key that we do this after we remove the op from the _inProgress map // or someone else in cancelCommand could read the bumped generation and cancel the next // command that uses this op. See SERVER-20556. { stdx::lock_guard lk(op->_access->mutex); ++(op->_access->id); } // We need to bump the generation BEFORE we call reset() or we could flip the timeout in the // timeout callback before returning the AsyncOp to the pool. ownedOp->reset(); asioConn->bindAsyncOp(std::move(ownedOp)); if (!resp.isOK()) { asioConn->indicateFailure(resp.status); } else { asioConn->indicateUsed(); asioConn->indicateSuccess(); } signalWorkAvailable(); } void NetworkInterfaceASIO::_asyncRunCommand(AsyncOp* op, NetworkOpHandler handler) { LOG(2) << "Starting asynchronous command " << op->request().id << " on host " << op->request().target.toString(); if (MONGO_FAIL_POINT(NetworkInterfaceASIOasyncRunCommandFail)) { _validateAndRun(op, asio::error::basic_errors::network_unreachable, [] {}); return; } // We invert the following steps below to run a command: // 1 - send the given command // 2 - receive a header for the response // 3 - validate and receive response body // 4 - advance the state machine by calling handler() auto cmd = op->command(); // Step 4 auto recvMessageCallback = [this, cmd, handler, op](std::error_code ec, size_t bytes) { // We don't call _validateAndRun here as we assume the caller will. handler(ec, bytes); }; // Step 3 auto recvHeaderCallback = [this, cmd, handler, recvMessageCallback, op](std::error_code ec, size_t bytes) { // The operation could have been canceled after starting the command, but before // receiving the header _validateAndRun(op, ec, [this, op, recvMessageCallback, ec, bytes, cmd, handler] { // validate response id uint32_t expectedId = cmd->toSend().header().getId(); uint32_t actualId = cmd->header().constView().getResponseToMsgId(); if (actualId != expectedId) { LOG(3) << "got wrong response:" << " expected response id: " << expectedId << ", got response id: " << actualId; return handler(make_error_code(ErrorCodes::ProtocolError), bytes); } asyncRecvMessageBody(cmd->conn().stream(), &cmd->header(), &cmd->toRecv(), std::move(recvMessageCallback)); }); }; // Step 2 auto sendMessageCallback = [this, cmd, handler, recvHeaderCallback, op](std::error_code ec, size_t bytes) { _validateAndRun(op, ec, [this, cmd, op, recvHeaderCallback] { asyncRecvMessageHeader( cmd->conn().stream(), &cmd->header(), std::move(recvHeaderCallback)); }); }; // Step 1 asyncSendMessage(cmd->conn().stream(), &cmd->toSend(), std::move(sendMessageCallback)); } void NetworkInterfaceASIO::_runConnectionHook(AsyncOp* op) { if (!_hook) { return _beginCommunication(op); } auto swOptionalRequest = callNoexcept(*_hook, &NetworkConnectionHook::makeRequest, op->request().target); if (!swOptionalRequest.isOK()) { return _completeOperation(op, swOptionalRequest.getStatus()); } auto optionalRequest = std::move(swOptionalRequest.getValue()); if (optionalRequest == boost::none) { return _beginCommunication(op); } auto beginStatus = op->beginCommand(*optionalRequest); if (!beginStatus.isOK()) { return _completeOperation(op, beginStatus); } auto finishHook = [this, op]() { auto response = op->command()->response(op, op->operationProtocol(), now(), _metadataHook.get()); if (!response.isOK()) { return _completeOperation(op, response); } auto handleStatus = callNoexcept( *_hook, &NetworkConnectionHook::handleReply, op->request().target, std::move(response)); if (!handleStatus.isOK()) { return _completeOperation(op, handleStatus); } return _beginCommunication(op); }; return _asyncRunCommand(op, [this, op, finishHook](std::error_code ec, std::size_t bytes) { _validateAndRun(op, ec, finishHook); }); } } // namespace executor } // namespace mongo