Merge lp:~marcustomlinson/unity-scopes-api/lp-1267917-2 into lp:unity-scopes-api

Proposed by Marcus Tomlinson
Status: Merged
Approved by: Michal Hruby
Approved revision: 138
Merged at revision: 144
Proposed branch: lp:~marcustomlinson/unity-scopes-api/lp-1267917-2
Merge into: lp:unity-scopes-api
Diff against target: 208 lines (+83/-57)
3 files modified
include/unity/scopes/internal/smartscopes/HttpClientQtThread.h (+8/-4)
src/scopes/internal/smartscopes/HttpClientQt.cpp (+8/-22)
src/scopes/internal/smartscopes/HttpClientQtThread.cpp (+67/-31)
To merge this branch: bzr merge lp:~marcustomlinson/unity-scopes-api/lp-1267917-2
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michal Hruby (community) Approve
Review via email: mp+202428@code.launchpad.net

Commit message

Ensure that manager_ and reply_ objects are created and destroyed from within the same Qt thread.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

11 + moveToThread(this);

Shouldn't be necessary.

20 + reply_->deleteLater();

The reply_ object is created inside the thread, so it should be destroyed inside the thread, isn't that what the original error was about?

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

Hmm, looks a bit odd, are we really creating a thread solely to serve one request?

Nonetheless, it does fix the issue, I'm just not convinced it's a good idea to rely on emitting a signal to quit the thread, as this is inverting the responsibility and the thread is no longer a worker, but a "I'll quit when you ask me to" thing.

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

This is a bit of a tricky situation. Consider the requirements:

1. HttpClientQt.h should not contain Qt code that requires any more than forward declarations (this is to avoid anything outside of this class having to link to Qt).

2. A call to HttpClientQt::get needs to return a handle to the future result and asynchronously execute the request.

3. QSocketNotifier (in QNetworkAccessManager) can only be used with threads started with QThread.

4. The manager_ and reply_ Qt members (regardless of who their parent class is) need to be created and destroyed in the same QThread.

In order to satisfy req.1, we include the HttpClientThread class in the HttpClientQt cpp file. This class can then contain all the Qt specific stuff like signals/slots.

req.2 is performed by spawning a C++11 worker thread on request which eventually assigns a promise with the result. This thread then uses an HttpClientQtThread object to execute the query (yes, HttpClientQtThread is not really used as a worker thread as you pointed out)

Considering we already have a C++11 worker thread per request, does the helper Qt class really need to be a QThread? Well yes. I tried this class as just a QObject with synchronous methods but ended up with errors on new-ing QNetworkAccessManager (hence req.3).

Naturally then, as we now have a QThread available, we can use this thread to create, use and destroy the manager_ and reply_ HttpClientQtThread members (req.4). Lastly, because we need to access the reply_ object after it is assigned but before it is destroyed, we need to suspend the QThread until HttpClientQtThread signals it to exit (on destruction).

I hope this makes some sense as to why the code is structured this way (By no means am I saying that this is the only possible solution).

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

> Naturally then, as we now have a QThread available, we can use this thread to
> create, use and destroy the manager_ and reply_ HttpClientQtThread members
> (req.4). Lastly, because we need to access the reply_ object after it is
> assigned but before it is destroyed, we need to suspend the QThread until
> HttpClientQtThread signals it to exit (on destruction).

I wouldn't agree here, all the worker is asked to do is to return whether the http request succeeded plus a memory buffer with the reply, there's absolutely no need to keep the reply_ object alive as long as these two properties are stored somewhere. And then the inversion goes away.

I still disagree with using a thread per-request, but that's outside of scope of this MP.

Revision history for this message
Michal Hruby (mhr3) wrote :

Why is the query_done still there? Why not just quit() the thread?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

+1, let's get it in!

review: Approve
138. By Marcus Tomlinson

