[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[vidalia-svn] r1914: Do the control connection asynchronously so we aren't trying (in trunk: . src/control)



Author: edmanm
Date: 2007-09-05 23:51:37 -0400 (Wed, 05 Sep 2007)
New Revision: 1914

Modified:
   trunk/
   trunk/src/control/controlconnection.cpp
   trunk/src/control/controlconnection.h
   trunk/src/control/controlsocket.cpp
   trunk/src/control/controlsocket.h
Log:
 r2131@adrastea:  edmanm | 2007-09-05 23:48:45 -0400
 Do the control connection asynchronously so we aren't trying to call
 waitForConnected() in the control socket thread before it has an event loop
 running.



Property changes on: trunk
___________________________________________________________________
 svk:merge ticket from /vidalia/local/trunk [r2131] on 54b3572a-7227-0410-958f-53ecd705b71a

Modified: trunk/src/control/controlconnection.cpp
===================================================================
--- trunk/src/control/controlconnection.cpp	2007-09-06 03:51:29 UTC (rev 1913)
+++ trunk/src/control/controlconnection.cpp	2007-09-06 03:51:37 UTC (rev 1914)
@@ -33,16 +33,16 @@
 #include "controlconnection.h"
 
 /** Maximum number of times we'll try to connect to Tor before giving up.*/
-#define MAX_CONNECT_ATTEMPTS  6
-/** Time to wait between control connection attempts (in microseconds). */
-#define CONNECT_RETRY_DELAY   500*1000
+#define MAX_CONNECT_ATTEMPTS  5
+/** Time to wait between control connection attempts (in milliseconds). */
+#define CONNECT_RETRY_DELAY   2*1000
 
 
 /** Default constructor. */
 ControlConnection::ControlConnection(TorEvents *events)
 {
   _events = events;
-  _sock = 0;
+  _status = Unset;
 }
 
 /** Destructor. */
@@ -58,81 +58,141 @@
 void
 ControlConnection::connect(QHostAddress addr, quint16 port)
 {
+  if (isRunning()) {
+    vError("Bug: Tried to call ControlConnection::connect() when the "
+          "control thread is already running.");
+    return;
+  }
+
   /* Save the destination information */
   _addr = addr;
   _port = port;
+  _sock = 0;
+  _connectAttempt = 0;
+  setStatus(Connecting);
+
   /* Kick off the thread in which the control socket will live */
   QThread::start();
 }
 
 /** Attempt to establish a connection to Tor's control interface. We will try
-* a maximum of MAX_CONNECT_ATTEMPTS, waiting CONNECT_RETRY_DELAY between each
-* attempt, to give slow Tors a chance to finish binding their control port. */
-bool
+ * a maximum of MAX_CONNECT_ATTEMPTS, waiting CONNECT_RETRY_DELAY between each
+ * attempt, to give slow Tors a chance to finish binding their control port. */
+void
 ControlConnection::connect()
 {
-  QString errmsg;
-  bool result;
-  
-  setStatus(Connecting);
-  for (int i = 0; i < MAX_CONNECT_ATTEMPTS; i++) {
-    /* Check if we're supposed to cancel our attempt to connect */
-    if (status() != Connecting) {
-      vNotice("Cancelling attempt to connect to Tor's control port.");
-      return false;
-    }
-    
-    /* Try to connect */
-    _connMutex.lock();
-    vDebug("Control connection attempt %1 of %2").arg(i+1)
-                                                  .arg(MAX_CONNECT_ATTEMPTS);
-    result = _sock->connect(_addr, _port, &errmsg); 
-    _connMutex.unlock();
-    if (result) {
-      vInfo("Connected to Tor's control port.");
-      setStatus(Connected);
-      emit connected();
-      return true;
-    }
-    QThread::usleep(CONNECT_RETRY_DELAY);
-  }
-  setStatus(Disconnected);
-  vWarn("Failed to connect to Tor's control port after %1 attempts: %2")
-                                                      .arg(MAX_CONNECT_ATTEMPTS)
-                                                      .arg(errmsg);
-  emit connectFailed(errmsg);
-  return false;
+  _connectAttempt++;
+  vInfo("Connecting to Tor (Attempt %1 of %2)").arg(_connectAttempt)
+                                               .arg(MAX_CONNECT_ATTEMPTS);
+  _connMutex.lock();
+  _sock->connectToHost(_addr, _port);
+  _connMutex.unlock();
 }
 
 /** Disconnect from Tor's control interface. */
 void
 ControlConnection::disconnect()
 {
-  /* Stop the event loop */
+  setStatus(Disconnecting);
+  _connMutex.lock();
+  _sock->disconnectFromHost();
+  _connMutex.unlock();
+}
+
+/** Called when the control socket is connected. This method checks that the
+ * control protocol version of the Tor we connected to is at least V1. */
+void
+ControlConnection::onConnected()
+{
+  setStatus(Connected);
+  emit connected();
+}
+
+/** Called when the control socket is disconnected and stops the control
+ * thread's event loop. */
+void
+ControlConnection::onDisconnected()
+{
+  setStatus(Disconnected);
+  emit disconnected();
   exit(0);
 }
 
+/** Called when the control socket encounters <b>error</b>. */
+void
+ControlConnection::onError(QAbstractSocket::SocketError error)
+{
+  if (status() == Connecting) {
+    /* If we got a 'connection refused' and we haven't exceeded
+     * MAX_CONNECT_ATTEMPTS, then try to reconnect since Tor is probably
+     * running, but it doesn't have a ControlPort open yet. */
+    if (error == QAbstractSocket::ConnectionRefusedError &&
+        _connectAttempt < MAX_CONNECT_ATTEMPTS) {
+      vInfo("Control connection refused. Retrying in %1ms.")
+                                                  .arg(CONNECT_RETRY_DELAY);
+      _connectTimer->start(CONNECT_RETRY_DELAY);
+    } else {
+      /* Exceeded maximum number of connect attempts. Give up. */
+      QString errstr = ControlSocket::toString(error);
+      vWarn("Vidalia was unable to connect to Tor: %1").arg(errstr);
+      emit connectFailed(tr("Vidalia was unable to connect to Tor. (%1)")
+                                                             .arg(errstr));
+      setStatus(Disconnected);
+    }
+  } else if (error == QAbstractSocket::RemoteHostClosedError) {
+    /* Tor closed the connection. This is common when we send a 'shutdown' or
+     * 'halt' signal to Tor and doesn't need to be logged as loudly. */
+    vNotice("Tor closed the control connection.");
+  } else {
+    /* Some other error. */
+    /*XXX We may want to be emitting these so the GUI thread can learn about
+     * them and display an error message. */
+    vWarn("Control socket error: %1").arg(ControlSocket::toString(error));
+  }
+}
+
 /** Cancels a pending control connection to Tor. */
 void
 ControlConnection::cancelConnect()
 {
-  QMutexLocker locker(&_connMutex);
-  _status = Disconnected;
+  vNotice("Control connection attempt cancelled.");
+  setStatus(Disconnected);
+  exit(0);
 }
 
 /** Returns the status of the control connection. */
 ControlConnection::Status
 ControlConnection::status()
 {
-  QMutexLocker locker(&_connMutex);
+  QMutexLocker locker(&_statusMutex);
   return _status;
 }
 
+/** Returns a string description of the control Status value
+ * <b>status</b>. */
+QString
+ControlConnection::statusString(Status status)
+{
+  QString str;
+  switch (status) {
+    case Unset:  str = "Unset"; break;
+    case Disconnected:  str = "Disconnected"; break;
+    case Disconnecting: str = "Disconnecting"; break;
+    case Connecting: str = "Connecting"; break;
+    case Connected: str = "Connected"; break;
+    default:  str = "unknown";
+  }
+  return str;
+}
+
 /** Sets the control connection status. */
 void
 ControlConnection::setStatus(Status status)
 {
-  QMutexLocker locker(&_connMutex);
+  QMutexLocker locker(&_statusMutex);
+  vNotice("Control connection status changed from '%1' to '%2'")
+                                    .arg(statusString(_status))
+                                    .arg(statusString(status));
   _status = status;
 }
 
@@ -178,6 +238,8 @@
   /* Check for a valid and connected socket */
   _connMutex.lock();
   if (!_sock || _status != Connected) {
+    vDebug("Unable to send control command '%1' when socket status is '%2'")
+                                            .arg(cmd.keyword()).arg(_status);
     _connMutex.unlock();
     return err(errmsg, tr("Control socket is not connected."));
   }
@@ -278,29 +340,36 @@
   /* Create a new control socket */
   _connMutex.lock();
   _sock = new ControlSocket();
+  _connectTimer = new QTimer();
+  _connectTimer->setSingleShot(true);
+  
   QObject::connect(_sock, SIGNAL(readyRead()), this, SLOT(onReadyRead()),
                    Qt::DirectConnection);
-  QObject::connect(_sock, SIGNAL(disconnected()), this, SLOT(quit()),
+  QObject::connect(_sock, SIGNAL(disconnected()), this, SLOT(onDisconnected()),
                    Qt::DirectConnection);
+  QObject::connect(_sock, SIGNAL(connected()), this, SLOT(onConnected()),
+                   Qt::DirectConnection);
+  QObject::connect(_sock, SIGNAL(error(QAbstractSocket::SocketError)), 
+                   this, SLOT(onError(QAbstractSocket::SocketError)),
+                   Qt::DirectConnection);
+  QObject::connect(_connectTimer, SIGNAL(timeout()), this, SLOT(connect()),
+                   Qt::DirectConnection);
+
   _sock->installEventFilter(this);
   _connMutex.unlock();
   
   /* Attempt to connect to Tor */
-  if (connect()) {
-    vDebug("Starting control connection event loop.");
-    /* Kick off the event loop */
-    exec();
-    vDebug("Exited control connection event loop.");
-  }
-  
-  /* Update the connection status */
-  setStatus(Disconnected);
-  emit disconnected();
+  connect();
+  vDebug("Starting control connection event loop.");
+  exec();
+  vDebug("Exited control connection event loop.");
 
   /* Clean up the socket */
   _connMutex.lock();
-  QObject::disconnect(_sock, 0, 0, 0);
+  _sock->removeEventFilter(this);
+  _sock->disconnect(this);
   delete _sock;
+  delete _connectTimer;
   _sock = 0;
   _connMutex.unlock();
 

Modified: trunk/src/control/controlconnection.h
===================================================================
--- trunk/src/control/controlconnection.h	2007-09-06 03:51:29 UTC (rev 1913)
+++ trunk/src/control/controlconnection.h	2007-09-06 03:51:37 UTC (rev 1914)
@@ -33,6 +33,8 @@
 #include <QMutex>
 #include <QQueue>
 #include <QWaitCondition>
+#include <QTimer>
+#include <QHostAddress>
 
 #include "eventtype.h"
 #include "controlsocket.h"
@@ -46,9 +48,11 @@
 public:
   /** Control connection status */
   enum Status {
-    Disconnected, /**< Control connection disconnected.    */
-    Connecting,   /**< Control connection attempt pending. */
-    Connected     /**< Control connection established.     */
+    Unset,         /**< Control connection status is not yet set. */
+    Disconnected,  /**< Control connection disconnected.     */
+    Disconnecting, /**< Control connection is disconnecting. */
+    Connecting,    /**< Control connection attempt pending.  */
+    Connected      /**< Control connection established.      */
   };
 
   /** Default constructor. */
@@ -82,14 +86,22 @@
   bool eventFilter(QObject *obj, QEvent *event);
 
 private slots:
+  /** Connects to Tor's control interface. */
+  void connect();
   /** Called when there is data on the control socket. */
   void onReadyRead();
+  /** Called when the control socket is connected. */
+  void onConnected();
+  /** Called when the control socket is disconnected. */
+  void onDisconnected();
+  /** Called when the control socket encounters an error. */
+  void onError(QAbstractSocket::SocketError error);
 
 private:
   /** Sets the control connection status. */
   void setStatus(Status status);
-  /** Connects to Tor's control interface. */
-  bool connect();
+  /** Returns the string description of <b>status</b>. */
+  QString statusString(Status status);
   /** Main thread implementation. */
   void run();
 
@@ -100,6 +112,10 @@
   quint16 _port; /**< Port of Tor's control interface. */
   QMutex _connMutex; /**< Mutex around the control socket. */
   QMutex _recvMutex; /**< Mutex around the queue of ReceiveWaiters. */
+  QMutex _statusMutex; /**< Mutex around the connection status value. */
+  int _connectAttempt; /**< How many times we've tried to connect to Tor while
+                            waiting for Tor to start. */
+  QTimer* _connectTimer; /**< Timer used to delay connect attempts. */
 
   /** Private class used to wait for a response to a control command. */
   class ReceiveWaiter {

Modified: trunk/src/control/controlsocket.cpp
===================================================================
--- trunk/src/control/controlsocket.cpp	2007-09-06 03:51:29 UTC (rev 1913)
+++ trunk/src/control/controlsocket.cpp	2007-09-06 03:51:37 UTC (rev 1914)
@@ -25,17 +25,11 @@
  * \brief Socket used to connect to Tor's control interface
  */
 
-#include <QHostAddress>
 #include <util/string.h>
 #include <vidalia.h>
 
 #include "controlsocket.h"
 
-/** Give up after waiting five seconds for the control socket to connect to
-* Tor. This timeout used to be shorter (three seconds), but some Agnitum
-* OutPost users yelled at us wanting a longer timeout, for some reason. */
-#define CONN_TIMEOUT  5000
-
 /** Timeout reads in 250ms. We can set this to a short value because if there
 * isn't any data to read, we want to return anyway. */
 #define READ_TIMEOUT  250
@@ -46,42 +40,6 @@
 {
 }
 
-/** Connects to Tor's control socket on the specified host and port. If the
- * connection is successful, true is returned. If the connection fails, then
- * this function returns false and sets <b>errmsg</b> appropriately, if not
- * null. */
-bool
-ControlSocket::connect(QHostAddress addr, quint16 port, QString *errmsg)
-{
-  /* Connect the control socket. */
-  vNotice("Connecting to Tor's control port on %1:%2").arg(addr.toString())
-                                                      .arg(port);
-  connectToHost(addr, port);
-  if (!waitForConnected(CONN_TIMEOUT)) {
-    vWarn("Failed to connect to Tor's control port: %1").arg(errorString());
-    return err(errmsg, tr("Error connecting to %1:%2 [%3]")
-                                            .arg(addr.toString())
-                                            .arg(port)
-                                            .arg(errorString()));
-  }
-  return true;
-}
-
-/** Disconnects from Tor's control socket */
-bool
-ControlSocket::disconnect(QString *errmsg)
-{
-  disconnectFromHost();
-  if (isConnected()) {
-    if (!waitForDisconnected(CONN_TIMEOUT)) {
-      vWarn("Failed to disconnect from Tor: %1").arg(errorString());
-      return err(errmsg, tr("Error disconnecting socket. [%1]")
-                                            .arg(errorString()));
-    }
-  }
-  return true;
-}
-
 /** Returns true if the control socket is connected and ready to send or
  * receive. */
 bool
@@ -215,3 +173,37 @@
   return true;
 }
 
+/** Returns the string description of <b>error</b>. */
+QString
+ControlSocket::toString(const QAbstractSocket::SocketError error)
+{
+  QString str;
+  switch (error) {
+    case ConnectionRefusedError:
+      str = "Connection refused by peer."; break;
+    case RemoteHostClosedError:
+      str = "Remote host closed the connection."; break;
+    case HostNotFoundError:
+      str = "Host address not found."; break;
+    case SocketAccessError:
+      str = "Insufficient access privileges."; break;
+    case SocketResourceError:
+      str = "Insufficient resources."; break;
+    case SocketTimeoutError:
+      str = "Socket operation timed out."; break;
+    case DatagramTooLargeError:
+      str = "Datagram size exceeded the operating system limit."; break;
+    case NetworkError:
+      str = "Network error occurred."; break;
+    case AddressInUseError:
+      str = "Specified address already in use."; break;
+    case SocketAddressNotAvailableError:
+      str = "Specified address does not belong to the host."; break;
+    case UnsupportedSocketOperationError:
+      str = "The requested operation is not supported."; break;
+    default:
+      str = "An unidentified error occurred."; break;
+  }
+  return str;
+}
+

Modified: trunk/src/control/controlsocket.h
===================================================================
--- trunk/src/control/controlsocket.h	2007-09-06 03:51:29 UTC (rev 1913)
+++ trunk/src/control/controlsocket.h	2007-09-06 03:51:37 UTC (rev 1914)
@@ -29,7 +29,6 @@
 #define _CONTROLSOCKET_H
 
 #include <QTcpSocket>
-#include <QHostAddress>
 
 #include "controlcommand.h"
 #include "controlreply.h"
@@ -40,14 +39,9 @@
   Q_OBJECT
 
 public:
-  /** Default constructor and destructor */
+  /** Default constructor. */
   ControlSocket();
 
-  /** Connect to Tor on the specified host and port */
-  bool connect(QHostAddress addr, quint16 port, QString *errmsg = 0);
-  /** Disconnect from Tor */
-  bool disconnect(QString *errmsg = 0);
-  
   /** Send a command to Tor */
   bool sendCommand(ControlCommand cmd, QString *errmsg = 0);
   /** Read a response from Tor */
@@ -56,7 +50,10 @@
   /** Returns true if the control socket is connected and ready to send or
    * receive. */
   bool isConnected();
-
+  
+  /** Returns the string description of <b>error</b>. */
+  static QString toString(const QAbstractSocket::SocketError error);
+  
 protected:
   /** Reads line data off the socket in chunks. */
   bool readLineData(QString &line, QString *errmsg = 0);