Merge lp:~marcustomlinson/unity-scopes-api/lp-1267917-2 into lp:unity-scopes-api
- lp-1267917-2
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
Michal Hruby (mhr3) wrote : | # |
11 + moveToThread(this);
Shouldn't be necessary.
20 + reply_-
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?
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:131
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:132
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
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 QNetworkAccessM
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 QNetworkAccessM
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).
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:133
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
Michal Hruby (mhr3) wrote : | # |
Why is the query_done still there? Why not just quit() the thread?
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:134
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Michal Hruby (mhr3) wrote : | # |
+1, let's get it in!
- 138. By Marcus Tomlinson
-
Create a direct connection between manager.finished() and got_reply() in order to avoid reply being freed before use.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:136
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'include/unity/scopes/internal/smartscopes/HttpClientQtThread.h' | |||
2 | --- include/unity/scopes/internal/smartscopes/HttpClientQtThread.h 2014-01-13 07:18:08 +0000 | |||
3 | +++ include/unity/scopes/internal/smartscopes/HttpClientQtThread.h 2014-01-22 11:48:09 +0000 | |||
4 | @@ -38,12 +38,15 @@ | |||
5 | 38 | HttpClientQtThread(const QUrl& url, uint timeout); | 38 | HttpClientQtThread(const QUrl& url, uint timeout); |
6 | 39 | ~HttpClientQtThread(); | 39 | ~HttpClientQtThread(); |
7 | 40 | 40 | ||
8 | 41 | bool get_reply(std::string& reply); | ||
9 | 42 | |||
10 | 43 | private: | ||
11 | 41 | void run(); | 44 | void run(); |
12 | 42 | QNetworkReply* getReply(); | ||
13 | 43 | 45 | ||
14 | 44 | public Q_SLOTS: | 46 | public Q_SLOTS: |
15 | 45 | void cancel(); | 47 | void cancel(); |
17 | 46 | void queryDone(QNetworkReply*); | 48 | void timeout(); |
18 | 49 | void got_reply(QNetworkReply* reply); | ||
19 | 47 | 50 | ||
20 | 48 | Q_SIGNALS: | 51 | Q_SIGNALS: |
21 | 49 | void abort(); | 52 | void abort(); |
22 | @@ -51,9 +54,10 @@ | |||
23 | 51 | private: | 54 | private: |
24 | 52 | QUrl url_; | 55 | QUrl url_; |
25 | 53 | uint timeout_; | 56 | uint timeout_; |
26 | 54 | QNetworkReply* reply_; | ||
27 | 55 | QNetworkAccessManager* manager_; | ||
28 | 56 | std::mutex reply_mutex_; | 57 | std::mutex reply_mutex_; |
29 | 58 | |||
30 | 59 | bool success_; | ||
31 | 60 | std::string reply_; | ||
32 | 57 | }; | 61 | }; |
33 | 58 | 62 | ||
34 | 59 | #endif // UNITY_SCOPES_INTERNAL_SMARTSCOPES_HTTPCLIENTQTTHREAD_H | 63 | #endif // UNITY_SCOPES_INTERNAL_SMARTSCOPES_HTTPCLIENTQTTHREAD_H |
35 | 60 | 64 | ||
36 | === modified file 'src/scopes/internal/smartscopes/HttpClientQt.cpp' | |||
37 | --- src/scopes/internal/smartscopes/HttpClientQt.cpp 2014-01-13 12:25:13 +0000 | |||
38 | +++ src/scopes/internal/smartscopes/HttpClientQt.cpp 2014-01-22 11:48:09 +0000 | |||
39 | @@ -104,32 +104,18 @@ | |||
40 | 104 | 104 | ||
41 | 105 | get_qt_thread_->start(); | 105 | get_qt_thread_->start(); |
42 | 106 | loop.exec(); | 106 | loop.exec(); |
63 | 107 | get_qt_thread_->wait(); | 107 | |
64 | 108 | 108 | std::string reply; | |
65 | 109 | QNetworkReply* reply = get_qt_thread_->getReply(); | 109 | bool success = get_qt_thread_->get_reply(reply); |
66 | 110 | 110 | ||
67 | 111 | if (!reply) | 111 | if (!success) |
68 | 112 | { | 112 | { |
69 | 113 | // no reply | 113 | unity::ResourceException e(reply); |
50 | 114 | unity::ResourceException e("No reply from " + request_url + ":" + std::to_string(port)); | ||
51 | 115 | promise_->set_exception(e.self()); | ||
52 | 116 | } | ||
53 | 117 | else if (!reply->isFinished()) | ||
54 | 118 | { | ||
55 | 119 | // incomplete reply | ||
56 | 120 | unity::ResourceException e("Incomplete reply from " + request_url + ":" + std::to_string(port)); | ||
57 | 121 | promise_->set_exception(e.self()); | ||
58 | 122 | } | ||
59 | 123 | else if (reply->error() != QNetworkReply::NoError) | ||
60 | 124 | { | ||
61 | 125 | // communication error | ||
62 | 126 | unity::ResourceException e(reply->errorString().toStdString()); | ||
70 | 127 | promise_->set_exception(e.self()); | 114 | promise_->set_exception(e.self()); |
71 | 128 | } | 115 | } |
72 | 129 | else | 116 | else |
73 | 130 | { | 117 | { |
76 | 131 | QString reply_string(reply->readAll()); | 118 | promise_->set_value(reply); |
75 | 132 | promise_->set_value(reply_string.toStdString()); | ||
77 | 133 | } | 119 | } |
78 | 134 | }); | 120 | }); |
79 | 135 | 121 | ||
80 | 136 | 122 | ||
81 | === modified file 'src/scopes/internal/smartscopes/HttpClientQtThread.cpp' | |||
82 | --- src/scopes/internal/smartscopes/HttpClientQtThread.cpp 2014-01-13 12:25:13 +0000 | |||
83 | +++ src/scopes/internal/smartscopes/HttpClientQtThread.cpp 2014-01-22 11:48:09 +0000 | |||
84 | @@ -27,8 +27,7 @@ | |||
85 | 27 | : QThread(), | 27 | : QThread(), |
86 | 28 | url_(url), | 28 | url_(url), |
87 | 29 | timeout_(timeout), | 29 | timeout_(timeout), |
90 | 30 | reply_(nullptr), | 30 | success_(false) |
89 | 31 | manager_(nullptr) | ||
91 | 32 | { | 31 | { |
92 | 33 | } | 32 | } |
93 | 34 | 33 | ||
94 | @@ -36,48 +35,85 @@ | |||
95 | 36 | { | 35 | { |
96 | 37 | cancel(); | 36 | cancel(); |
97 | 38 | 37 | ||
100 | 39 | delete reply_; | 38 | wait(); |
101 | 40 | delete manager_; | 39 | } |
102 | 40 | |||
103 | 41 | bool HttpClientQtThread::get_reply(std::string& reply) | ||
104 | 42 | { | ||
105 | 43 | std::lock_guard<std::mutex> lock(reply_mutex_); | ||
106 | 44 | |||
107 | 45 | reply = reply_; | ||
108 | 46 | return success_; | ||
109 | 41 | } | 47 | } |
110 | 42 | 48 | ||
111 | 43 | void HttpClientQtThread::run() | 49 | void HttpClientQtThread::run() |
112 | 44 | { | 50 | { |
125 | 45 | { | 51 | QNetworkAccessManager* manager = new QNetworkAccessManager(); |
126 | 46 | std::lock_guard<std::mutex> lock(reply_mutex_); | 52 | |
127 | 47 | 53 | QNetworkRequest request(url_); | |
128 | 48 | manager_ = new QNetworkAccessManager(); | 54 | |
129 | 49 | 55 | QNetworkReply* reply = manager->get(request); | |
130 | 50 | QNetworkRequest request(url_); | 56 | |
131 | 51 | 57 | connect(manager, &QNetworkAccessManager::finished, this, &HttpClientQtThread::got_reply, Qt::DirectConnection); | |
132 | 52 | reply_ = manager_->get(request); | 58 | connect(this, &HttpClientQtThread::abort, reply, &QNetworkReply::abort); |
121 | 53 | |||
122 | 54 | connect(manager_, &QNetworkAccessManager::finished, this, &HttpClientQtThread::queryDone); | ||
123 | 55 | connect(this, &HttpClientQtThread::abort, reply_, &QNetworkReply::abort); | ||
124 | 56 | } | ||
133 | 57 | 59 | ||
134 | 58 | QTimer timeout; | 60 | QTimer timeout; |
136 | 59 | timeout.singleShot(timeout_, this, SLOT(cancel())); | 61 | timeout.singleShot(timeout_, this, SLOT(timeout())); |
137 | 60 | QThread::exec(); // enter event loop | 62 | QThread::exec(); // enter event loop |
149 | 61 | } | 63 | |
150 | 62 | 64 | delete reply; | |
151 | 63 | QNetworkReply* HttpClientQtThread::getReply() | 65 | delete manager; |
141 | 64 | { | ||
142 | 65 | std::lock_guard<std::mutex> lock(reply_mutex_); | ||
143 | 66 | return reply_; | ||
144 | 67 | } | ||
145 | 68 | |||
146 | 69 | void HttpClientQtThread::queryDone(QNetworkReply*) | ||
147 | 70 | { | ||
148 | 71 | quit(); | ||
152 | 72 | } | 66 | } |
153 | 73 | 67 | ||
154 | 74 | void HttpClientQtThread::cancel() | 68 | void HttpClientQtThread::cancel() |
155 | 75 | { | 69 | { |
156 | 76 | std::lock_guard<std::mutex> lock(reply_mutex_); | 70 | std::lock_guard<std::mutex> lock(reply_mutex_); |
157 | 77 | 71 | ||
161 | 78 | if (reply_) | 72 | success_ = false; |
162 | 79 | { | 73 | reply_ = "Request to " + url_.url().toStdString() + ":" + std::to_string(url_.port()) + " cancelled"; |
163 | 80 | emit abort(); | 74 | |
164 | 75 | emit abort(); | ||
165 | 76 | quit(); | ||
166 | 77 | } | ||
167 | 78 | |||
168 | 79 | void HttpClientQtThread::timeout() | ||
169 | 80 | { | ||
170 | 81 | std::lock_guard<std::mutex> lock(reply_mutex_); | ||
171 | 82 | |||
172 | 83 | success_ = false; | ||
173 | 84 | reply_ = "Request to " + url_.url().toStdString() + ":" + std::to_string(url_.port()) + " timed out"; | ||
174 | 85 | |||
175 | 86 | emit abort(); | ||
176 | 87 | quit(); | ||
177 | 88 | } | ||
178 | 89 | |||
179 | 90 | void HttpClientQtThread::got_reply(QNetworkReply* reply) | ||
180 | 91 | { | ||
181 | 92 | std::lock_guard<std::mutex> lock(reply_mutex_); | ||
182 | 93 | |||
183 | 94 | if (!reply) | ||
184 | 95 | { | ||
185 | 96 | // no reply | ||
186 | 97 | success_ = false; | ||
187 | 98 | reply_ = "No reply from " + url_.url().toStdString() + ":" + std::to_string(url_.port()); | ||
188 | 99 | } | ||
189 | 100 | else if (!reply->isFinished()) | ||
190 | 101 | { | ||
191 | 102 | // incomplete reply | ||
192 | 103 | success_ = false; | ||
193 | 104 | reply_ = "Incomplete reply from " + url_.url().toStdString() + ":" + std::to_string(url_.port()); | ||
194 | 105 | } | ||
195 | 106 | else if (reply->error() != QNetworkReply::NoError) | ||
196 | 107 | { | ||
197 | 108 | // communication error | ||
198 | 109 | success_ = false; | ||
199 | 110 | reply_ = reply->errorString().toStdString(); | ||
200 | 111 | } | ||
201 | 112 | else | ||
202 | 113 | { | ||
203 | 114 | success_ = true; | ||
204 | 115 | QByteArray byte_array = reply->readAll(); | ||
205 | 116 | reply_ = std::string(byte_array.constData(), byte_array.size()); | ||
206 | 81 | } | 117 | } |
207 | 82 | 118 | ||
208 | 83 | quit(); | 119 | quit(); |
PASSED: Continuous integration, rev:130 jenkins. qa.ubuntu. com/job/ unity-scopes- api-ci/ 252/ jenkins. qa.ubuntu. com/job/ unity-scopes- api-trusty- amd64-ci/ 252 jenkins. qa.ubuntu. com/job/ unity-scopes- api-trusty- armhf-ci/ 252 jenkins. qa.ubuntu. com/job/ unity-scopes- api-trusty- armhf-ci/ 252/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ unity-scopes- api-trusty- i386-ci/ 252
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- scopes- api-ci/ 252/rebuild
http://