Create a direct connection between manager.finished() and got_reply() in order to avoid reply being freed before use.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'include/unity/scopes/internal/smartscopes/HttpClientQtThread.h'
--- include/unity/scopes/internal/smartscopes/HttpClientQtThread.h 2014-01-13 07:18:08 +0000
+++ include/unity/scopes/internal/smartscopes/HttpClientQtThread.h 2014-01-22 11:48:09 +0000
@@ -38,12 +38,15 @@
38 HttpClientQtThread(const QUrl& url, uint timeout);38 HttpClientQtThread(const QUrl& url, uint timeout);
39 ~HttpClientQtThread();39 ~HttpClientQtThread();
4040
41 bool get_reply(std::string& reply);
42
43private:
41 void run();44 void run();
42 QNetworkReply* getReply();
4345
44public Q_SLOTS:46public Q_SLOTS:
45 void cancel();47 void cancel();
46 void queryDone(QNetworkReply*);48 void timeout();
49 void got_reply(QNetworkReply* reply);
4750
48Q_SIGNALS:51Q_SIGNALS:
49 void abort();52 void abort();
@@ -51,9 +54,10 @@
51private:54private:
52 QUrl url_;55 QUrl url_;
53 uint timeout_;56 uint timeout_;
54 QNetworkReply* reply_;
55 QNetworkAccessManager* manager_;
56 std::mutex reply_mutex_;57 std::mutex reply_mutex_;
58
59 bool success_;
60 std::string reply_;
57};61};
5862
59#endif // UNITY_SCOPES_INTERNAL_SMARTSCOPES_HTTPCLIENTQTTHREAD_H63#endif // UNITY_SCOPES_INTERNAL_SMARTSCOPES_HTTPCLIENTQTTHREAD_H
6064
=== modified file 'src/scopes/internal/smartscopes/HttpClientQt.cpp'
--- src/scopes/internal/smartscopes/HttpClientQt.cpp 2014-01-13 12:25:13 +0000
+++ src/scopes/internal/smartscopes/HttpClientQt.cpp 2014-01-22 11:48:09 +0000
@@ -104,32 +104,18 @@
104104
105 get_qt_thread_->start();105 get_qt_thread_->start();
106 loop.exec();106 loop.exec();
107 get_qt_thread_->wait();107
108108 std::string reply;
109 QNetworkReply* reply = get_qt_thread_->getReply();109 bool success = get_qt_thread_->get_reply(reply);
110110
111 if (!reply)111 if (!success)
112 {112 {
113 // no reply113 unity::ResourceException e(reply);
114 unity::ResourceException e("No reply from " + request_url + ":" + std::to_string(port));
115 promise_->set_exception(e.self());
116 }
117 else if (!reply->isFinished())
118 {
119 // incomplete reply
120 unity::ResourceException e("Incomplete reply from " + request_url + ":" + std::to_string(port));
121 promise_->set_exception(e.self());
122 }
123 else if (reply->error() != QNetworkReply::NoError)
124 {
125 // communication error
126 unity::ResourceException e(reply->errorString().toStdString());
127 promise_->set_exception(e.self());114 promise_->set_exception(e.self());
128 }115 }
129 else116 else
130 {117 {
131 QString reply_string(reply->readAll());118 promise_->set_value(reply);
132 promise_->set_value(reply_string.toStdString());
133 }119 }
134 });120 });
135121
136122
=== modified file 'src/scopes/internal/smartscopes/HttpClientQtThread.cpp'
--- src/scopes/internal/smartscopes/HttpClientQtThread.cpp 2014-01-13 12:25:13 +0000
+++ src/scopes/internal/smartscopes/HttpClientQtThread.cpp 2014-01-22 11:48:09 +0000
@@ -27,8 +27,7 @@
27 : QThread(),27 : QThread(),
28 url_(url),28 url_(url),
29 timeout_(timeout),29 timeout_(timeout),
30 reply_(nullptr),30 success_(false)
31 manager_(nullptr)
32{31{
33}32}
3433
@@ -36,48 +35,85 @@
36{35{
37 cancel();36 cancel();
3837
39 delete reply_;38 wait();
40 delete manager_;39}
40
41bool HttpClientQtThread::get_reply(std::string& reply)
42{
43 std::lock_guard<std::mutex> lock(reply_mutex_);
44
45 reply = reply_;
46 return success_;
41}47}
4248
43void HttpClientQtThread::run()49void HttpClientQtThread::run()
44{50{
45 {51 QNetworkAccessManager* manager = new QNetworkAccessManager();
46 std::lock_guard<std::mutex> lock(reply_mutex_);52
4753 QNetworkRequest request(url_);
48 manager_ = new QNetworkAccessManager();54
4955 QNetworkReply* reply = manager->get(request);
50 QNetworkRequest request(url_);56
5157 connect(manager, &QNetworkAccessManager::finished, this, &HttpClientQtThread::got_reply, Qt::DirectConnection);
52 reply_ = manager_->get(request);58 connect(this, &HttpClientQtThread::abort, reply, &QNetworkReply::abort);
53
54 connect(manager_, &QNetworkAccessManager::finished, this, &HttpClientQtThread::queryDone);
55 connect(this, &HttpClientQtThread::abort, reply_, &QNetworkReply::abort);
56 }
5759
58 QTimer timeout;60 QTimer timeout;
59 timeout.singleShot(timeout_, this, SLOT(cancel()));61 timeout.singleShot(timeout_, this, SLOT(timeout()));
60 QThread::exec(); // enter event loop62 QThread::exec(); // enter event loop
61}63
6264 delete reply;
63QNetworkReply* HttpClientQtThread::getReply()65 delete manager;
64{
65 std::lock_guard<std::mutex> lock(reply_mutex_);
66 return reply_;
67}
68
69void HttpClientQtThread::queryDone(QNetworkReply*)
70{
71 quit();
72}66}
7367
74void HttpClientQtThread::cancel()68void HttpClientQtThread::cancel()
75{69{
76 std::lock_guard<std::mutex> lock(reply_mutex_);70 std::lock_guard<std::mutex> lock(reply_mutex_);
7771
78 if (reply_)72 success_ = false;
79 {73 reply_ = "Request to " + url_.url().toStdString() + ":" + std::to_string(url_.port()) + " cancelled";
80 emit abort();74
75 emit abort();
76 quit();
77}
78
79void HttpClientQtThread::timeout()
80{
81 std::lock_guard<std::mutex> lock(reply_mutex_);
82
83 success_ = false;
84 reply_ = "Request to " + url_.url().toStdString() + ":" + std::to_string(url_.port()) + " timed out";
85
86 emit abort();
87 quit();
88}
89
90void HttpClientQtThread::got_reply(QNetworkReply* reply)
91{
92 std::lock_guard<std::mutex> lock(reply_mutex_);
93
94 if (!reply)
95 {
96 // no reply
97 success_ = false;
98 reply_ = "No reply from " + url_.url().toStdString() + ":" + std::to_string(url_.port());
99 }
100 else if (!reply->isFinished())
101 {
102 // incomplete reply
103 success_ = false;
104 reply_ = "Incomplete reply from " + url_.url().toStdString() + ":" + std::to_string(url_.port());
105 }
106 else if (reply->error() != QNetworkReply::NoError)
107 {
108 // communication error
109 success_ = false;
110 reply_ = reply->errorString().toStdString();
111 }
112 else
113 {
114 success_ = true;
115 QByteArray byte_array = reply->readAll();
116 reply_ = std::string(byte_array.constData(), byte_array.size());
81 }117 }
82118
83 quit();119 quit();

Subscribers

People subscribed via source and target branches

to all changes: