void TransportSocketCache::onSocketDisconnected(Url url, const ServiceInfo& info)
{
  // remove from the available connections
  boost::mutex::scoped_lock lock(_socketMutex);

  ConnectionMap::iterator machineIt = _connections.find(info.machineId());
  if (machineIt == _connections.end())
    return;
  qiLogDebug() << "onSocketDisconnected: about to erase socket";
  auto attempt = machineIt->second[url];
  attempt->state = State_Error;
  checkClear(attempt, info.machineId());
  auto syncDisconnectInfos = _disconnectInfos.synchronize();
  updateDisconnectInfos(*syncDisconnectInfos, attempt->endpoint);
}
void TransportSocketCache::onSocketDisconnected(TransportSocketPtr socket,
                                                Url url,
                                                const std::string& reason,
                                                const ServiceInfo& info)
{
  // remove from the available connections
  boost::mutex::scoped_lock lock(_socketMutex);

  ConnectionMap::iterator machineIt = _connections.find(info.machineId());
  if (machineIt == _connections.end())
    return;

  machineIt->second[url]->state = State_Error;
  checkClear(machineIt->second[url], info.machineId());
}
Future<TransportSocketPtr> TransportSocketCache::socket(const ServiceInfo& servInfo, const std::string& protocol)
{
  const std::string& machineId = servInfo.machineId();
  ConnectionAttemptPtr couple = boost::make_shared<ConnectionAttempt>();
  couple->relatedUrls = servInfo.endpoints();
  bool local = machineId == os::getMachineId();
  UrlVector connectionCandidates;

  // If the connection is local, we're mainly interested in localhost endpoint
  if (local)
    connectionCandidates = localhost_only(servInfo.endpoints());

  // If the connection isn't local or if the service doesn't expose local endpoints,
  // try and connect to whatever is available.
  if (connectionCandidates.size() == 0)
    connectionCandidates = servInfo.endpoints();

  couple->endpoint = TransportSocketPtr();
  couple->state = State_Pending;
  {
    // If we already have a pending connection to one of the urls, we return the future in question
    boost::mutex::scoped_lock lock(_socketMutex);

    if (_dying)
      return makeFutureError<TransportSocketPtr>("TransportSocketCache is closed.");

    ConnectionMap::iterator machineIt = _connections.find(machineId);
    if (machineIt != _connections.end())
    {
      // Check if any connection to the machine matches one of our urls
      UrlVector& vurls = couple->relatedUrls;
      for (std::map<Url, ConnectionAttemptPtr>::iterator b = machineIt->second.begin(), e = machineIt->second.end();
           b != e;
           b++)
      {
        UrlVector::iterator uIt = std::find(vurls.begin(), vurls.end(), b->first);
        // We found a matching machineId and URL : return the connected endpoint.
        if (uIt != vurls.end())
          return b->second->promise.future();
      }
    }
    // Otherwise, we keep track of all those URLs and assign them the same promise in our map.
    // They will all track the same connection.
    couple->attemptCount = connectionCandidates.size();
    std::map<Url, ConnectionAttemptPtr>& urlMap = _connections[machineId];
    for (UrlVector::iterator it = connectionCandidates.begin(), end = connectionCandidates.end(); it != end; ++it)
    {
      urlMap[*it] = couple;
      TransportSocketPtr socket = makeTransportSocket(it->protocol());
      _allPendingConnections.push_back(socket);
      Future<void> sockFuture = socket->connect(*it);
      qiLogDebug() << "Inserted [" << machineId << "][" << it->str() << "]";
      sockFuture.connect(&TransportSocketCache::onSocketParallelConnectionAttempt, this, _1, socket, *it, servInfo);
    }
  }
  return couple->promise.future();
}
/*
 * 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,
                                                             MessageSocketPtr 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 replied.";
      qiLogError() << err.str();
      attempt->promise.setError(err.str());
      attempt->state = State_Error;
      checkClear(attempt, info.machineId());
    }
    return;
  }
  qi::SignalLink disconnectionTracking = socket->disconnected.connect(
        &TransportSocketCache::onSocketDisconnected, this, url, info)
      .setCallType(MetaCallType_Direct);
  attempt->state = State_Connected;
  attempt->endpoint = socket;
  attempt->promise.setValue(socket);
  attempt->disconnectionTracking = disconnectionTracking;
  qiLogDebug() << "Connected to service #" << info.serviceId() << " through url " << url.str() << " and socket "
               << socket.get();
}