connection.cpp 17.6 KB
Newer Older
Till Adam's avatar
Till Adam committed
1
2
/***************************************************************************
 *   Copyright (C) 2006 by Till Adam <adam@kde.org>                        *
3
 *   Copyright (C) 2013 by Volker Krause <vkrause@kde.org>                 *
Till Adam's avatar
Till Adam committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
18
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
Till Adam's avatar
Till Adam committed
19
 ***************************************************************************/
20
#include "connection.h"
Laurent Montel's avatar
Laurent Montel committed
21
#include "akonadiserver_debug.h"
22

Laurent Montel's avatar
Laurent Montel committed
23

24
#include <QSettings>
25
#include <QEventLoop>
26
#include <QThreadStorage>
Till Adam's avatar
Till Adam committed
27
28

#include "storage/datastore.h"
29
#include "storage/dbdeadlockcatcher.h"
Till Adam's avatar
Till Adam committed
30
#include "handler.h"
31
#include "notificationmanager.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
32

Till Adam's avatar
Till Adam committed
33
#include "tracer.h"
34

35
#include <cassert>
Hannah von Reth's avatar
Hannah von Reth committed
36
37

#ifndef Q_OS_WIN
38
#include <cxxabi.h>
Hannah von Reth's avatar
Hannah von Reth committed
39
#endif
Till Adam's avatar
Till Adam committed
40

41

42
#include <private/protocol_p.h>
43
#include <private/datastream_p_p.h>
44
#include <private/standarddirs_p.h>
45
46

using namespace Akonadi;
47
using namespace Akonadi::Server;
Till Adam's avatar
Till Adam committed
48

49
50
#define IDLE_TIMER_TIMEOUT 180000 // 3 min

51
static QString connectionIdentifier(Connection *c) {
Laurent Montel's avatar
Laurent Montel committed
52
    const QString id = QString::asprintf("%p", static_cast<void *>(c));
53
54
55
    return id;
}

56
57
58
Connection::Connection(AkonadiServer &akonadi)
    : AkThread(connectionIdentifier(this), QThread::InheritPriority)
    , m_akonadi(akonadi)
Till Adam's avatar
Till Adam committed
59
{
60
61
}

62
63
64
Connection::Connection(quintptr socketDescriptor, AkonadiServer &akonadi)
    : AkThread(connectionIdentifier(this), QThread::InheritPriority)
    , m_akonadi(akonadi)
65
66
{
    m_socketDescriptor = socketDescriptor;
67
    m_identifier = connectionIdentifier(this); // same as objectName()
68

69
    const QSettings settings(Akonadi::StandardDirs::serverConfigFile(), QSettings::IniFormat);
70
    m_verifyCacheOnRetrieval = settings.value(QStringLiteral("Cache/VerifyOnRetrieval"), m_verifyCacheOnRetrieval).toBool();
71
72
73
74
75
}

void Connection::init()
{
    AkThread::init();
Till Adam's avatar
Till Adam committed
76

Daniel Vrátil's avatar
Daniel Vrátil committed
77
    auto socket = std::make_unique<QLocalSocket>();
78
    if (!socket->setSocketDescriptor(m_socketDescriptor)) {
Laurent Montel's avatar
Laurent Montel committed
79
        qCWarning(AKONADISERVER_LOG) << "Connection(" << m_identifier
Laurent Montel's avatar
Laurent Montel committed
80
                                     << ")::run: failed to set socket descriptor: "
Laurent Montel's avatar
Laurent Montel committed
81
82
                                     << socket->error()
                                     << "(" << socket->errorString() << ")";
Till Adam's avatar
Till Adam committed
83
84
85
        return;
    }

Daniel Vrátil's avatar
Daniel Vrátil committed
86
87
    m_socket = std::move(socket);
    connect(m_socket.get(), &QLocalSocket::disconnected, this, &Connection::slotSocketDisconnected);
88

Daniel Vrátil's avatar
Daniel Vrátil committed
89
90
    m_idleTimer = std::make_unique<QTimer>();
    connect(m_idleTimer.get(), &QTimer::timeout, this, &Connection::slotConnectionIdle);
Till Adam's avatar
Till Adam committed
91

92
    storageBackend()->notificationCollector()->setConnection(this);
93

Daniel Vrátil's avatar
Daniel Vrátil committed
94
    if (m_socket->state() == QLocalSocket::ConnectedState) {
95
96
        QTimer::singleShot(0, this, &Connection::handleIncomingData);
    } else {
Daniel Vrátil's avatar
Daniel Vrátil committed
97
        connect(m_socket.get(), &QLocalSocket::connected, this, &Connection::handleIncomingData,
98
99
100
101
102
103
                Qt::QueuedConnection);
    }

    try {
        slotSendHello();
    } catch (const ProtocolException &e) {
104
        qCWarning(AKONADISERVER_LOG) << "Protocol Exception sending \"hello\" on connection" << m_identifier << ":" << e.what();
105
106
        m_socket->disconnectFromServer();
    }
107
108
109
110
}

void Connection::quit()
{
111
112
113
114
115
116
    if (QThread::currentThread()->loopLevel() > 1) {
        m_connectionClosing = true;
        Q_EMIT connectionClosing();
        return;
    }

117
    m_akonadi.tracer().endConnection(m_identifier, QString());
118

Daniel Vrátil's avatar
Daniel Vrátil committed
119
120
    m_socket.reset();
    m_idleTimer.reset();
121

122
    AkThread::quit();
123
}
124

125
126
void Connection::slotSendHello()
{
David Faure's avatar
David Faure committed
127
    SchemaVersion version = SchemaVersion::retrieveAll().at(0);
128

129
130
131
132
133
134
    Protocol::HelloResponse hello;
    hello.setServerName(QStringLiteral("Akonadi"));
    hello.setMessage(QStringLiteral("Not Really IMAP server"));
    hello.setProtocolVersion(Protocol::version());
    hello.setGeneration(version.generation());
    sendResponse(0, std::move(hello));
135
136
}

137
138
DataStore *Connection::storageBackend()
{
139
140
    if (!m_backend) {
        m_backend = DataStore::self();
141
142
143
    }
    return m_backend;
}
144

145
146
Connection::~Connection()
{
147
    quitThread();
148
149
150
151

    if (m_reportTime) {
        reportTime();
    }
152
153
154
155
}

void Connection::slotConnectionIdle()
{
Laurent Montel's avatar
Laurent Montel committed
156
    Q_ASSERT(m_currentHandler == nullptr);
Laurent Montel's avatar
Laurent Montel committed
157
    if (m_backend && m_backend->isOpened()) {
158
159
160
161
162
        if (m_backend->inTransaction()) {
            // This is a programming error, the timer should not have fired.
            // But it is safer to abort and leave the connection open, until
            // a later operation causes the idle timer to fire (than crash
            // the akonadi server).
163
            qCInfo(AKONADISERVER_LOG) << m_sessionId << "NOT Closing idle db connection; we are in transaction";
164
165
166
167
            return;
        }
        m_backend->close();
    }
Till Adam's avatar
Till Adam committed
168
169
}

170
171
172
173
174
175
176
177
178
179
180
void Connection::slotSocketDisconnected()
{
    // If we have active handler, wait for it to finish, then we emit the signal
    // from slotNewDate()
    if (m_currentHandler) {
        return;
    }

    Q_EMIT disconnected();
}

181
182
183
184
185
186
187
188
189
190
191
192
193
void Connection::parseStream(const Protocol::CommandPtr &cmd)
{
    if (!m_currentHandler->parseStream()) {
        try {
            m_currentHandler->failureResponse("Error while handling a command");
        } catch (...) {
            m_connectionClosing = true;
        }
        qCWarning(AKONADISERVER_LOG) << "Error while handling command" << cmd->type()
                                     << "on connection" << m_identifier;
    }
}

194
void Connection::handleIncomingData()
Till Adam's avatar
Till Adam committed
195
{
196
    Q_FOREVER {
197

198
        if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) {
199
200
            break;
        }
201

202
203
204
205
        // Blocks with event loop until some data arrive, allows us to still use QTimers
        // and similar while waiting for some data to arrive
        if (m_socket->bytesAvailable() < int(sizeof(qint64))) {
            QEventLoop loop;
Daniel Vrátil's avatar
Daniel Vrátil committed
206
207
            connect(m_socket.get(), &QLocalSocket::readyRead, &loop, &QEventLoop::quit);
            connect(m_socket.get(), &QLocalSocket::stateChanged, &loop, &QEventLoop::quit);
208
209
210
            connect(this, &Connection::connectionClosing, &loop, &QEventLoop::quit);
            loop.exec();
        }
211

212
        if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) {
213
214
            break;
        }
215

216
        m_idleTimer->stop();
217

218
219
220
221
        // will only open() a previously idle backend.
        // Otherwise, a new backend could lazily be constructed by later calls.
        if (!storageBackend()->isOpened()) {
            m_backend->open();
222
223
        }

224
225
        QString currentCommand;
        while (m_socket->bytesAvailable() >= int(sizeof(qint64))) {
Daniel Vrátil's avatar
Daniel Vrátil committed
226
            Protocol::DataStream stream(m_socket.get());
227
228
229
230
231
232
            qint64 tag = -1;
            stream >> tag;
            // TODO: Check tag is incremental sequence

            Protocol::CommandPtr cmd;
            try {
Daniel Vrátil's avatar
Daniel Vrátil committed
233
                cmd = Protocol::deserialize(m_socket.get());
234
            } catch (const Akonadi::ProtocolException &e) {
235
236
                qCWarning(AKONADISERVER_LOG) << "ProtocolException while deserializing incoming data on connection"
                                             << m_identifier << ":" <<  e.what();
Daniel Vrátil's avatar
Daniel Vrátil committed
237
                setState(Server::LoggingOut);
238
239
                return;
            } catch (const std::exception &e) {
240
241
                qCWarning(AKONADISERVER_LOG) << "Unknown exception while deserializing incoming data on connection"
                                             << m_identifier << ":" << e.what();
Daniel Vrátil's avatar
Daniel Vrátil committed
242
                setState(Server::LoggingOut);
243
244
245
                return;
            }
            if (cmd->type() == Protocol::Command::Invalid) {
246
247
                qCWarning(AKONADISERVER_LOG) << "Received an invalid command on connection" << m_identifier
                                             << ": resetting connection";
Daniel Vrátil's avatar
Daniel Vrátil committed
248
                setState(Server::LoggingOut);
249
250
                return;
            }
251

252
            // Tag context and collection context is not persistent.
253
254
            m_context.setTag(nullopt);
            m_context.setCollection({});
255
256
            if (m_akonadi.tracer().currentTracer() != QLatin1String("null")) {
                m_akonadi.tracer().connectionInput(m_identifier, tag, cmd);
257
            }
258

Daniel Vrátil's avatar
Daniel Vrátil committed
259
            m_currentHandler = findHandlerForCommand(cmd->type());
260
            if (!m_currentHandler) {
261
262
                qCWarning(AKONADISERVER_LOG) << "Invalid command: no such handler for" << cmd->type()
                                             << "on connection" << m_identifier;
Daniel Vrátil's avatar
Daniel Vrátil committed
263
                setState(Server::LoggingOut);
264
                return;
265
            }
266
267
            if (m_reportTime) {
                startTime();
268
            }
269
270
271
272
273

            m_currentHandler->setConnection(this);
            m_currentHandler->setTag(tag);
            m_currentHandler->setCommand(cmd);
            try {
274
                DbDeadlockCatcher catcher([this, &cmd]() { parseStream(cmd); });
275
276
277
278
279
280
281
            } catch (const Akonadi::Server::HandlerException &e) {
                if (m_currentHandler) {
                    try {
                        m_currentHandler->failureResponse(e.what());
                    } catch (...) {
                        m_connectionClosing = true;
                    }
282
283
                    qCWarning(AKONADISERVER_LOG) << "Handler exception when handling command" << cmd->type()
                                                 << "on connection" << m_identifier << ":" << e.what();
284
285
286
287
288
289
290
291
                }
            } catch (const Akonadi::Server::Exception &e) {
                if (m_currentHandler) {
                    try {
                        m_currentHandler->failureResponse(QString::fromUtf8(e.type()) + QLatin1String(": ") + QString::fromUtf8(e.what()));
                    } catch (...) {
                        m_connectionClosing = true;
                    }
292
293
                    qCWarning(AKONADISERVER_LOG) << "General exception when handling command" << cmd->type()
                                                 << "on connection" << m_identifier << ":" << e.what();
294
295
296
297
                }
            } catch (const Akonadi::ProtocolException &e) {
                // No point trying to send anything back to client, the connection is
                // already messed up
298
299
                qCWarning(AKONADISERVER_LOG) << "Protocol exception when handling command" << cmd->type()
                                             << "on connection" << m_identifier << ":" << e.what();
300
                m_connectionClosing = true;
301
#if defined(Q_OS_LINUX) && !defined(_LIBCPP_VERSION)
302
303
304
305
306
307
308
309
310
            } catch (abi::__forced_unwind&) {
                // HACK: NPTL throws __forced_unwind during thread cancellation and
                // we *must* rethrow it otherwise the program aborts. Due to the issue
                // described in #376385 we might end up destroying (cancelling) the
                // thread from a nested loop executed inside parseStream() above,
                // so the exception raised in there gets caught by this try..catch
                // statement and it must be rethrown at all cost. Remove this hack
                // once the root problem is fixed.
                throw;
311
#endif
312
            } catch (...) {
313
314
                qCCritical(AKONADISERVER_LOG) << "Unknown exception while handling command" << cmd->type()
                                              << "on connection" << m_identifier;
315
316
317
318
319
320
                if (m_currentHandler) {
                    try {
                        m_currentHandler->failureResponse("Unknown exception caught");
                    } catch (...) {
                        m_connectionClosing = true;
                    }
321
                }
322
            }
323
324
325
            if (m_reportTime) {
                stopTime(currentCommand);
            }
Daniel Vrátil's avatar
Daniel Vrátil committed
326
            m_currentHandler.reset();
327

328
            if (!m_socket || m_socket->state() != QLocalSocket::ConnectedState) {
329
330
331
332
333
334
335
                Q_EMIT disconnected();
                return;
            }

            if (m_connectionClosing) {
                break;
            }
336
        }
