void RemoteObject::close(const std::string& reason, bool fromSignal) { qiLogDebug() << "Closing remote object"; TransportSocketPtr socket; { boost::mutex::scoped_lock lock(_socketMutex); socket = _socket; _socket.reset(); } if (socket) { // Do not hold any lock when invoking signals. qiLogDebug() << "Removing connection from socket " << (void*)socket.get(); socket->messagePendingDisconnect(_service, TransportSocket::ALL_OBJECTS, _linkMessageDispatcher); if (!fromSignal) socket->disconnected.disconnect(_linkDisconnected); } std::map<int, qi::Promise<AnyReference> > promises; { boost::mutex::scoped_lock lock(_promisesMutex); promises = _promises; _promises.clear(); } // Nobody should be able to add anything to promises at this point. std::map<int, qi::Promise<AnyReference> >::iterator it; for (it = promises.begin(); it != promises.end(); ++it) { qiLogVerbose() << "Reporting error for request " << it->first << "(" << reason << ")"; it->second.setError(reason); } //@warning: remove connection are not removed // not very important ATM, because RemoteObject // cant be reconnected }
void GwObjectHost::assignClientMessageObjectsGwIds(const Signature& signature, Message& msg, TransportSocketPtr sender) { // if there's no chance of any object being in the call we're done. if (!hasObjectsSomewhere(signature)) return; AnyReference callParameters = msg.value(signature, sender); // re-serialize the arguments so that the objects can receive a GW-specific objectId // ObjectHost uses a static int for its objectId so we're OK instantiating multiple // ones. Message forward; MockObjectHost host(Message::Service_Server); forward.setFlags(msg.flags()); forward.setValue(callParameters, signature, &host, sender.get()); msg.setBuffer(forward.buffer()); // The message will store all the objects it serializes in the host. const ObjectHost::ObjectMap& objects = host.objects(); std::map<GwObjectId, MetaObject> newObjectsMetaObjects; std::map<GwObjectId, std::pair<TransportSocketPtr, ObjectAddress> > newObjectsOrigin; std::map<ObjectAddress, GwObjectId> newHostObjectBank; for (ObjectHost::ObjectMap::const_iterator it = objects.begin(), end = objects.end(); it != end; ++it) { GwObjectId oid = it->first; ServiceBoundObject* sbo = static_cast<ServiceBoundObject*>(it->second.get()); RemoteObject* ro = static_cast<RemoteObject*>(sbo->object().asGenericObject()->value); ObjectAddress addr; addr.service = ro->service(); addr.object = ro->object(); ro->setTransportSocket(TransportSocketPtr()); newObjectsMetaObjects[oid] = ro->metaObject(); newObjectsOrigin[oid] = std::make_pair(sender, addr); newHostObjectBank[addr] = oid; // We set an empty transportsocket. // Otherwise when we destroy `passed` below, the remoteobject // will attempt to send back home a `terminate` message, which we don't want. // By setting a null socket the object will stay alive on the remote end. qiLogDebug() << "Message " << msg.address() << ", Object connection: {" << addr.service << "," << addr.object << "} <=> {0," << oid << "}"; } { boost::upgrade_lock<boost::shared_mutex> lock(_mutex); boost::upgrade_to_unique_lock<boost::shared_mutex> unique_lock(lock); _objectsMetaObjects.insert(newObjectsMetaObjects.begin(), newObjectsMetaObjects.end()); _objectsOrigin.insert(newObjectsOrigin.begin(), newObjectsOrigin.end()); _hostObjectBank[sender].insert(newHostObjectBank.begin(), newHostObjectBank.end()); } callParameters.destroy(); }
/* * Corner case to manage (TODO): * * You are connecting to machineId foo, you are machineId bar. foo and bar are * on different sub-networks with the same netmask. They sadly got the same IP * on their subnet: 192.168.1.42. When trying to connect to foo from bar, we * will try to connect its endpoints, basically: * - tcp://1.2.3.4:1333 (public IP) * - tcp://192.168.1.42:1333 (subnet public IP) * If bar is listening on port 1333, we may connect to it instead of foo (our * real target). */ void TransportSocketCache::onSocketParallelConnectionAttempt(Future<void> fut, TransportSocketPtr socket, Url url, const ServiceInfo& info) { boost::mutex::scoped_lock lock(_socketMutex); if (_dying) { qiLogDebug() << "ConnectionAttempt: TransportSocketCache is closed"; if (!fut.hasError()) { _allPendingConnections.remove(socket); socket->disconnect(); } return; } ConnectionMap::iterator machineIt = _connections.find(info.machineId()); std::map<Url, ConnectionAttemptPtr>::iterator urlIt; if (machineIt != _connections.end()) urlIt = machineIt->second.find(url); if (machineIt == _connections.end() || urlIt == machineIt->second.end()) { // The socket was disconnected at some point, and we removed it from our map: // return early. _allPendingConnections.remove(socket); socket->disconnect(); return; } ConnectionAttemptPtr attempt = urlIt->second; attempt->attemptCount--; if (attempt->state != State_Pending) { qiLogDebug() << "Already connected: reject socket " << socket.get() << " endpoint " << url.str(); _allPendingConnections.remove(socket); socket->disconnect(); checkClear(attempt, info.machineId()); return; } if (fut.hasError()) { // Failing to connect to some of the endpoint is expected. qiLogDebug() << "Could not connect to service #" << info.serviceId() << " through url " << url.str(); _allPendingConnections.remove(socket); // It's a critical error if we've exhausted all available endpoints. if (attempt->attemptCount == 0) { std::stringstream err; err << "Could not connect to service #" << info.serviceId() << ": no endpoint answered."; qiLogError() << err.str(); attempt->promise.setError(err.str()); attempt->state = State_Error; checkClear(attempt, info.machineId()); } return; } socket->disconnected.connect(&TransportSocketCache::onSocketDisconnected, this, socket, url, _1, info) .setCallType(MetaCallType_Direct); attempt->state = State_Connected; attempt->endpoint = socket; attempt->promise.setValue(socket); qiLogDebug() << "Connected to service #" << info.serviceId() << " through url " << url.str() << " and socket " << socket.get(); }
qi::Future<AnyReference> RemoteObject::metaCall(AnyObject, unsigned int method, const qi::GenericFunctionParameters &in, MetaCallType callType, Signature returnSignature) { MetaMethod *mm = metaObject().method(method); if (!mm) { std::stringstream ss; ss << "Method " << method << " not found on service " << _service; return makeFutureError<AnyReference>(ss.str()); } float canConvert = 1; if (returnSignature.isValid()) { canConvert = mm->returnSignature().isConvertibleTo(returnSignature); qiLogDebug() << "return type conversion score: " << canConvert; if (canConvert == 0) { // last chance for dynamics and adventurous users canConvert = returnSignature.isConvertibleTo(mm->returnSignature()); if (canConvert == 0) return makeFutureError<AnyReference>( "Call error: will not be able to convert return type from " + mm->returnSignature().toString() + " to " + returnSignature.toString()); else qiLogVerbose() << "Return signature might be incorrect depending on the value, from " + mm->returnSignature().toString() + " to " + returnSignature.toString(); } } /* The promise will be set: - From here in case of error - From a network callback, called asynchronously in thread pool So it is safe to use a sync promise. */ qi::Promise<AnyReference> out(&PromiseNoop<AnyReference>, FutureCallbackType_Sync); qi::Message msg; TransportSocketPtr sock; // qiLogDebug() << this << " metacall " << msg.service() << " " << msg.function() <<" " << msg.id(); { boost::mutex::scoped_lock lock(_socketMutex); boost::mutex::scoped_lock lock2(_promisesMutex); // Check socket while holding the lock to avoid a race with close() // where we would add a promise to the map after said map got cleared if (!_socket || !_socket->isConnected()) { return makeFutureError<AnyReference>("Socket is not connected"); } // The remote object can be concurrently closed / other operation that modifies _socket // (even set it to null). We store the current socket locally so that the behavior // of metacall stays consistent throughout the function's execution. sock = _socket; if (_promises.find(msg.id()) != _promises.end()) { qiLogError() << "There is already a pending promise with id " << msg.id(); } qiLogDebug() << "Adding promise id:" << msg.id(); _promises[msg.id()] = out; } qi::Signature funcSig = mm->parametersSignature(); try { msg.setValues(in, funcSig, this, sock.get()); } catch(const std::exception& e) { qiLogVerbose() << "setValues exception: " << e.what(); if (!sock->remoteCapability("MessageFlags", false)) throw e; // Delegate conversion to the remote end. msg.addFlags(Message::TypeFlag_DynamicPayload); msg.setValues(in, "m", this, sock.get()); } if (canConvert < 0.2) { msg.addFlags(Message::TypeFlag_ReturnType); msg.setValue(returnSignature.toString(), Signature("s")); } msg.setType(qi::Message::Type_Call); msg.setService(_service); msg.setObject(_object); msg.setFunction(method); //error will come back as a error message if (!sock->isConnected() || !sock->send(msg)) { qi::MetaMethod* meth = metaObject().method(method); std::stringstream ss; if (meth) { ss << "Network error while sending data to method: '"; ss << meth->toString(); ss << "'."; } else { ss << "Network error while sending data an unknown method (id=" << method << ")."; } if (!sock->isConnected()) { ss << " Socket is not connected."; qiLogVerbose() << ss.str(); } else { qiLogError() << ss.str(); } out.setError(ss.str()); { boost::mutex::scoped_lock lock(_promisesMutex); qiLogDebug() << "Removing promise id:" << msg.id(); _promises.erase(msg.id()); } } else out.setOnCancel(qi::bind(&RemoteObject::onFutureCancelled, this, msg.id())); return out.future(); }