summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Mollekopf <mollekopf@kolabsys.com>2013-10-14 21:52:28 (GMT)
committerChristian Mollekopf <mollekopf@kolabsys.com>2013-10-14 21:52:28 (GMT)
commit2f34b3689b5be523ce934a9f876e0e5e34b5dbc7 (patch)
treeb36f9bb580b2daa0c8c84f452afbf370c1b6e593
parent41b11ca2c9dfbcd570dbb60b350ed010316b4d95 (diff)
downloadlibcalendaring-2f34b3689b5be523ce934a9f876e0e5e34b5dbc7.tar.gz
Backported the kimap threadhandling fixes including the ssl fallback.
-rw-r--r--kimap/session.cpp35
-rw-r--r--kimap/session.h12
-rw-r--r--kimap/session_p.h15
-rw-r--r--kimap/sessionthread.cpp171
-rw-r--r--kimap/sessionthread_p.h27
5 files changed, 179 insertions, 81 deletions
diff --git a/kimap/session.cpp b/kimap/session.cpp
index 031508b..0f758e6 100644
--- a/kimap/session.cpp
+++ b/kimap/session.cpp
@@ -53,13 +53,24 @@ Session::Session( const QString &hostName, quint16 port, QObject *parent)
d->state = Disconnected;
d->jobRunning = false;
- d->thread = new SessionThread(hostName, port, this);
+ d->thread = new SessionThread(hostName, port);
connect(d->thread, SIGNAL(encryptionNegotiationResult(bool,KTcpSocket::SslVersion)),
d, SLOT(onEncryptionNegotiationResult(bool,KTcpSocket::SslVersion)));
- connect(d->thread, SIGNAL(sslError(KSslErrorUiData)), this, SLOT(handleSslError(KSslErrorUiData)));
+ // connect(d->thread, SIGNAL(sslError(KSslErrorUiData)), this, SLOT(handleSslError(KSslErrorUiData)));
+ connect( d->thread, SIGNAL(sslError(KSslErrorUiData)),
+ d, SLOT(handleSslError(KSslErrorUiData)) );
+ connect( d->thread, SIGNAL(socketDisconnected()),
+ d, SLOT(socketDisconnected()) );
+ connect( d->thread, SIGNAL(responseReceived(KIMAP::Message)),
+ d, SLOT(responseReceived(KIMAP::Message)) );
+ connect( d->thread, SIGNAL(socketConnected()),
+ d, SLOT(socketConnected()) );
+ connect( d->thread, SIGNAL(socketActivity()),
+ d, SLOT(socketActivity()) );
+ connect( d->thread, SIGNAL(socketError()),
+ d, SLOT(socketError()) );
d->startSocketTimer();
- d->thread->start();
}
Session::~Session()
@@ -114,11 +125,8 @@ void KIMAP::Session::close()
void SessionPrivate::handleSslError(const KSslErrorUiData& errorData)
{
- if (uiProxy && uiProxy->ignoreSslError(errorData)) {
- QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, true) );
- } else {
- QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, false) );
- }
+ const bool ignoreSslError = uiProxy && uiProxy->ignoreSslError( errorData );
+ thread->sslErrorHandlerResponse(ignoreSslError);
}
SessionPrivate::SessionPrivate( Session *session )
@@ -143,8 +151,8 @@ void SessionPrivate::addJob(Job *job)
queue.append(job);
emit q->jobQueueSizeChanged( q->jobQueueSize() );
- QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(jobDone(KJob*)) );
- QObject::connect( job, SIGNAL(destroyed(QObject*)), q, SLOT(jobDestroyed(QObject*)) );
+ QObject::connect( job, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*)) );
+ QObject::connect( job, SIGNAL(destroyed(QObject*)), this, SLOT(jobDestroyed(QObject*)) );
if ( state!=Session::Disconnected ) {
startNext();
@@ -153,7 +161,7 @@ void SessionPrivate::addJob(Job *job)
void SessionPrivate::startNext()
{
- QTimer::singleShot( 0, q, SLOT(doStartNext()) );
+ QMetaObject::invokeMethod( this, "doStartNext" );
}
void SessionPrivate::doStartNext()
@@ -397,14 +405,15 @@ void SessionPrivate::clearJobQueue()
currentJob->connectionLost();
}
- qDeleteAll(queue);
+ QQueue<Job*> queueCopy = queue; // copy because jobDestroyed calls removeAll
+ qDeleteAll(queueCopy);
queue.clear();
emit q->jobQueueSizeChanged( 0 );
}
void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version)
{
- QMetaObject::invokeMethod( thread, "startSsl", Qt::QueuedConnection, Q_ARG(KTcpSocket::SslVersion, version) );
+ thread->startSsl( version );
}
QString Session::selectedMailBox() const
diff --git a/kimap/session.h b/kimap/session.h
index e23fbc4..ca14ed3 100644
--- a/kimap/session.h
+++ b/kimap/session.h
@@ -135,18 +135,6 @@ class KIMAP_EXPORT Session : public QObject
void stateChanged(KIMAP::Session::State newState, KIMAP::Session::State oldState);
private:
- Q_PRIVATE_SLOT( d, void doStartNext() )
- Q_PRIVATE_SLOT( d, void jobDone( KJob* ) )
- Q_PRIVATE_SLOT( d, void jobDestroyed( QObject* ) )
- Q_PRIVATE_SLOT( d, void responseReceived( const KIMAP::Message& ) )
-
- Q_PRIVATE_SLOT( d, void socketConnected() )
- Q_PRIVATE_SLOT( d, void socketDisconnected() )
- Q_PRIVATE_SLOT( d, void socketError() )
- Q_PRIVATE_SLOT( d, void socketActivity() )
-
- Q_PRIVATE_SLOT( d, void handleSslError( const KSslErrorUiData &errorData ) )
-
friend class SessionPrivate;
SessionPrivate *const d;
};
diff --git a/kimap/session_p.h b/kimap/session_p.h
index 37bfd57..702fef3 100644
--- a/kimap/session_p.h
+++ b/kimap/session_p.h
@@ -46,7 +46,7 @@ class KIMAP_EXPORT SessionPrivate : public QObject
friend class Session;
public:
- SessionPrivate( Session *session );
+ explicit SessionPrivate( Session *session );
virtual ~SessionPrivate();
void addJob(Job *job);
@@ -54,8 +54,6 @@ class KIMAP_EXPORT SessionPrivate : public QObject
void startSsl(const KTcpSocket::SslVersion &version);
void sendData( const QByteArray &data );
- void handleSslError( const KSslErrorUiData &errorData );
-
KTcpSocket::SslVersion negotiatedEncryption() const;
void setSocketTimeout( int ms );
@@ -68,20 +66,23 @@ class KIMAP_EXPORT SessionPrivate : public QObject
void onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion sslVersion);
void onSocketTimeout();
- private:
- void startNext();
void doStartNext();
void jobDone( KJob *job );
void jobDestroyed( QObject *job );
- void clearJobQueue();
void responseReceived( const KIMAP::Message &response );
- void setState(Session::State state);
void socketConnected();
void socketDisconnected();
void socketError();
void socketActivity();
+ void handleSslError( const KSslErrorUiData &errorData );
+
+ private:
+ void startNext();
+ void clearJobQueue();
+ void setState(Session::State state);
+
void startSocketTimer();
void stopSocketTimer();
void restartSocketTimer();
diff --git a/kimap/sessionthread.cpp b/kimap/sessionthread.cpp
index b7a1cb9..8db4873 100644
--- a/kimap/sessionthread.cpp
+++ b/kimap/sessionthread.cpp
@@ -21,6 +21,7 @@
#include <QtCore/QDebug>
#include <QtCore/QTimer>
+#include <QtCore/QThread>
#include <kdebug.h>
@@ -35,38 +36,47 @@ Q_DECLARE_METATYPE(KSslErrorUiData)
static const int _kimap_socketErrorTypeId = qRegisterMetaType<KTcpSocket::Error>();
static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>();
-SessionThread::SessionThread( const QString &hostName, quint16 port, Session *parent )
- : QThread(), m_hostName(hostName), m_port(port),
- m_session(parent), m_socket(0), m_stream(0), m_encryptedMode(false)
+SessionThread::SessionThread( const QString &hostName, quint16 port )
+ : QObject(), m_hostName( hostName ), m_port( port ),
+ m_socket( 0 ), m_stream( 0 ), m_mutex(),
+ m_encryptedMode( false ),
+ triedSslVersions( 0 ), doSslFallback( false )
{
- // Yeah, sounds weird, but QThread object is linked to the parent
- // thread not to itself, and I'm too lazy to introduce yet another
- // internal QObject
- moveToThread(this);
+ // Just like the Qt docs now recommend, for event-driven threads:
+ // don't derive from QThread, create one directly and move the object to it.
+ QThread* thread = new QThread();
+ moveToThread( thread );
+ thread->start();
+ QMetaObject::invokeMethod( this, "threadInit" );
}
SessionThread::~SessionThread()
{
- // don't call quit() directly, this will deadlock in wait() if exec() hasn't run yet
- QMetaObject::invokeMethod( this, "quit" );
- if ( !wait( 10 * 1000 ) ) {
+ QMetaObject::invokeMethod( this, "threadQuit" );
+ if ( !thread()->wait( 10 * 1000 ) ) {
kWarning() << "Session thread refuses to die, killing harder...";
- terminate();
+ thread()->terminate();
// Make sure to wait until it's done, otherwise it can crash when the pthread callback is called
- wait();
+ thread()->wait();
}
+ delete thread();
}
+// Called in primary thread
void SessionThread::sendData( const QByteArray &payload )
{
QMutexLocker locker(&m_mutex);
m_dataQueue.enqueue( payload );
- QTimer::singleShot( 0, this, SLOT(writeDataQueue()) );
+ QMetaObject::invokeMethod( this, "writeDataQueue" );
}
+// Called in secondary thread
void SessionThread::writeDataQueue()
{
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( !m_socket )
+ return;
QMutexLocker locker(&m_mutex);
while ( !m_dataQueue.isEmpty() ) {
@@ -76,9 +86,8 @@ void SessionThread::writeDataQueue()
void SessionThread::readMessage()
{
- QMutexLocker locker(&m_mutex);
-
- if ( m_stream->availableDataSize()==0 ) {
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( !m_stream || m_stream->availableDataSize() == 0 ) {
return;
}
@@ -107,8 +116,11 @@ void SessionThread::readMessage()
}
*payload << Message::Part(literal);
} else {
- // Oops! Something really bad happened
- throw ImapParserException( "Inconsistent state, probably due to some packet loss" );
+ // Oops! Something really bad happened, we won't be able to recover
+ // so close the socket immediately
+ qWarning( "Inconsistent state, probably due to some packet loss" );
+ doCloseSocket();
+ return;
}
}
@@ -119,25 +131,33 @@ void SessionThread::readMessage()
}
if ( m_stream->availableDataSize()>1 ) {
- QTimer::singleShot( 0, this, SLOT(readMessage()) );
+ QMetaObject::invokeMethod( this, "readMessage", Qt::QueuedConnection );
}
}
+// Called in main thread
void SessionThread::closeSocket()
{
- QTimer::singleShot( 0, this, SLOT(doCloseSocket()) );
+ QMetaObject::invokeMethod( this, "doCloseSocket", Qt::QueuedConnection );
}
+// Called in secondary thread
void SessionThread::doCloseSocket()
{
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( !m_socket )
+ return;
m_encryptedMode = false;
m_socket->close();
}
+// Called in secondary thread
void SessionThread::reconnect()
{
- QMutexLocker locker(&m_mutex);
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( m_socket == 0 ) // threadQuit already called
+ return;
if ( m_socket->state() != SessionSocket::ConnectedState &&
m_socket->state() != SessionSocket::ConnectingState ) {
@@ -149,55 +169,117 @@ void SessionThread::reconnect()
}
}
-void SessionThread::run()
+// Called in secondary thread
+void SessionThread::threadInit()
{
+ Q_ASSERT( QThread::currentThread() == thread() );
m_socket = new SessionSocket;
m_stream = new ImapStreamParser( m_socket );
connect( m_socket, SIGNAL(readyRead()),
this, SLOT(readMessage()), Qt::QueuedConnection );
+ // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect()
connect( m_socket, SIGNAL(disconnected()),
- m_session, SLOT(socketDisconnected()) );
+ this, SLOT(slotSocketDisconnected()), Qt::QueuedConnection );
connect( m_socket, SIGNAL(connected()),
- m_session, SLOT(socketConnected()) );
+ this, SIGNAL(socketConnected()) );
connect( m_socket, SIGNAL(error(KTcpSocket::Error)),
- m_session, SLOT(socketError()) );
+ this, SLOT(socketError(KTcpSocket::Error)) );
connect( m_socket, SIGNAL(bytesWritten(qint64)),
- m_session, SLOT(socketActivity()) );
+ this, SIGNAL(socketActivity()) );
if ( m_socket->metaObject()->indexOfSignal("encryptedBytesWritten(qint64)" ) > -1 ) {
connect( m_socket, SIGNAL(encryptedBytesWritten(qint64)), // needs kdelibs > 4.8
- m_session, SLOT(socketActivity()) );
+ this, SIGNAL(socketActivity()) );
}
connect( m_socket, SIGNAL(readyRead()),
- m_session, SLOT(socketActivity()) );
-
- connect( this, SIGNAL(responseReceived(KIMAP::Message)),
- m_session, SLOT(responseReceived(KIMAP::Message)) );
+ this, SIGNAL(socketActivity()) );
- QTimer::singleShot( 0, this, SLOT(reconnect()) );
- exec();
+ QMetaObject::invokeMethod(this, "reconnect", Qt::QueuedConnection);
+}
+// Called in secondary thread
+void SessionThread::threadQuit()
+{
+ Q_ASSERT( QThread::currentThread() == thread() );
delete m_stream;
+ m_stream = 0;
delete m_socket;
+ m_socket = 0;
+ thread()->quit();
}
-void SessionThread::startSsl(const KTcpSocket::SslVersion &version)
+// Called in primary thread
+void SessionThread::startSsl( KTcpSocket::SslVersion version )
{
- QMutexLocker locker(&m_mutex);
+ QMetaObject::invokeMethod( this, "doStartSsl", Q_ARG(KTcpSocket::SslVersion, version) );
+}
+
+// Called in secondary thread (via invokeMethod)
+void SessionThread::doStartSsl( KTcpSocket::SslVersion version )
+{
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( !m_socket )
+ return;
+ if ( version == KTcpSocket::AnySslVersion ) {
+ doSslFallback = true;
+ if ( m_socket->advertisedSslVersion() == KTcpSocket::UnknownSslVersion ) {
+ m_socket->setAdvertisedSslVersion( KTcpSocket::AnySslVersion );
+ } else if ( !( triedSslVersions & KTcpSocket::TlsV1 ) ) {
+ triedSslVersions |= KTcpSocket::TlsV1;
+ m_socket->setAdvertisedSslVersion( KTcpSocket::TlsV1 );
+ } else if ( !( triedSslVersions & KTcpSocket::SslV3 ) ) {
+ triedSslVersions |= KTcpSocket::SslV3;
+ m_socket->setAdvertisedSslVersion( KTcpSocket::SslV3 );
+ } else if ( !( triedSslVersions & KTcpSocket::SslV2 ) ) {
+ triedSslVersions |= KTcpSocket::SslV2;
+ m_socket->setAdvertisedSslVersion( KTcpSocket::SslV2 );
+ doSslFallback = false;
+ }
+ } else {
+ m_socket->setAdvertisedSslVersion(version);
+ }
- m_socket->setAdvertisedSslVersion(version);
m_socket->ignoreSslErrors();
connect(m_socket, SIGNAL(encrypted()), this, SLOT(sslConnected()));
m_socket->startClientEncryption();
}
+// Called in secondary thread
+void SessionThread::slotSocketDisconnected()
+{
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( doSslFallback ) {
+ reconnect();
+ } else {
+ emit socketDisconnected();
+ }
+}
+
+// Called in secondary thread
+void SessionThread::socketError(KTcpSocket::Error error)
+{
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( !m_socket )
+ return;
+ Q_UNUSED( error ); // can be used for debugging
+ if ( doSslFallback ) {
+ m_socket->disconnectFromHost();
+ } else {
+ emit socketError();
+ }
+}
+
+// Called in secondary thread
void SessionThread::sslConnected()
{
- QMutexLocker locker(&m_mutex);
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( !m_socket )
+ return;
KSslCipher cipher = m_socket->sessionCipher();
- if ( m_socket->sslErrors().count() > 0 || m_socket->encryptionMode() != KTcpSocket::SslClientMode
- || cipher.isNull() || cipher.usedBits() == 0) {
+ if ( m_socket->sslErrors().count() > 0 ||
+ m_socket->encryptionMode() != KTcpSocket::SslClientMode ||
+ cipher.isNull() || cipher.usedBits() == 0 ) {
qDebug() << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull()
<< ", cipher.usedBits() is" << cipher.usedBits()
<< ", the socket says:" << m_socket->errorString()
@@ -206,6 +288,7 @@ void SessionThread::sslConnected()
KSslErrorUiData errorData(m_socket);
emit sslError(errorData);
} else {
+ doSslFallback = false;
qDebug() << "TLS negotiation done.";
m_encryptedMode = true;
emit encryptionNegotiationResult(true, m_socket->negotiatedSslVersion());
@@ -214,7 +297,15 @@ void SessionThread::sslConnected()
void SessionThread::sslErrorHandlerResponse(bool response)
{
- QMutexLocker locker(&m_mutex);
+ QMetaObject::invokeMethod(this, "doSslErrorHandlerResponse", Q_ARG(bool, response));
+}
+
+// Called in secondary thread (via invokeMethod)
+void SessionThread::doSslErrorHandlerResponse(bool response)
+{
+ Q_ASSERT( QThread::currentThread() == thread() );
+ if ( !m_socket )
+ return;
if (response) {
m_encryptedMode = true;
emit encryptionNegotiationResult(true, m_socket->negotiatedSslVersion());
diff --git a/kimap/sessionthread_p.h b/kimap/sessionthread_p.h
index 672c418..47a94fb 100644
--- a/kimap/sessionthread_p.h
+++ b/kimap/sessionthread_p.h
@@ -22,7 +22,6 @@
#include <QtCore/QMutex>
#include <QtCore/QQueue>
-#include <QtCore/QThread>
#include <ktcpsocket.h>
@@ -32,52 +31,62 @@ namespace KIMAP {
class ImapStreamParser;
struct Message;
-class Session;
-class SessionThread : public QThread
+class SessionThread : public QObject
{
Q_OBJECT
public:
- explicit SessionThread( const QString &hostName, quint16 port, Session *parent );
+ explicit SessionThread( const QString &hostName, quint16 port );
~SessionThread();
inline QString hostName() { return m_hostName; }
inline quint16 port() { return m_port; }
void sendData( const QByteArray &payload );
- void run();
public slots:
void closeSocket();
- void reconnect();
- void startSsl(const KTcpSocket::SslVersion &version);
+ void startSsl(KTcpSocket::SslVersion version);
+ void sslErrorHandlerResponse(bool result);
signals:
+ void socketConnected();
+ void socketDisconnected();
+ void socketActivity();
+ void socketError();
void responseReceived(const KIMAP::Message &response);
void encryptionNegotiationResult(bool, KTcpSocket::SslVersion);
void sslError(const KSslErrorUiData&);
private slots:
+ void reconnect();
+ void threadInit();
+ void threadQuit();
void readMessage();
void writeDataQueue();
void sslConnected();
- void sslErrorHandlerResponse(bool result);
void doCloseSocket();
+ void socketError(KTcpSocket::Error);
+ void slotSocketDisconnected();
+ void doStartSsl(KTcpSocket::SslVersion);
+ void doSslErrorHandlerResponse(bool result);
private:
QString m_hostName;
quint16 m_port;
- Session *m_session;
SessionSocket *m_socket;
ImapStreamParser *m_stream;
QQueue<QByteArray> m_dataQueue;
+ // Protects m_dataQueue
QMutex m_mutex;
bool m_encryptedMode;
+ KTcpSocket::SslVersions triedSslVersions;
+ bool doSslFallback;
};
}