337

338
339
340
        // reset, arm the timer
        m_idleTimer->start(IDLE_TIMER_TIMEOUT);

341
        if (m_connectionClosing) {
342
            break;
343
        }
Guy Maurel's avatar
Guy Maurel committed
344
    }
345

346
347
348
349
350
    if (m_connectionClosing) {
        m_socket->disconnect(this);
        m_socket->close();
        QTimer::singleShot(0, this, &Connection::quit);
    }
Till Adam's avatar
Till Adam committed
351
352
}

353
const CommandContext &Connection::context() const
354
{
355
356
357
358
359
360
    return m_context;
}

void Connection::setContext(const CommandContext &context)
{
    m_context = context;
361
362
}

Daniel Vrátil's avatar
Daniel Vrátil committed
363
std::unique_ptr<Handler> Connection::findHandlerForCommand(Protocol::Command::Type command)
Till Adam's avatar
Till Adam committed
364
{
365
    auto handler = Handler::findHandlerForCommandAlwaysAllowed(command, m_akonadi);
366
367
    if (handler) {
        return handler;
368
    }
Till Adam's avatar
Till Adam committed
369

370
    switch (m_connectionState) {
371
    case NonAuthenticated:
372
        handler =  Handler::findHandlerForCommandNonAuthenticated(command, m_akonadi);
373
374
        break;
    case Authenticated:
375
        handler =  Handler::findHandlerForCommandAuthenticated(command, m_akonadi);
376
377
378
        break;
    case LoggingOut:
        break;
Till Adam's avatar
Till Adam committed
379
380
    }

381
    return handler;
Till Adam's avatar
Till Adam committed
382
383
}

384
385
386
387
388
qint64 Connection::currentTag() const
{
    return m_currentHandler->tag();
}

Daniel Vrátil's avatar
Daniel Vrátil committed
389
void Connection::setState(ConnectionState state)
Till Adam's avatar
Till Adam committed
390
{
391
    if (state == m_connectionState) {
392
393
        return;
    }
Till Adam's avatar
Till Adam committed
394
    m_connectionState = state;
395
    switch (m_connectionState) {
396
    case NonAuthenticated:
397
        assert(0);   // can't happen, it's only the initial state, we can't go back to it
398
399
400
401
        break;
    case Authenticated:
        break;
    case LoggingOut:
402
        m_socket->disconnectFromServer();
403
        break;
Till Adam's avatar
Till Adam committed
404
405
406
    }
}

407
void Connection::setSessionId(const QByteArray &id)
Volker Krause's avatar
Volker Krause committed
408
{
Laurent Montel's avatar
Laurent Montel committed
409
    m_identifier = QString::asprintf("%s (%p)", id.data(), static_cast<void *>(this));
410
    m_akonadi.tracer().beginConnection(m_identifier, QString());
411
    //m_streamParser->setTracerIdentifier(m_identifier);
412
413
414

    m_sessionId = id;
    setObjectName(QString::fromLatin1(id));
415
416
    // this races with the use of objectName() in QThreadPrivate::start
    //thread()->setObjectName(objectName() + QStringLiteral("-Thread"));
417
    storageBackend()->setSessionId(id);
Volker Krause's avatar
Volker Krause committed
418
419
}

420
QByteArray Connection::sessionId() const
Volker Krause's avatar
Volker Krause committed
421
{
422
    return m_sessionId;
Volker Krause's avatar
Volker Krause committed
423
424
}

425
bool Connection::isOwnerResource(const PimItem &item) const
426
{
427
    if (context().resource().isValid() && item.collection().resourceId() == context().resource().id()) {
428
429
430
431
432
433
434
        return true;
    }
    // fallback for older resources
    if (sessionId() == item.collection().resource().name().toUtf8()) {
        return true;
    }
    return false;
435
}
436

437
bool Connection::isOwnerResource(const Collection &collection) const
438
{
439
    if (context().resource().isValid() && collection.resourceId() == context().resource().id()) {
440
441
442
443
444
445
        return true;
    }
    if (sessionId() == collection.resource().name().toUtf8()) {
        return true;
    }
    return false;
446
447
}

448
bool Connection::verifyCacheOnRetrieval() const
449
{
450
    return m_verifyCacheOnRetrieval;
451
}
452
453
454
455
456
457
458
459
460
461
462
463

void Connection::startTime()
{
    m_time.start();
}

void Connection::stopTime(const QString &identifier)
{
    int elapsed = m_time.elapsed();
    m_totalTime += elapsed;
    m_totalTimeByHandler[identifier] += elapsed;
    m_executionsByHandler[identifier]++;
Laurent Montel's avatar
Laurent Montel committed
464
    qCDebug(AKONADISERVER_LOG) << identifier << " time : " << elapsed << " total: " << m_totalTime;
465
466
467
468
}

void Connection::reportTime() const
{
Laurent Montel's avatar
Laurent Montel committed
469
470
    qCDebug(AKONADISERVER_LOG) << "===== Time report for " << m_identifier << " =====";
    qCDebug(AKONADISERVER_LOG) << " total: " << m_totalTime;
471
472
    for (auto it = m_totalTimeByHandler.cbegin(), end = m_totalTimeByHandler.cend(); it != end; ++it) {
        const QString &handler = it.key();
Laurent Montel's avatar
Laurent Montel committed
473
        qCDebug(AKONADISERVER_LOG) << "handler : " << handler << " time: " << m_totalTimeByHandler.value(handler) << " executions " << m_executionsByHandler.value(handler) << " avg: " << m_totalTimeByHandler.value(handler) / m_executionsByHandler.value(handler);
474
    }
475
476
}

477
void Connection::sendResponse(qint64 tag, const Protocol::CommandPtr &response)
478
{
479
480
    if (m_akonadi.tracer().currentTracer() != QLatin1String("null")) {
        m_akonadi.tracer().connectionOutput(m_identifier, tag, response);
Daniel Vrátil's avatar
Daniel Vrátil committed
481
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
482
    Protocol::DataStream stream(m_socket.get());
483
    stream << tag;
Daniel Vrátil's avatar
Daniel Vrátil committed
484
    Protocol::serialize(m_socket.get(), response);
485
486
487
488
489
490
491
492
    if (!m_socket->waitForBytesWritten()) {
        if (m_socket->state() == QLocalSocket::ConnectedState) {
            throw ProtocolException("Server write timeout");
        } else {
            // The client has disconnected before we managed to send our response,
            // which is not an error
        }
    }
493
494
}

495

496
Protocol::CommandPtr Connection::readCommand()
497
{
498
    while (m_socket->bytesAvailable() < (int) sizeof(qint64)) {
Daniel Vrátil's avatar
Daniel Vrátil committed
499
        Protocol::DataStream::waitForData(m_socket.get(), 10000); // 10 seconds, just in case client is busy
500
    }
501

Daniel Vrátil's avatar
Daniel Vrátil committed
502
    Protocol::DataStream stream(m_socket.get());
503
504
    qint64 tag;
    stream >> tag;
505

506
    // TODO: compare tag with m_currentHandler->tag() ?
Daniel Vrátil's avatar
Daniel Vrátil committed
507
    return Protocol::deserialize(m_socket.get());
508
}