[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[vidalia-svn] r2838: OK, story time: So, Qt really doesn't like it if you try to (vidalia/trunk/src/torcontrol)
Author: edmanm
Date: 2008-07-06 18:43:48 -0400 (Sun, 06 Jul 2008)
New Revision: 2838
Modified:
vidalia/trunk/src/torcontrol/CMakeLists.txt
vidalia/trunk/src/torcontrol/controlconnection.cpp
vidalia/trunk/src/torcontrol/controlconnection.h
vidalia/trunk/src/torcontrol/controlsocket.cpp
vidalia/trunk/src/torcontrol/controlsocket.h
Log:
OK, story time: So, Qt really doesn't like it if you try to send data on a
socket in one thread and read data from it in another thread, even if you do
your locking correctly. Touching the socket in a thread other than the one in
which it was instantiated == death. I started working on a patch that does
away with threads and blocking I/O and simply uses Qt's signals and slots for
dealing with socket operations asynchronously from the main thread. This patch
got really big (already 3000+ lines and I'm not yet done) and isn't something
we want to do when we're trying to put out a 'stable' Vidalia bundle. We'll save
that patch for the next version (or start playing the stable/dev branch game?).
So, here's a hack much like what was removed in r2580, except this actually
works with Qt 4.4. The basic idea is to post an event to the control socket,
which will get delivered to the socket in the correct thread. The event tells
the socket what to send, and the main thread waits for the result of the send.
Sucky, huh?
Modified: vidalia/trunk/src/torcontrol/CMakeLists.txt
===================================================================
--- vidalia/trunk/src/torcontrol/CMakeLists.txt 2008-07-06 22:41:50 UTC (rev 2837)
+++ vidalia/trunk/src/torcontrol/CMakeLists.txt 2008-07-06 22:43:48 UTC (rev 2838)
@@ -28,6 +28,7 @@
replyline.cpp
routerdescriptor.cpp
routerstatus.cpp
+ sendcommandevent.cpp
serverstatusevent.cpp
stream.cpp
tcglobal.cpp
Modified: vidalia/trunk/src/torcontrol/controlconnection.cpp
===================================================================
--- vidalia/trunk/src/torcontrol/controlconnection.cpp 2008-07-06 22:41:50 UTC (rev 2837)
+++ vidalia/trunk/src/torcontrol/controlconnection.cpp 2008-07-06 22:43:48 UTC (rev 2838)
@@ -34,6 +34,7 @@
_events = events;
_status = Unset;
_sock = 0;
+ _sendWaiter = new SendCommandEvent::SendWaiter();
}
/** Destructor. */
@@ -41,8 +42,10 @@
{
/* Exit the event loop */
exit();
- /* Wait for the thread to finish. */
+ /* Wait for the thread to finish */
wait();
+ /* Clean up after the send waiter */
+ delete _sendWaiter;
}
/** Connect to the specified Tor control interface. */
@@ -200,45 +203,47 @@
ControlConnection::send(const ControlCommand &cmd,
ControlReply &reply, QString *errmsg)
{
- ReceiveWaiter w;
bool result = false;
QString errstr;
-
- _connMutex.lock();
- if (!_sock) {
- _connMutex.unlock();
- return err(errmsg, tr("Control socket is not connected."));
- }
- if (_sock->sendCommand(cmd, &errstr)) {
+
+ _recvMutex.lock();
+ if (send(cmd, &errstr)) {
/* Create and enqueue a new receive waiter */
- _recvQueue.enqueue(&w);
- _connMutex.unlock();
+ ReceiveWaiter *w = new ReceiveWaiter();
+ _recvQueue.enqueue(w);
+ _recvMutex.unlock();
+
+ /* Wait for and get the result, clean up, and return */
+ result = w->getResult(&reply, &errstr);
+ if (!result)
+ tc::error("Failed to receive control reply: %1").arg(errstr);
+ delete w;
} else {
- _connMutex.unlock();
tc::error("Failed to send control command (%1): %2").arg(cmd.keyword())
.arg(errstr);
- return err(errmsg, errstr);
+ _recvMutex.unlock();
}
- /* Wait for and get the result, clean up, and return */
- result = w.getResult(&reply, &errstr);
- if (!result) {
- tc::error("Failed to receive control reply: %1").arg(errstr);
- if (errmsg)
- *errmsg = errstr;
- }
+ if (!result && errmsg)
+ *errmsg = errstr;
return result;
}
-/** Sends a control command to Tor and returns without waiting for the
- * response. */
+/** Sends a control command to Tor and returns true if the command was sent
+ * successfully. Otherwise, returns false and <b>*errmsg</b> (if supplied)
+ * will be set. */
bool
ControlConnection::send(const ControlCommand &cmd, QString *errmsg)
{
- QMutexLocker locker(&_connMutex);
- if (!_sock)
+ _connMutex.lock();
+ if (!_sock || !_sock->isConnected()) {
+ _connMutex.unlock();
return err(errmsg, tr("Control socket is not connected."));
- return _sock->sendCommand(cmd, errmsg);
+ }
+ QCoreApplication::postEvent(_sock, new SendCommandEvent(cmd, _sendWaiter));
+ _connMutex.unlock();
+
+ return _sendWaiter->getResult(errmsg);
}
/** Called when there is data on the control socket. */
@@ -263,10 +268,12 @@
/* Response to a previous command */
tc::debug("Control Reply: %1").arg(reply.toString());
+ _recvMutex.lock();
if (!_recvQueue.isEmpty()) {
waiter = _recvQueue.dequeue();
waiter->setResult(true, reply);
}
+ _recvMutex.unlock();
}
} else {
tc::error("Unable to read control reply: %1").arg(errmsg);
@@ -311,14 +318,19 @@
delete _sock;
delete _connectTimer;
_sock = 0;
+ _connMutex.unlock();
/* If there are any messages waiting for a response, clear them. */
+ if (_sendWaiter->status() == SendCommandEvent::SendWaiter::Waiting)
+ _sendWaiter->setResult(false, tr("Control socket is not connected."));
+
+ _recvMutex.lock();
while (!_recvQueue.isEmpty()) {
ReceiveWaiter *w = _recvQueue.dequeue();
w->setResult(false, ControlReply(),
tr("Control socket is not connected."));
}
- _connMutex.unlock();
+ _recvMutex.unlock();
}
Modified: vidalia/trunk/src/torcontrol/controlconnection.h
===================================================================
--- vidalia/trunk/src/torcontrol/controlconnection.h 2008-07-06 22:41:50 UTC (rev 2837)
+++ vidalia/trunk/src/torcontrol/controlconnection.h 2008-07-06 22:43:48 UTC (rev 2838)
@@ -28,6 +28,7 @@
#include "eventtype.h"
#include "controlsocket.h"
#include "torevents.h"
+#include "sendcommandevent.h"
class ControlConnection : public QThread
@@ -98,6 +99,7 @@
QHostAddress _addr; /**< Address of Tor's control interface. */
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. */
@@ -122,6 +124,7 @@
QString _errmsg; /**< Error message if the reply fails. */
};
QQueue<ReceiveWaiter *> _recvQueue; /**< Objects waiting for a reply. */
+ SendCommandEvent::SendWaiter* _sendWaiter;
};
#endif
Modified: vidalia/trunk/src/torcontrol/controlsocket.cpp
===================================================================
--- vidalia/trunk/src/torcontrol/controlsocket.cpp 2008-07-06 22:41:50 UTC (rev 2837)
+++ vidalia/trunk/src/torcontrol/controlsocket.cpp 2008-07-06 22:43:48 UTC (rev 2838)
@@ -18,6 +18,7 @@
#include "tcglobal.h"
#include "controlsocket.h"
+#include "sendcommandevent.h"
/** 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. */
@@ -37,6 +38,24 @@
return (isValid() && state() == QAbstractSocket::ConnectedState);
}
+/** Processes custom events sent to this object (e.g. SendCommandEvents) from
+ * other threads. */
+void
+ControlSocket::customEvent(QEvent *event)
+{
+ if (event->type() == CustomEventType::SendCommandEvent) {
+ SendCommandEvent *sce = dynamic_cast<SendCommandEvent *>(event);
+ if (! sce)
+ return;
+
+ QString errmsg;
+ bool result = sendCommand(sce->command(), &errmsg);
+ if (sce->waiter())
+ sce->waiter()->setResult(result, errmsg);
+ sce->accept();
+ }
+}
+
/** Send a control command to Tor on the control socket, conforming to Tor's
* Control Protocol V1:
*
@@ -46,7 +65,7 @@
*/
bool
ControlSocket::sendCommand(ControlCommand cmd, QString *errmsg)
-{
+{
if (!isConnected()) {
return err(errmsg, tr("Control socket is not connected."));
}
@@ -125,7 +144,8 @@
return false;
}
- /* The implementation below is (loosely) based on the Java control library from Tor */
+ /* The implementation below is (loosely) based on the Java control library
+ * from Tor */
do {
/* Read a line of the response */
if (!readLine(line, errmsg)) {
Modified: vidalia/trunk/src/torcontrol/controlsocket.h
===================================================================
--- vidalia/trunk/src/torcontrol/controlsocket.h 2008-07-06 22:41:50 UTC (rev 2837)
+++ vidalia/trunk/src/torcontrol/controlsocket.h 2008-07-06 22:43:48 UTC (rev 2838)
@@ -44,6 +44,9 @@
static QString toString(const QAbstractSocket::SocketError error);
protected:
+ /** Processes custom events sent to this object (e.g. SendCommandEvents)
+ * from other threads. */
+ void customEvent(QEvent *event);
/** Reads line data off the socket in chunks. */
bool readLineData(QString &line, QString *errmsg = 0);
/** Reads a line of data from the socket (blocking) */