Warning, /pim/trojita/docs/masters/architecture.tex is written in an unsupported language. File is not indexed.

0001 % vim: spelllang=en spell textwidth=120
0002 \documentclass[trojita]{subfiles}
0003 
0004 \begin{document}
0005 
0006 \chapter{Trojitá's Architecture}
0007 
0008 This chapter provides a brief introduction to the architecture of Trojitá from a programmer's point of view.  Additional
0009 information is provided in the documentation shipped as a part of the source tree.
0010 
0011 \section{Overview of Components}
0012 
0013 Trojitá makes heavy use of certain idioms common in Qt programming and in object-oriented software development in
0014 general.
0015 
0016 At the highest layer lies the GUI, graphical code managing the actual user interaction.  This code contains no knowledge
0017 of IMAP or any other e-mail protocol; it is simply a graphical presentation layer specific to the desktop version of
0018 Trojitá.  In the releases intended for mobile platforms, the traditional {\tt QWidget}-based GUI is replaced by a
0019 variant built on top of Qt's QML \cite{qml}, a framework especially suited for touch-centric devices.
0020 
0021 Any interaction with IMAP is initiated through the model-view framework \cite{qt-mvc} and related utilities.  A core
0022 class encapsulating a representation of a single IMAP server, the {\tt Model} class, is accompanied by various proxy
0023 models and other helpers to segregate and transform the data into a better shape suitable for the upper layers.
0024 
0025 Any action which shall take effect is, however, not performed by any of the model-related classes.  Trojitá utilizes the
0026 concept of {\em tasks}, a set of single-purpose classes each serving a distinct role.  Examples of such tasks are
0027 ``obtain a connection'', ``synchronize a mailbox'', ``download a message'' or ``update message flags''.
0028 
0029 One layer below the Tasks, the Parser is located.  This class along with its support tools converts data between a byte
0030 stream arriving from or destined to the network and higher-level commands and responses which are utilized by the upper
0031 layers.  Actual network I/O operations are handled through a thin wrapper around Qt's own {\tt QIODevice} called {\tt
0032 Streams}.~\footnote{The {\tt QIODevice} is wrapped to allow for transparent compression using the deflate algorithm.
0033 Due to the historic reasons, the {\tt Stream} subclasses use the {\em has-a} instead of the {\em is-a} approach; this
0034 was required back when Trojitá shipped I/O implementations not based on the {\tt QIODevice} class for evaluation
0035 purposes.  It also helps to reduce the amount of functions each I/O implementation has to implement.}
0036 
0037 \subsection{Handling Input/Output}
0038 
0039 The raw byte streams provided by the network abstraction classes are intercepted by the {\tt Parser} class.  It takes
0040 any incoming octet sequence and with the help of a {\tt LowLevelParser} instantiates the concrete subclasses of the {\tt
0041 AbstractResponse} class.  Actual parsing of the responses is typically deferred to the corresponding {\tt Response}
0042 constructors which will tokenize the incoming data through {\tt LowLevelParser}'s methods and act on their contents
0043 according to the corresponding grammar rules.
0044 
0045 The response parsing had to be substantially relaxed from its original state due to observed interoperability issues.
0046 Even the most popular IMAP implementations struggled with following the ABNF-defined \cite{rfc5234} syntax to the
0047 letter; the most iconic example is Google's service which has prevented the IMAP clients talking to it from accessing
0048 forwarded messages for many years~\cite{gmail-bodystructure-sucks}.~\footnote{As of mid-2012, this issue remains
0049 unfixed despite my bug reports raised through both official and unofficial channels.}
0050 
0051 For some time, Trojitá's {\tt Parser} was implemented in a separate thread in an attempt to improve performance.
0052 However, profiling showed that the amount of time spent in parsing was negligible compared to the rest of the
0053 application with the only exception being the {\tt THREAD} response which essentially requires a complex transformation
0054 of nested lists.  As such, the whole of Trojitá is now a single threaded application.~\footnote{The WebKit engine used
0055 in HTML rendering creates threads for its individual purposes; the similar behavior is observed in the QML engine used
0056 in the mobile version.  These threads are not counted here, for they are considered to come from the ``system
0057 libraries''.}
0058 
0059 \subsection{The Concept of Tasks}
0060 
0061 The Tasks are designed to collaborate with each other, and there is a network of dependencies on how they can be used
0062 so that each task is responsible for only a subset of the overall work required to achieve a particular goal.  For
0063 example, when a message shall be marked as read, an {\tt UpdateFlagTask} is created.  Because ``marking message as
0064 read'' is implemented by manipulating IMAP message flags, an action which can only happen while a mailbox is selected,
0065 the {\tt UpdateFlagsTask} asks the {\tt Model} to return an instance of a {\tt KeepMailboxOpenTask}, a ``manager''
0066 object which is responsible for keeping the mailbox state synchronized between the server and the client.~\footnote{The
0067 {\tt KeepMailboxOpenTask} also serves as a dispatcher ensuring that the selected mailbox is switched only when any tasks
0068 which needed a particular mailbox open will have finished.} If the mailbox is already opened, an existing instance of
0069 the {\tt KeepMailboxOpenTask} is returned; if that is not the case, a new instance is returned.  The {\tt
0070 UpdateFlagsTask}'s execution is blocked and will commence only when (and if) the acquisition of a synchronized status
0071 succeeds.  Similarly, this ``maintaining'' task (the {\tt KeepMailboxOpenTask} itself deals just with incremental {\em
0072 updates} to the mailbox state and is activated only when the mailbox is opened. The actual mailbox synchronization is
0073 delegated to the {\tt ObtainSynchronizedMailboxTask}.  This synchronizer obtains the underlying connection handle
0074 through consultation with the {\tt Model} to decide whether a new connection shall be established or an existing one
0075 re-purposed.  This policy decision is completely contained in the {\tt Model} and is not visible to other layers (or the
0076 tasks) at all.
0077 
0078 Similar divisions of responsibility exist during other scenarios; the supporting infrastructure makes sure that no
0079 actions are started unless their prerequisites have succeeded.
0080 
0081 The whole hierarchy is presented to the user in various shapes; the most basic and rudimentary one is a ``busy
0082 indicator'' showing whether any activity is taking place.  A more detailed view showing an overview of all active tasks
0083 in a tree-like manner illustrating their dependencies is available (see the {\tt TaskPresentationModel} class in
0084 Trojitá's sources).
0085 
0086 The introduction of Tasks to Trojitá in 2010 proved to be an excellent decision allowing for much increased development
0087 pace and contributed to the much improved robustness and code clarity.  Thanks to a proper use of git's branches, the
0088 transition was undertaken over a longer time pried without disturbing the ongoing application development.
0089 
0090 The Tasks are instantiated by an auxiliary factory class in order to better facilitate automated unit testing with
0091 mock objects.
0092 
0093 \subsection{Routing Responses}
0094 
0095 The Tasks introduced in the previous section are also used for proper response processing.  At all times, the {\tt
0096 Model} can access a list of ``active tasks'' which are assigned to a particular IMAP connection.  When a response
0097 arrives, it is dispatched to these tasks in a well-defined order until either of the tasks declares the response as
0098 ``handled''.  If no task claims responsibility for a particular response, the {\tt Model} itself takes action.  This
0099 action might be either regular processing, or, in case the {\tt Model} cannot safely take care of said response, an
0100 error being raised and the connection sewered to prevent possible data loss due to suspected bug in the IMAP
0101 implementation.
0102 
0103 The responses themselves are communicated to the {\tt Model} (and, through it, to the responsible Tasks) through a queue
0104 of responses using Qt's own signal-and-slot mechanism.  The same way of passing data is used for additional
0105 ``meta information'' like informing about connection errors, parser exceptions or the low-level SSL/TLS certificate
0106 properties.  Having a unified queue of the incoming data made error handling much more elegant.~\footnote{Previously,
0107 matters were complicated by the way how QWidget-based UIs tend to deal with errors through dialog boxes which trigger
0108 nested event loops.}  Care has been taken to optimize this parsed data passing to minimize the amount of copying and ---
0109 in general --- Qt's {\tt QMetaObject} invocations while balancing the GUI responsiveness.  The IMAP handlers now process
0110 the incoming responses in batches, pausing every so often to allow the GUI to keep up with other incoming events to
0111 prevent an appearance of the whole application ``freezing'' temporarily.  The selected approach works well even on cell
0112 phones with limited resources.
0113 
0114 Any responses passing through the queue are also logged into an in-memory ring buffer.  Should an error occur, this
0115 buffer is used as a ``black box'' device which is shown to the user to give her a better context of what went wrong.
0116 This debug logging proved to be extremely valuable in debugging interoperability issues with non-compliant servers as
0117 well as when fixing bugs in Trojitá.
0118 
0119 \subsection{Models and Proxies}
0120 
0121 Historically, much of the IMAP logic in Trojitá has been concentrated in the {\tt Model} class.  Over the time, I've
0122 refactored that code to separate modules; however, even today, the {\tt Model} handles both data publication through the
0123 Qt's model-view framework as well as IMAP response dispatch to the relevant Tasks.  With little less than two thousands
0124 of physical lines of code, the {\tt Model} remains the second-largest source file in the code base (the biggest file is
0125 a unit tests related to various modes of mailbox synchronization).
0126 
0127 The {\tt Model} itself exports a view containing anything available from a particular IMAP server account through the
0128 Qt's model-view API.  The actual data is stored in a tree formed by various {\tt TreeItem} subclasses.
0129 
0130 As showing a single, rich-structured tree containing {\em everything} from mailboxes to individual message parts would
0131 not be particularly user-friendly, a number of so called proxy models were created.  These models usually operate on
0132 Qt's {\tt QModelIndex} level, but where profiling showed that a compelling speed increase would result from bypassing
0133 the {\tt QVariant} abstraction, direct access through the underlying (and Trojitá-specific) {\tt TreeItem} classes was
0134 used instead.  This has notably affected the design of the {\tt ThreadingMsgListModel} which is actually the biggest
0135 consumer of the CPU time when Trojitá opens a huge mailbox and activates message threading for the first time.
0136 
0137 Proxies exist for performing various transformations and filtering of the available data; some of them (like the {\tt
0138 MailboxModel} and {\tt MsgListModel}) are generic enough and used in all of the desktop client, the mobile application
0139 and the single-purpose batch synchronizer (see Appendix \ref{sec:xtuple}, p.~\pageref{sec:xtuple}) while others (like
0140 the {\tt PrettyMsgListModel}) are exclusive to the traditional desktop GUI.
0141 
0142 \subsection{Lazy Loading and Cache}
0143 
0144 The model-view separation strictly followed throughout Trojitá proved to be very useful when leveraging the full
0145 potential of many advanced IMAP features.  Because the individual message parts are accessible separately, Trojitá's
0146 native mode of operation supported the often-mentioned use case of ``only download a tiny text accompanying the big
0147 photo attachment and then ask user whether to fetch the photo'' without any effort.  At the same time, this separation
0148 made certain tasks which would typically be considered trivial a bit more demanding --- e.g. when forwarding a message,
0149 Trojitá has to explicitly retrieve two independent body parts, one for the headers and one for the message payload, and
0150 explicitly join them together.  On the other hand, in most of the situations this separation brought benefits visible to
0151 the end user which trumped the minor, uncommon complications.
0152 
0153 Any data once downloaded are kept in a persistent cache.  This feature allows Trojitá to work extremely well under
0154 unfavorable network conditions.  It also allows its users to access any data which were already known previously in an
0155 offline mode.
0156 
0157 Several caching backends are shipped in Trojitá; some of them store data exclusively in memory and therefore are not
0158 {\em persistent} per se, others use SQLite \cite{sqlite} for actual data storage.  Another mode which offloads ``big
0159 data'' storage to additional on-disk files is used by default.~\footnote{Work on the structured cache backends was
0160 sponsored by the KWest GbmH. (Appendix \ref{sec:kwest}, p.~\pageref{sec:kwest}).}
0161 
0162 \section{The Mobile Version}
0163 
0164 Trojitá also ships with a special version targeted for use on cell phones with touch screen such as Nokia's N9.  The
0165 release was tested on the Nokia N950, a developer device which I obtained through Nokia's Community Device Programme
0166 (Appendix \ref{sec:nokia-cdp}, p.~\pageref{sec:nokia-cdp}).
0167 
0168 The touch-optimized version of Trojitá shares most of the code with the desktop version; in particular, none of the
0169 underlying IMAP code had to be modified.  The changes were concentrated solely in the GUI layer which was completely
0170 rewritten using QML \cite{qml}, Qt's declarative language for fluid user interface creation.  A certain amount of C++
0171 code was also required, mainly for seamless integration of the underlying data providers with the JavaScript-based QML
0172 data presentation.
0173 
0174 I also had to extend some of the existing Qt classes with proper functions required for Trojitá.  One of them was
0175 replacing QML's native {\tt QDeclarativeWebView} component, an QML item encapsulating WebKit, the HTML renderer.  This
0176 replacement was required because the stock version from upstream Qt did not support specifying a {\tt
0177 QNetworkAccessManager} instance to use on a per-item basis~\cite{jkt-qdeclarativewebview}.  In QML, the whole QML scene
0178 shares a single instance of the {\tt QNetworkAccessManager} which is used for all sorts of data fetching.  In Trojitá,
0179 such a behavior is unacceptable for security reasons because the actual message data are transferred through said
0180 manager, and the manager therefore has to implement a proper security policy denying any requests for external
0181 elements.~\footnote{If the e-mail rendered was able to access external data, e.g. fetch images from the Internet at
0182 will, the resulting HTTP transfers would compromise the user's privacy and reveal confidential information like presence
0183 and schedule of a real person accessing a particular e-mail account.  Such tracing can be trivially implemented by
0184 including a unique identifier in the image URL, which is the reason why any decent MUA rejects such network requests by
0185 default.}  Another reason for this separation is to make sure that each {\tt QNetworkAccessManager} can only access data
0186 originating from a single e-mail message, an enforcement crucial to prevent phishing attempts and maintain the
0187 confidentiality of the user's messages.  There were also additional technical reasons for this extension, some of which
0188 were related to a documented requirement of QML's global instance adding provisions for future multithreaded access, a
0189 requirement which Trojitá's implementation explicitly does not guarantee for performance reasons.
0190 
0191 Apart from the mentioned issues, the porting proved to be very easy, to the extent when the most limiting factor were
0192 the author's lack of familiarity with QML and this technology's relative immaturity and lack of existing components when
0193 compared to the ``regular'' Qt applications.~\footnote{An example is the lack of a {\tt QSettings} equivalent in QML;
0194 users are suggested to use raw SQL access through {\tt openDatabaseSync} method which is indeed much more flexible, but
0195 also located several layers below the {\tt QSettings} on an index of the out-of-box usability --- clearly, writing
0196 custom SQL queries is more demanding than issuing a call to {\tt int i = QSettings().value("i").toInt()}.}  No design
0197 decisions of the application were hit as obstacles during the porting process.
0198 
0199 Trojitá's asynchronous mode of operation where any data transfers are performed in the background, without blocking the
0200 user interface, was paramount in achieving a decent performance and smooth UI updates.  The batched data processing
0201 mentioned in previous sections of this chapter is an example of a performance optimization which contributed to the
0202 excellent responsiveness of the cell phone version.
0203 
0204 The mobile version is still not as mature as the regular desktop release; most importantly, it does not support sending
0205 e-mails for now.  Proper platform integration should also be done in future.  For these reasons, the application was
0206 submitted to the Ovi store, Nokia's ``app store'' product, as a {\em technical preview}.  Even at its present state,
0207 however, the application is very useful for accessing user's e-mail on the go and for quick checking of whether
0208 something important has happened.  Trojitá's support for many IMAP extensions which reduce the overall bandwidth
0209 consumption was also tremendously useful --- many of them, like the {\tt CONDSTORE} and {\tt QRESYNC}, were designed
0210 with exactly this use case in mind and are also reasonably wide deployed among the publicly available IMAP server
0211 implementations.
0212 
0213 \section{Regression Testing}
0214 
0215 The IMAP protocol implementation developed as a part of Trojitá is covered by an extensive test suite verifying its
0216 behavior under varying conditions.  All extensions supported by Trojitá which relate to e-mail retrieval are covered by
0217 the test suite.
0218 
0219 Most of the testing works by replacing the connection to the IMAP server by a mock object capable of verifying that the
0220 transmitted data matches the developer's expectations and returning the sample responses back.  This approach is
0221 self-contained, does not require any other software to test and therefore increases the chances of regressions getting
0222 caught before they made their way to a production release.
0223 
0224 The following is an example on how an actual test might look like:
0225 
0226 \begin{minted}{c++}
0227 /** @short Test QRESYNC reporting changed flags */
0228 void ImapModelObtainSynchronizedMailboxTest::testQresyncChangedFlags()
0229 {
0230     // Trick the IMAP code into believing that the server supports QRESYNC
0231     // The test environment can do that without the burden of explicitly
0232     // faking the CAPABILITY response.
0233     FakeCapabilitiesInjector injector(model);
0234     injector.injectCapability("QRESYNC");
0235 
0236     // Simulate the previous state of the mailbox
0237     Imap::Mailbox::SyncState sync;
0238     sync.setExists(3);
0239     sync.setUidValidity(666);
0240     sync.setUidNext(15);
0241     sync.setHighestModSeq(33);
0242     QList<uint> uidMap;
0243     uidMap << 6 << 9 << 10;
0244 
0245     // Store the simulated state in the persistent cache
0246     model->cache()->setMailboxSyncState("a", sync);
0247     model->cache()->setUidMapping("a", uidMap);
0248     model->cache()->setMsgFlags("a", 6, QStringList() << "x");
0249     model->cache()->setMsgFlags("a", 9, QStringList() << "y");
0250     model->cache()->setMsgFlags("a", 10, QStringList() << "z");
0251 
0252     // At this point, we can proceed with actual testing
0253 
0254     // Initiate the activity
0255     model->resyncMailbox(idxA);
0256     // Check the I/O communication
0257     cClient(t.mk("SELECT a (QRESYNC (666 33 (2 9)))\r\n"));
0258     cServer("* 3 EXISTS\r\n"
0259             "* OK [UIDVALIDITY 666] .\r\n"
0260             "* OK [UIDNEXT 15] .\r\n"
0261             "* OK [HIGHESTMODSEQ 36] .\r\n"
0262             "* 2 FETCH (UID 9 FLAGS (x2 \\Seen))\r\n"
0263             );
0264     cServer(t.last("OK selected\r\n"));
0265     // Make sure that nothing else was transferred "over the network"
0266     cEmpty();
0267 
0268     // Verify that the persistent cache had been updated properly
0269     sync.setHighestModSeq(36);
0270     QCOMPARE(model->cache()->mailboxSyncState("a"), sync);
0271     QCOMPARE(static_cast<int>(model->cache()->mailboxSyncState("a").exists()),
0272              uidMap.size());
0273     QCOMPARE(model->cache()->uidMapping("a"), uidMap);
0274     QCOMPARE(model->cache()->msgFlags("a", 6),
0275              QStringList() << "x");
0276     QCOMPARE(model->cache()->msgFlags("a", 9),
0277              QStringList() << "\\Seen" << "x2");
0278     QCOMPARE(model->cache()->msgFlags("a", 10),
0279              QStringList() << "z");
0280 
0281     // Make sure that we've picked up the changed flag
0282     QCOMPARE(idxA.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 2);
0283 
0284     // Nothing else should be running
0285     justKeepTask();
0286 }
0287 \end{minted}
0288 
0289 Trojitá includes many of these individual test cases covering both regular operation as well as several pathological
0290 scenarios which would be tricky to hit in the real world.  In addition to that, the automated tests also verify speed of
0291 mailbox synchronization with folders containing hundreds of thousands of messages, a scenario which might be rare in
0292 practice~\footnote{The {\em Seznam.cz}, the most popular Czech free e-mail provider, imposes a limit of just 30,000
0293 messages per account as of early 2012.} but extremely useful for catching programmer mistakes like proposing an $O(n^2)$
0294 algorithm~\footnote{It was necessary to locate a message in the mailbox by its UID.  A conventional binary search
0295 algorithm was not applicable because the search could encounter ``message placeholders'' whose UID was not know, and
0296 therefore could not be compared to the reference value.  In the end, measurements have shown that starting with binary
0297 search and falling back on a linear scan later in the process upon encountering the first undefined value works fast
0298 enough in all tested circumstances.} over an $O(n \log n)$ one --- in spite of the fact that the overhead of the testing
0299 framework, especially when working with huge data transfers, as is indeed the case when synchronizing such a mailbox, is
0300 comparable to the actual code being profiled.
0301 
0302 \subsection{Scalability}
0303 
0304 I'm regularly testing Trojitá on mailboxes with hundreds of thousands of messages.  Trojitá's unique design which
0305 guarantees that only the {\em required} data is transferred is paramount in achieving (and retaining) reasonable
0306 performance under varying workloads.
0307 
0308 Trojitá respects all relevant recommendations about incremental retrieval of data.  Unless in the so called ``expensive
0309 mode'' where Trojitá tries to save bandwidth at the cost of increased waiting time, an intelligent preload is
0310 implemented which will seamlessly ask for data which is likely to be required in future in a background preload
0311 operation.
0312 
0313 \subsection{Command Pipelining}
0314 
0315 Whenever possible, Trojitá issues the IMAP commands in parallel.~\footnote{The parallelization imposes a configurable
0316 limit on the number of concurrently active tasks so as not to overload the server.}  Unfortunately, many expensive
0317 operations invoked on the server side often requires mailbox-wide locks, at least in the current server's
0318 implementations.  An example of such behavior are Dovecot's adaptive indexes \cite{dovecot-adaptive-indexes} which are
0319 created on-demand, in response to actual client's requests.  When Trojitá asks for threading on a big mailbox for the
0320 first time, Dovecot will block handling the threading request and building the required index, not responding to a query
0321 for metadata of messages, even though Trojitá has already issued a command requesting their delivery.
0322 
0323 Under normal circumstances, though, pipelining is crucial for decent performance in presence of excess network round
0324 trip times, and this parallel delivery of commands works extremely well even in case where the servers are unable to
0325 fulfill the requests in parallel.
0326 
0327 \subsection{Low-level optimization}
0328 
0329 Performance measurements were undertaken using Valgrind's \cite{valgrind} {\tt callgrind} \cite{valgrind-callgrind} tool
0330 while memory usage was tracked using Valgrind's {\tt massif} \cite{valgrind-massif}.  It is hard to compare existing
0331 clients based on the ``consumed memory'' alone, especially when considering that certain desktop clients like KDE's
0332 Akonadi-based \cite{akonadi} products are split into several processes, many of which are handling both IMAP-related and
0333 unrelated operations.  The memory profiling was therefore concentrated on making Trojitá's memory usage ``reasonable''
0334 and for checking of obvious regressions.
0335 
0336 Some of the results I have observed when profiling the code were initially rather surprising --- for example, replacing
0337 a {\tt QStringList} implementation by a {\tt QSet<QString>} one actually {\em increased} the memory usage from 296~MB in
0338 a synthetic benchmark~\footnote{The test used was {\tt ImapModelObtainSynchronizedMailboxTest::testFlagReSyncBenchmark}
0339 as of commit {\tt cf92f5f9f8e929e1427877c0db470d8c59651d3b} (March 2012) with the number of messages in mailbox being
0340 increased to half a million.  Measurements were performed on an {\tt x86\_64} Gentoo Linux machine running Qt~4.7.1.} to
0341 385~MB, i.e. by roughly one third --- a result caused by the {\tt QSet}'s hash table memory overhead.
0342 
0343 In the end, the total use in the mentioned benchmark was reduced to just 186~MB by explicit use of {\tt QString}'s
0344 implicit sharing where the actual text data are saved in memory only once, using {\tt QString}'s native reference
0345 counting.  The results could be likely improved by moving the data sharing one level up to the level of the whole {\tt
0346 QStringList} instead of the underlying elements.
0347 
0348 More drastic reductions could be obtained by actively removing unused data from memory and reducing the amount of empty
0349 placeholders being initialized to ``null'' values in the {\tt TreeItemMessage} instances, or, potentially, deferring
0350 their instantiations indefinitely till the corresponding messages come to the widget's viewport.  However, no compelling
0351 reason to do so on the target platforms was observed so far.
0352 
0353 \end{document}