Merge lp:~thomas-voss/dbus-cpp/fix-race-and-add-tests into lp:~phablet-team/dbus-cpp/soname_bump
- fix-race-and-add-tests
- Merge into soname_bump
Status: | Superseded |
---|---|
Proposed branch: | lp:~thomas-voss/dbus-cpp/fix-race-and-add-tests |
Merge into: | lp:~phablet-team/dbus-cpp/soname_bump |
Diff against target: |
1062 lines (+298/-481) 13 files modified
include/core/dbus/impl/object.h (+18/-31) include/core/dbus/object.h (+12/-1) include/core/dbus/pending_call.h (+0/-6) src/core/dbus/asio/executor.cpp (+36/-25) src/core/dbus/asio_executor.cpp (+0/-376) src/core/dbus/bus.cpp (+11/-1) src/core/dbus/dbus.cpp (+1/-0) src/core/dbus/pending_call_impl.h (+56/-30) tests/bus_test.cpp (+19/-4) tests/dbus_test.cpp (+7/-3) tests/executor_test.cpp (+135/-0) tests/signal_delivery_test.cpp (+2/-2) tests/test_service.h (+1/-2) |
To merge this branch: | bzr merge lp:~thomas-voss/dbus-cpp/fix-race-and-add-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Phablet Team | Pending | ||
Review via email: mp+209381@code.launchpad.net |
This proposal supersedes a proposal from 2014-03-05.
This proposal has been superseded by a proposal from 2014-03-05.
Commit message
Description of the change
Add a test for racyness and provide an alternative to _and_block.
- 51. By Thomas Voß
-
Remove obsolete std::cout's.
- 52. By Thomas Voß
-
Cleanup whitespace.
- 53. By Thomas Voß
-
Remove obsolete std::this_
thread: :sleep_ for in tests/bus_test.cpp. - 54. By Thomas Voß
-
Fix racyness in signal-delivery test.
- 55. By Thomas Voß
-
Add missing symbols for coverage build.
- 56. By Thomas Voß
-
Add a linker script to force the linker to only export symbols that are in namespace core::dbus::*.
Adjust symbol files accordingly. - 57. By Thomas Voß
-
Adjust symbol file for 32bit platforms to account for difference in size of integer types.
- 58. By Thomas Voß
-
Add specific symbols file for coverage builds, only containing optional symbols.
- 59. By Thomas Voß
-
Add missing coverage-specific symbols for armhf.
- 60. By Thomas Voß
-
Make sure that -fstack-protector is stripped from the compiler flags to avoid build failures on arm64.
- 61. By Thomas Voß
-
Move stripping of -fstack-protector before include.
Unmerged revisions
- 61. By Thomas Voß
-
Move stripping of -fstack-protector before include.
- 60. By Thomas Voß
-
Make sure that -fstack-protector is stripped from the compiler flags to avoid build failures on arm64.
- 59. By Thomas Voß
-
Add missing coverage-specific symbols for armhf.
- 58. By Thomas Voß
-
Add specific symbols file for coverage builds, only containing optional symbols.
- 57. By Thomas Voß
-
Adjust symbol file for 32bit platforms to account for difference in size of integer types.
- 56. By Thomas Voß
-
Add a linker script to force the linker to only export symbols that are in namespace core::dbus::*.
Adjust symbol files accordingly. - 55. By Thomas Voß
-
Add missing symbols for coverage build.
- 54. By Thomas Voß
-
Fix racyness in signal-delivery test.
- 53. By Thomas Voß
-
Remove obsolete std::this_
thread: :sleep_ for in tests/bus_test.cpp. - 52. By Thomas Voß
-
Cleanup whitespace.
Preview Diff
1 | === modified file 'include/core/dbus/impl/object.h' |
2 | --- include/core/dbus/impl/object.h 2014-01-24 11:23:09 +0000 |
3 | +++ include/core/dbus/impl/object.h 2014-03-05 07:16:59 +0000 |
4 | @@ -85,6 +85,12 @@ |
5 | } |
6 | |
7 | template<typename Method, typename ResultType, typename... Args> |
8 | +inline Result<ResultType> Object::transact_method(const Args& ... args) |
9 | +{ |
10 | + return invoke_method_asynchronously<Method, ResultType, Args...>(args...).get(); |
11 | +} |
12 | + |
13 | +template<typename Method, typename ResultType, typename... Args> |
14 | inline std::future<Result<ResultType>> Object::invoke_method_asynchronously(const Args& ... args) |
15 | { |
16 | auto msg_factory = parent->get_connection()->message_factory(); |
17 | @@ -97,41 +103,22 @@ |
18 | if (!msg) |
19 | throw std::runtime_error("No memory available to allocate DBus message"); |
20 | |
21 | - msg->writer().append(args...); |
22 | + auto writer = msg->writer(); |
23 | + encode_message(writer, args...); |
24 | |
25 | auto pending_call = |
26 | parent->get_connection()->send_with_reply_and_timeout( |
27 | - msg->get(), Method::default_timeout()); |
28 | + msg, Method::default_timeout()); |
29 | |
30 | - auto cb = |
31 | - [](DBusPendingCall* pending, void* user_data) |
32 | - { |
33 | - auto promise = static_cast<std::promise<Result<ResultType>>*>(user_data); |
34 | - |
35 | - auto msg = dbus_pending_call_steal_reply(pending); |
36 | - if (msg) |
37 | - { |
38 | - auto result = Result<ResultType>::from_message(msg); |
39 | - promise->set_value(result); |
40 | - } |
41 | - else |
42 | - { |
43 | - promise->set_exception(std::make_exception_ptr(std::runtime_error("Method invocation timed out"))); |
44 | - } |
45 | - }; |
46 | - |
47 | - auto promise = new std::promise<Result<ResultType>>(); |
48 | - |
49 | - dbus_pending_call_set_notify( |
50 | - pending_call, |
51 | - cb, |
52 | - promise, |
53 | - [](void* p) |
54 | - { |
55 | - delete static_cast<std::promise<Result<ResultType>>*>(p); |
56 | - }); |
57 | - |
58 | - return promise->get_future(); |
59 | + auto promise = std::make_shared<std::promise<Result<ResultType>>>(); |
60 | + auto future = promise->get_future(); |
61 | + |
62 | + pending_call->then([promise](const Message::Ptr& reply) |
63 | + { |
64 | + promise->set_value(Result<ResultType>::from_message(reply)); |
65 | + }); |
66 | + |
67 | + return future; |
68 | } |
69 | |
70 | template<typename PropertyDescription> |
71 | |
72 | === modified file 'include/core/dbus/object.h' |
73 | --- include/core/dbus/object.h 2014-01-24 11:17:26 +0000 |
74 | +++ include/core/dbus/object.h 2014-03-05 07:16:59 +0000 |
75 | @@ -97,7 +97,7 @@ |
76 | inline void emit_signal(const Args& ... args); |
77 | |
78 | /** |
79 | - * @brief Invokes a method of a remote object blocking while waiting for the result. |
80 | + * @brief Invokes a method of a remote object blocking the bus instance while waiting for the result. |
81 | * @tparam Method The method to invoke. |
82 | * @tparam ResultType The expected type of the result. |
83 | * @tparam Args Parameter pack of arguments passed to the invocation. |
84 | @@ -108,6 +108,17 @@ |
85 | inline Result<ResultType> invoke_method_synchronously(const Args& ... args); |
86 | |
87 | /** |
88 | + * @brief Invokes a method of a remote object blocking the current thread while waiting for the result. |
89 | + * @tparam Method The method to invoke. |
90 | + * @tparam ResultType The expected type of the result. |
91 | + * @tparam Args Parameter pack of arguments passed to the invocation. |
92 | + * @param [in] args Argument instances passed to the invocation. |
93 | + * @return A future wrapping an invocation result, either signalling an error or containing the result of the invocation. |
94 | + */ |
95 | + template<typename Method, typename ResultType, typename... Args> |
96 | + inline Result<ResultType> transact_method(const Args& ... args); |
97 | + |
98 | + /** |
99 | * @brief Invokes a method of a remote object returning a std::future to synchronize with the result |
100 | * @tparam Method The method to invoke. |
101 | * @tparam ResultType The expected type of the result. |
102 | |
103 | === modified file 'include/core/dbus/pending_call.h' |
104 | --- include/core/dbus/pending_call.h 2013-11-27 18:57:42 +0000 |
105 | +++ include/core/dbus/pending_call.h 2014-03-05 07:16:59 +0000 |
106 | @@ -63,12 +63,6 @@ |
107 | bool operator==(const PendingCall&) const = delete; |
108 | |
109 | /** |
110 | - * @brief Suspends the current thread until a reply has arrived. |
111 | - * @return Pointer to a message. |
112 | - */ |
113 | - virtual std::shared_ptr<Message> wait_for_reply() = 0; |
114 | - |
115 | - /** |
116 | * @brief Cancels the outstanding call. |
117 | */ |
118 | virtual void cancel() = 0; |
119 | |
120 | === modified file 'src/core/dbus/asio/executor.cpp' |
121 | --- src/core/dbus/asio/executor.cpp 2013-11-27 18:57:42 +0000 |
122 | +++ src/core/dbus/asio/executor.cpp 2014-03-05 07:16:59 +0000 |
123 | @@ -25,7 +25,12 @@ |
124 | #include <boost/asio/io_service.hpp> |
125 | |
126 | #include <stdexcept> |
127 | + |
128 | +#include <condition_variable> |
129 | #include <memory> |
130 | +#include <mutex> |
131 | +#include <future> |
132 | +#include <thread> |
133 | |
134 | namespace core |
135 | { |
136 | @@ -40,7 +45,7 @@ |
137 | |
138 | static inline bool is_timeout_enabled(DBusTimeout* timeout) |
139 | { |
140 | - return dbus_timeout_get_enabled(timeout); |
141 | + return TRUE == dbus_timeout_get_enabled(timeout); |
142 | } |
143 | |
144 | static inline int get_timeout_interval(DBusTimeout* timeout) |
145 | @@ -64,7 +69,7 @@ |
146 | |
147 | static inline bool is_watch_enabled(DBusWatch* watch) |
148 | { |
149 | - return dbus_watch_get_enabled(watch); |
150 | + return TRUE == dbus_watch_get_enabled(watch); |
151 | } |
152 | |
153 | static inline int get_watch_unix_fd(DBusWatch* watch) |
154 | @@ -105,9 +110,9 @@ |
155 | throw std::runtime_error("Precondition violated: timeout has to be non-null"); |
156 | } |
157 | |
158 | - ~Timeout() noexcept |
159 | + ~Timeout() |
160 | { |
161 | - timer.cancel(); |
162 | + // cancel(); |
163 | } |
164 | |
165 | void start() |
166 | @@ -117,37 +122,36 @@ |
167 | return; |
168 | } |
169 | |
170 | - timer.expires_from_now(boost::posix_time::milliseconds(traits::Timeout<UnderlyingTimeoutType>::get_timeout_interval(timeout))); |
171 | - timer.async_wait(std::bind(&Timeout::on_timeout, Timeout<UnderlyingTimeoutType>::shared_from_this(), std::placeholders::_1)); |
172 | + timer.expires_from_now( |
173 | + boost::posix_time::milliseconds( |
174 | + traits::Timeout<UnderlyingTimeoutType>::get_timeout_interval( |
175 | + timeout))); |
176 | + timer.async_wait( |
177 | + std::bind(&Timeout::on_timeout, |
178 | + Timeout<UnderlyingTimeoutType>::shared_from_this(), |
179 | + std::placeholders::_1)); |
180 | } |
181 | |
182 | - void restart_or_cancel() |
183 | + void cancel() |
184 | { |
185 | - if (!traits::Timeout<UnderlyingTimeoutType>::is_timeout_enabled(timeout)) |
186 | + try |
187 | { |
188 | timer.cancel(); |
189 | - return; |
190 | + } catch(...) |
191 | + { |
192 | + // Really not sure what we should do about exceptions here. |
193 | } |
194 | - |
195 | - timer.expires_from_now(boost::posix_time::milliseconds(traits::Timeout<UnderlyingTimeoutType>::get_timeout_interval(timeout))); |
196 | - timer.async_wait(std::bind(&Timeout::on_timeout, Timeout<UnderlyingTimeoutType>::shared_from_this(), std::placeholders::_1)); |
197 | - } |
198 | - |
199 | - void cancel() |
200 | - { |
201 | - timer.cancel(); |
202 | } |
203 | |
204 | void on_timeout(const boost::system::error_code& ec) |
205 | { |
206 | + if (ec == boost::asio::error::operation_aborted) |
207 | + return; |
208 | + |
209 | if (ec) |
210 | return; |
211 | |
212 | - if (ec != boost::asio::error::operation_aborted) |
213 | - { |
214 | - traits::Timeout<UnderlyingTimeoutType>::invoke_timeout_handler(timeout); |
215 | - restart_or_cancel(); |
216 | - } |
217 | + traits::Timeout<UnderlyingTimeoutType>::invoke_timeout_handler(timeout); |
218 | } |
219 | |
220 | boost::asio::io_service& io_service; |
221 | @@ -299,7 +303,7 @@ |
222 | Holder<std::shared_ptr<Timeout<>>>::ptr_delete); |
223 | |
224 | t->start(); |
225 | - return true; |
226 | + return TRUE; |
227 | } |
228 | |
229 | static void on_dbus_remove_timeout(DBusTimeout* timeout, void*) |
230 | @@ -310,13 +314,20 @@ |
231 | static void on_dbus_timeout_toggled(DBusTimeout* timeout, void*) |
232 | { |
233 | auto holder = static_cast<Holder<std::shared_ptr<Timeout<>>>*>(dbus_timeout_get_data(timeout)); |
234 | - holder->value->restart_or_cancel(); |
235 | + holder->value->start(); |
236 | } |
237 | |
238 | static void on_dbus_wakeup_event_loop(void* data) |
239 | { |
240 | auto thiz = static_cast<Executor*>(data); |
241 | - thiz->io_service.post(std::bind(dbus_connection_dispatch, thiz->bus->raw())); |
242 | + auto bus = thiz->bus; |
243 | + thiz->io_service.post([bus]() |
244 | + { |
245 | + while (dbus_connection_get_dispatch_status(bus->raw()) == DBUS_DISPATCH_DATA_REMAINS) |
246 | + { |
247 | + dbus_connection_dispatch(bus->raw()); |
248 | + } |
249 | + }); |
250 | } |
251 | |
252 | public: |
253 | |
254 | === removed file 'src/core/dbus/asio_executor.cpp' |
255 | --- src/core/dbus/asio_executor.cpp 2013-11-27 18:57:42 +0000 |
256 | +++ src/core/dbus/asio_executor.cpp 1970-01-01 00:00:00 +0000 |
257 | @@ -1,376 +0,0 @@ |
258 | -/* |
259 | - * Copyright © 2012 Canonical Ltd. |
260 | - * |
261 | - * This program is free software: you can redistribute it and/or modify it |
262 | - * under the terms of the GNU Lesser General Public License version 3, |
263 | - * as published by the Free Software Foundation. |
264 | - * |
265 | - * This program is distributed in the hope that it will be useful, |
266 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
267 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
268 | - * GNU Lesser General Public License for more details. |
269 | - * |
270 | - * You should have received a copy of the GNU Lesser General Public License |
271 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
272 | - * |
273 | - * Authored by: Thomas Voß <thomas.voss@canonical.com> |
274 | - */ |
275 | - |
276 | -#include <core/dbus/bus.h> |
277 | -#include <core/dbus/executor.h> |
278 | -#include <core/dbus/traits/timeout.h> |
279 | -#include <core/dbus/traits/watch.h> |
280 | - |
281 | -#include <boost/asio.hpp> |
282 | -#include <boost/asio/io_service.hpp> |
283 | - |
284 | -#include <stdexcept> |
285 | -#include <memory> |
286 | - |
287 | -namespace core |
288 | -{ |
289 | -namespace dbus |
290 | -{ |
291 | -namespace traits |
292 | -{ |
293 | -template<> |
294 | -struct Timeout<DBusTimeout> |
295 | -{ |
296 | - typedef int DurationType; |
297 | - |
298 | - static inline bool is_timeout_enabled(DBusTimeout* timeout) |
299 | - { |
300 | - return dbus_timeout_get_enabled(timeout); |
301 | - } |
302 | - |
303 | - static inline int get_timeout_interval(DBusTimeout* timeout) |
304 | - { |
305 | - return DurationType(dbus_timeout_get_interval(timeout)); |
306 | - } |
307 | - |
308 | - static inline void invoke_timeout_handler(DBusTimeout* timeout) |
309 | - { |
310 | - dbus_timeout_handle(timeout); |
311 | - } |
312 | -}; |
313 | - |
314 | -template<> |
315 | -struct Watch<DBusWatch> |
316 | -{ |
317 | - inline static int readable_event() { return DBUS_WATCH_READABLE; } |
318 | - inline static int writeable_event() { return DBUS_WATCH_WRITABLE; } |
319 | - inline static int error_event() { return DBUS_WATCH_ERROR; } |
320 | - inline static int hangup_event() { return DBUS_WATCH_HANGUP; } |
321 | - |
322 | - static inline bool is_watch_enabled(DBusWatch* watch) |
323 | - { |
324 | - return dbus_watch_get_enabled(watch); |
325 | - } |
326 | - |
327 | - static inline int get_watch_unix_fd(DBusWatch* watch) |
328 | - { |
329 | - return dbus_watch_get_unix_fd(watch); |
330 | - } |
331 | - |
332 | - static inline bool is_watch_monitoring_fd_for_readable(DBusWatch* watch) |
333 | - { |
334 | - return dbus_watch_get_flags(watch) & DBUS_WATCH_READABLE; |
335 | - } |
336 | - |
337 | - static bool is_watch_monitoring_fd_for_writable(DBusWatch* watch) |
338 | - { |
339 | - return dbus_watch_get_flags(watch) & DBUS_WATCH_WRITABLE; |
340 | - } |
341 | - |
342 | - static bool invoke_watch_handler_for_event(DBusWatch* watch, int event) |
343 | - { |
344 | - return dbus_watch_handle(watch, event); |
345 | - } |
346 | -}; |
347 | -} |
348 | -namespace asio |
349 | -{ |
350 | -class Executor : public core::dbus::Executor |
351 | -{ |
352 | - public: |
353 | - template<typename UnderlyingTimeoutType = DBusTimeout> |
354 | - struct Timeout : std::enable_shared_from_this<Timeout<UnderlyingTimeoutType>> |
355 | - { |
356 | - Timeout(boost::asio::io_service& io_service, UnderlyingTimeoutType* timeout) |
357 | - : io_service(io_service), |
358 | - timer(io_service), |
359 | - timeout(timeout) |
360 | - { |
361 | - if (!timeout) |
362 | - throw std::runtime_error("Precondition violated: timeout has to be non-null"); |
363 | - } |
364 | - |
365 | - ~Timeout() noexcept |
366 | - { |
367 | - timer.cancel(); |
368 | - } |
369 | - |
370 | - void start() |
371 | - { |
372 | - if (!traits::Timeout<UnderlyingTimeoutType>::is_timeout_enabled(timeout)) |
373 | - { |
374 | - return; |
375 | - } |
376 | - |
377 | - timer.expires_from_now(boost::posix_time::milliseconds(traits::Timeout<UnderlyingTimeoutType>::get_timeout_interval(timeout))); |
378 | - timer.async_wait(std::bind(&Timeout::on_timeout, Timeout<UnderlyingTimeoutType>::shared_from_this(), std::placeholders::_1)); |
379 | - } |
380 | - |
381 | - void restart_or_cancel() |
382 | - { |
383 | - if (!traits::Timeout<UnderlyingTimeoutType>::is_timeout_enabled(timeout)) |
384 | - { |
385 | - timer.cancel(); |
386 | - return; |
387 | - } |
388 | - |
389 | - timer.expires_from_now(boost::posix_time::milliseconds(traits::Timeout<UnderlyingTimeoutType>::get_timeout_interval(timeout))); |
390 | - timer.async_wait(std::bind(&Timeout::on_timeout, Timeout<UnderlyingTimeoutType>::shared_from_this(), std::placeholders::_1)); |
391 | - } |
392 | - |
393 | - void cancel() |
394 | - { |
395 | - timer.cancel(); |
396 | - } |
397 | - |
398 | - void on_timeout(const boost::system::error_code& ec) |
399 | - { |
400 | - if (ec) |
401 | - return; |
402 | - |
403 | - if (ec != boost::asio::error::operation_aborted) |
404 | - { |
405 | - traits::Timeout<UnderlyingTimeoutType>::invoke_timeout_handler(timeout); |
406 | - restart_or_cancel(); |
407 | - } |
408 | - } |
409 | - |
410 | - boost::asio::io_service& io_service; |
411 | - boost::asio::deadline_timer timer; |
412 | - UnderlyingTimeoutType* timeout; |
413 | - }; |
414 | - |
415 | - template<typename UnderlyingWatchType = DBusWatch> |
416 | - struct Watch : std::enable_shared_from_this<Watch<UnderlyingWatchType>> |
417 | - { |
418 | - Watch(boost::asio::io_service& io_service, UnderlyingWatchType* watch) : io_service(io_service), |
419 | - stream_descriptor(io_service), |
420 | - watch(watch) |
421 | - { |
422 | - if (!watch) |
423 | - throw std::runtime_error("Precondition violated: watch has to be non-null"); |
424 | - } |
425 | - |
426 | - ~Watch() noexcept |
427 | - { |
428 | - stream_descriptor.cancel(); |
429 | - stream_descriptor.release(); |
430 | - } |
431 | - |
432 | - void start() |
433 | - { |
434 | - stream_descriptor.assign(traits::Watch<UnderlyingWatchType>::get_watch_unix_fd(watch)); |
435 | - restart(); |
436 | - } |
437 | - |
438 | - void restart() |
439 | - { |
440 | - if (traits::Watch<UnderlyingWatchType>::is_watch_monitoring_fd_for_readable(watch)) |
441 | - { |
442 | - stream_descriptor.async_read_some( |
443 | - boost::asio::null_buffers(), |
444 | - std::bind( |
445 | - &Watch::on_stream_descriptor_event, |
446 | - Watch<UnderlyingWatchType>::shared_from_this(), |
447 | - traits::Watch<UnderlyingWatchType>::readable_event(), |
448 | - std::placeholders::_1, |
449 | - std::placeholders::_2)); |
450 | - } |
451 | - if (traits::Watch<UnderlyingWatchType>::is_watch_monitoring_fd_for_writable(watch)) |
452 | - { |
453 | - stream_descriptor.async_write_some( |
454 | - boost::asio::null_buffers(), |
455 | - std::bind( |
456 | - &Watch::on_stream_descriptor_event, |
457 | - Watch<UnderlyingWatchType>::shared_from_this(), |
458 | - traits::Watch<UnderlyingWatchType>::writeable_event(), |
459 | - std::placeholders::_1, |
460 | - std::placeholders::_2)); |
461 | - } |
462 | - } |
463 | - |
464 | - void cancel() |
465 | - { |
466 | - try |
467 | - { |
468 | - stream_descriptor.cancel(); |
469 | - } |
470 | - catch (...) |
471 | - { |
472 | - } |
473 | - } |
474 | - |
475 | - void on_stream_descriptor_event(int event, const boost::system::error_code& error, std::size_t) |
476 | - { |
477 | - if (error == boost::asio::error::operation_aborted) |
478 | - { |
479 | - return; |
480 | - } |
481 | - |
482 | - if (error) |
483 | - { |
484 | - traits::Watch<UnderlyingWatchType>::invoke_watch_handler_for_event( |
485 | - watch, |
486 | - traits::Watch<UnderlyingWatchType>::error_event()); |
487 | - } |
488 | - else |
489 | - { |
490 | - if (!traits::Watch<UnderlyingWatchType>::invoke_watch_handler_for_event(watch, event)) |
491 | - throw std::runtime_error("Insufficient memory while handling watch event"); |
492 | - |
493 | - restart(); |
494 | - } |
495 | - } |
496 | - |
497 | - boost::asio::io_service& io_service; |
498 | - boost::asio::posix::stream_descriptor stream_descriptor; |
499 | - UnderlyingWatchType* watch; |
500 | - }; |
501 | - |
502 | - template<typename T> |
503 | - struct Holder |
504 | - { |
505 | - static void ptr_delete(void* p) |
506 | - { |
507 | - delete static_cast<Holder<T>*>(p); |
508 | - } |
509 | - |
510 | - Holder(const T& t) : value(t) |
511 | - { |
512 | - } |
513 | - |
514 | - T value; |
515 | - }; |
516 | - |
517 | - static dbus_bool_t on_dbus_add_watch(DBusWatch* watch, void* data) |
518 | - { |
519 | - if (dbus_watch_get_enabled(watch) == FALSE) |
520 | - return TRUE; |
521 | - |
522 | - auto thiz = static_cast<Executor*>(data); |
523 | - auto w = std::shared_ptr<Watch<>>(new Watch<>(thiz->io_service, watch)); |
524 | - auto holder = new Holder<std::shared_ptr<Watch<>>>(w); |
525 | - dbus_watch_set_data(watch, holder, Holder<std::shared_ptr<Watch<>>>::ptr_delete); |
526 | - |
527 | - w->start(); |
528 | - |
529 | - return TRUE; |
530 | - } |
531 | - |
532 | - static void on_dbus_remove_watch(DBusWatch* watch, void*) |
533 | - { |
534 | - auto w = static_cast<Holder<std::shared_ptr<Watch<>>>*>(dbus_watch_get_data(watch)); |
535 | - if (!w) |
536 | - return; |
537 | - w->value->cancel(); |
538 | - } |
539 | - |
540 | - static void on_dbus_watch_toggled(DBusWatch* watch, void*) |
541 | - { |
542 | - auto holder = static_cast<Holder<std::shared_ptr<Watch<>>>*>(dbus_watch_get_data(watch)); |
543 | - if (!holder) |
544 | - return; |
545 | - dbus_watch_get_enabled(watch) == TRUE ? holder->value->restart() : holder->value->cancel(); |
546 | - } |
547 | - |
548 | - static dbus_bool_t on_dbus_add_timeout(DBusTimeout* timeout, void* data) |
549 | - { |
550 | - auto thiz = static_cast<Executor*>(data); |
551 | - auto t = std::shared_ptr<Timeout<>>(new Timeout<>(thiz->io_service, timeout)); |
552 | - auto holder = new Holder<std::shared_ptr<Timeout<>>>(t); |
553 | - dbus_timeout_set_data( |
554 | - timeout, |
555 | - holder, |
556 | - Holder<std::shared_ptr<Timeout<>>>::ptr_delete); |
557 | - |
558 | - t->start(); |
559 | - return true; |
560 | - } |
561 | - |
562 | - static void on_dbus_remove_timeout(DBusTimeout* timeout, void*) |
563 | - { |
564 | - static_cast<Holder<std::shared_ptr<Timeout<>>>*>(dbus_timeout_get_data(timeout))->value->cancel(); |
565 | - } |
566 | - |
567 | - static void on_dbus_timeout_toggled(DBusTimeout* timeout, void*) |
568 | - { |
569 | - auto holder = static_cast<Holder<std::shared_ptr<Timeout<>>>*>(dbus_timeout_get_data(timeout)); |
570 | - holder->value->restart_or_cancel(); |
571 | - } |
572 | - |
573 | - static void on_dbus_wakeup_event_loop(void* data) |
574 | - { |
575 | - auto thiz = static_cast<Executor*>(data); |
576 | - thiz->io_service.post(std::bind(dbus_connection_dispatch, thiz->bus->raw())); |
577 | - } |
578 | - |
579 | - public: |
580 | - explicit Executor(const Bus::Ptr& bus) : bus(bus), work(io_service) |
581 | - { |
582 | - if (!bus) |
583 | - throw std::runtime_error("Precondition violated, cannot construct executor for null bus."); |
584 | - |
585 | - if (!dbus_connection_set_watch_functions( |
586 | - bus->raw(), |
587 | - on_dbus_add_watch, |
588 | - on_dbus_remove_watch, |
589 | - on_dbus_watch_toggled, |
590 | - this, |
591 | - nullptr)) |
592 | - throw std::runtime_error("Problem installing watch functions."); |
593 | - |
594 | - if (!dbus_connection_set_timeout_functions( |
595 | - bus->raw(), |
596 | - on_dbus_add_timeout, |
597 | - on_dbus_remove_timeout, |
598 | - on_dbus_timeout_toggled, |
599 | - this, |
600 | - nullptr)) |
601 | - throw std::runtime_error("Problem installing timeout functions."); |
602 | - |
603 | - dbus_connection_set_wakeup_main_function( |
604 | - bus->raw(), |
605 | - on_dbus_wakeup_event_loop, |
606 | - this, |
607 | - nullptr); |
608 | - } |
609 | - |
610 | - ~Executor() noexcept |
611 | - { |
612 | - stop(); |
613 | - } |
614 | - |
615 | - void run() |
616 | - { |
617 | - io_service.run(); |
618 | - } |
619 | - |
620 | - void stop() |
621 | - { |
622 | - io_service.stop(); |
623 | - } |
624 | - |
625 | - private: |
626 | - Bus::Ptr bus; |
627 | - boost::asio::io_service io_service; |
628 | - boost::asio::io_service::work work; |
629 | -}; |
630 | -} |
631 | -} |
632 | -} |
633 | -} |
634 | |
635 | === modified file 'src/core/dbus/bus.cpp' |
636 | --- src/core/dbus/bus.cpp 2014-01-25 16:08:10 +0000 |
637 | +++ src/core/dbus/bus.cpp 2014-03-05 07:16:59 +0000 |
638 | @@ -252,6 +252,16 @@ |
639 | const std::shared_ptr<Message>& msg, |
640 | const std::chrono::milliseconds& milliseconds) |
641 | { |
642 | + // TODO(tvoss): Enable this once method handlers have been adjusted to |
643 | + // operate on contexts. |
644 | + // if (d->executor) |
645 | + // { |
646 | + // throw std::logic_error("Calling Bus::send_with_reply_and_block_for_at_most on a " |
647 | + // "connection that is run by an executor, i.e., an event loop " |
648 | + // "is not supported as the underlying implementation in libdbus " |
649 | + // "is racy"); |
650 | + //} |
651 | + |
652 | Error se; |
653 | |
654 | auto result = dbus_connection_send_with_reply_and_block( |
655 | @@ -277,7 +287,7 @@ |
656 | std::addressof(pending_call), |
657 | timeout.count()); |
658 | |
659 | - if (!result) |
660 | + if (result == FALSE) |
661 | throw Errors::NoMemory{}; |
662 | |
663 | if (!pending_call) |
664 | |
665 | === modified file 'src/core/dbus/dbus.cpp' |
666 | --- src/core/dbus/dbus.cpp 2014-01-27 15:09:15 +0000 |
667 | +++ src/core/dbus/dbus.cpp 2014-03-05 07:16:59 +0000 |
668 | @@ -146,6 +146,7 @@ |
669 | |
670 | std::string DBus::hello() const |
671 | { |
672 | + // TODO: We really should switch to method transaction here. |
673 | return object->invoke_method_synchronously<Hello, std::string>().value(); |
674 | } |
675 | |
676 | |
677 | === modified file 'src/core/dbus/pending_call_impl.h' |
678 | --- src/core/dbus/pending_call_impl.h 2013-11-27 18:57:42 +0000 |
679 | +++ src/core/dbus/pending_call_impl.h 2014-03-05 07:16:59 +0000 |
680 | @@ -24,6 +24,7 @@ |
681 | |
682 | #include <dbus/dbus.h> |
683 | |
684 | +#include <condition_variable> |
685 | #include <mutex> |
686 | |
687 | namespace core |
688 | @@ -37,54 +38,73 @@ |
689 | private: |
690 | struct Wrapper |
691 | { |
692 | - std::shared_ptr<PendingCall> pending_call; |
693 | + std::shared_ptr<core::dbus::impl::PendingCall> pending_call; |
694 | }; |
695 | |
696 | static void on_pending_call_completed(DBusPendingCall* call, |
697 | void* cookie) |
698 | { |
699 | auto wrapper = static_cast<Wrapper*>(cookie); |
700 | - std::lock_guard<std::mutex> lg(wrapper->pending_call->callback_guard); |
701 | - if (wrapper->pending_call->callback) |
702 | + |
703 | + auto message = dbus_pending_call_steal_reply(call); |
704 | + |
705 | + if (message) |
706 | { |
707 | - auto message = dbus_pending_call_steal_reply(call); |
708 | - if (message) |
709 | - wrapper->pending_call->callback(Message::from_raw_message(message)); |
710 | + wrapper->pending_call->notify(Message::from_raw_message(message)); |
711 | } |
712 | } |
713 | |
714 | + void notify(const Message::Ptr& msg) |
715 | + { |
716 | + std::lock_guard<std::mutex> lg(guard); |
717 | + |
718 | + message = msg; |
719 | + |
720 | + if (callback) |
721 | + callback(message); |
722 | + |
723 | + } |
724 | + |
725 | DBusPendingCall* pending_call; |
726 | - std::mutex callback_guard; |
727 | - core::dbus::PendingCall::Notification callback; |
728 | + std::mutex guard; |
729 | + Message::Ptr message; |
730 | + PendingCall::Notification callback; |
731 | |
732 | public: |
733 | inline static core::dbus::PendingCall::Ptr create(DBusPendingCall* call) |
734 | { |
735 | - auto result = std::shared_ptr<core::dbus::impl::PendingCall>( |
736 | - new core::dbus::impl::PendingCall(call)); |
737 | - |
738 | - dbus_pending_call_set_notify( |
739 | - result->pending_call, |
740 | - PendingCall::on_pending_call_completed, |
741 | - new Wrapper{result}, |
742 | - [](void* data) { delete static_cast<Wrapper*>(data); }); |
743 | + auto result = std::shared_ptr<core::dbus::impl::PendingCall> |
744 | + { |
745 | + new core::dbus::impl::PendingCall{call} |
746 | + }; |
747 | + |
748 | + if (FALSE == dbus_pending_call_set_notify( |
749 | + result->pending_call, |
750 | + PendingCall::on_pending_call_completed, |
751 | + new Wrapper{result}, |
752 | + [](void* data) |
753 | + { |
754 | + delete static_cast<Wrapper*>(data); |
755 | + })) |
756 | + { |
757 | + throw std::runtime_error("Error setting up pending call notification."); |
758 | + } |
759 | + |
760 | + // And here comes the beauty of libdbus, and its racy architecture: |
761 | + { |
762 | + std::lock_guard<std::mutex> lg(result->guard); |
763 | + if (TRUE == dbus_pending_call_get_completed(call)) |
764 | + { |
765 | + // We took too long while setting up the pending call notification. |
766 | + // For that we now have to inject the message here. |
767 | + auto msg = dbus_pending_call_steal_reply(call); |
768 | + result->message = Message::from_raw_message(msg); |
769 | + } |
770 | + } |
771 | |
772 | return std::dynamic_pointer_cast<core::dbus::PendingCall>(result); |
773 | } |
774 | |
775 | - inline std::shared_ptr<Message> wait_for_reply() |
776 | - { |
777 | - dbus_pending_call_block(pending_call); |
778 | - auto reply = dbus_pending_call_steal_reply(pending_call); |
779 | - |
780 | - if (!reply) |
781 | - { |
782 | - // TODO(tvoss): Throw PendingCallYieldedEmptyReply. |
783 | - } |
784 | - |
785 | - return Message::from_raw_message(reply); |
786 | - } |
787 | - |
788 | void cancel() |
789 | { |
790 | dbus_pending_call_cancel(pending_call); |
791 | @@ -92,14 +112,20 @@ |
792 | |
793 | void then(const core::dbus::PendingCall::Notification& notification) |
794 | { |
795 | - std::lock_guard<std::mutex> lg{callback_guard}; |
796 | + std::lock_guard<std::mutex> lg(guard); |
797 | callback = notification; |
798 | + |
799 | + // We already have a reply and invoke the callback directly. |
800 | + if (message) |
801 | + callback(message); |
802 | } |
803 | |
804 | private: |
805 | PendingCall(DBusPendingCall* call) |
806 | : pending_call(call) |
807 | { |
808 | + |
809 | + |
810 | } |
811 | }; |
812 | } |
813 | |
814 | === modified file 'tests/bus_test.cpp' |
815 | --- tests/bus_test.cpp 2014-01-10 07:34:59 +0000 |
816 | +++ tests/bus_test.cpp 2014-03-05 07:16:59 +0000 |
817 | @@ -88,13 +88,28 @@ |
818 | "ListNames"); |
819 | |
820 | auto bus = session_bus(); |
821 | + bus->install_executor(dbus::asio::make_executor(bus)); |
822 | + std::thread t{[bus](){bus->run();}}; |
823 | + |
824 | + std::this_thread::sleep_for(std::chrono::seconds{1}); |
825 | + |
826 | const std::chrono::milliseconds timeout = std::chrono::seconds(10); |
827 | |
828 | auto call = bus->send_with_reply_and_timeout(msg, timeout); |
829 | - auto reply = call->wait_for_reply(); |
830 | - EXPECT_EQ(dbus::Message::Type::method_return, reply->type()); |
831 | - std::vector<std::string> result; reply->reader() >> result; |
832 | - EXPECT_TRUE(result.size() > 0); |
833 | + auto promise = std::make_shared<std::promise<std::vector<std::string>>>(); |
834 | + auto future = promise->get_future(); |
835 | + call->then([promise](const core::dbus::Message::Ptr& reply) |
836 | + { |
837 | + std::vector<std::string> result; reply->reader() >> result; |
838 | + promise->set_value(result); |
839 | + }); |
840 | + |
841 | + EXPECT_TRUE(future.get().size() > 0); |
842 | + |
843 | + bus->stop(); |
844 | + |
845 | + if (t.joinable()) |
846 | + t.join(); |
847 | } |
848 | |
849 | TEST_F(Bus, HasOwnerForNameReturnsTrueForExistingName) |
850 | |
851 | === modified file 'tests/dbus_test.cpp' |
852 | --- tests/dbus_test.cpp 2014-02-18 06:43:55 +0000 |
853 | +++ tests/dbus_test.cpp 2014-03-05 07:16:59 +0000 |
854 | @@ -98,12 +98,16 @@ |
855 | object->install_method_handler<test::Service::Method>(handler); |
856 | barrier.try_signal_ready_for(std::chrono::milliseconds{500}); |
857 | |
858 | - std::thread t{[bus](){ bus->run(); }}; |
859 | + std::thread t1{[bus](){ bus->run(); }}; |
860 | + std::thread t2{[bus](){ bus->run(); }}; |
861 | |
862 | sc.wait_for_signal(); |
863 | |
864 | - if (t.joinable()) |
865 | - t.join(); |
866 | + if (t1.joinable()) |
867 | + t1.join(); |
868 | + |
869 | + if (t2.joinable()) |
870 | + t2.join(); |
871 | |
872 | EXPECT_EQ(pid, sender_pid); |
873 | EXPECT_EQ(uid, sender_uid); |
874 | |
875 | === modified file 'tests/executor_test.cpp' |
876 | --- tests/executor_test.cpp 2014-02-18 06:43:55 +0000 |
877 | +++ tests/executor_test.cpp 2014-03-05 07:16:59 +0000 |
878 | @@ -36,6 +36,22 @@ |
879 | |
880 | namespace |
881 | { |
882 | +// A very simple wrapper for flipping a coin whether something should go wrong. |
883 | +// Please note that we are considering an unfair coin here, with a probability of 0.2 |
884 | +// for failure. |
885 | +struct ChaosMonkey |
886 | +{ |
887 | + constexpr static const double probability_for_failure{0.2}; |
888 | + |
889 | + bool thinks_that_something_should_go_wrong() |
890 | + { |
891 | + return coin(rng) == 1; |
892 | + } |
893 | + |
894 | + std::mt19937 rng{42}; |
895 | + std::binomial_distribution<> coin{1, 1.-probability_for_failure}; |
896 | +}; |
897 | + |
898 | struct Executor : public core::dbus::testing::Fixture |
899 | { |
900 | }; |
901 | @@ -127,6 +143,125 @@ |
902 | EXPECT_EQ(core::testing::ForkAndRunResult::empty, core::testing::fork_and_run(service, client)); |
903 | } |
904 | |
905 | +TEST_F(Executor, TimeoutsAreHandledCorrectly) |
906 | +{ |
907 | + core::testing::CrossProcessSync cross_process_sync; |
908 | + |
909 | + const int64_t expected_value = 42; |
910 | + auto service = [this, expected_value, &cross_process_sync]() |
911 | + { |
912 | + ChaosMonkey chaos_monkey; |
913 | + core::testing::SigTermCatcher sc; |
914 | + auto bus = session_bus(); |
915 | + bus->install_executor(dbus::asio::make_executor(bus)); |
916 | + auto service = dbus::Service::add_service<test::Service>(bus); |
917 | + auto skeleton = service->add_object_for_path(dbus::types::ObjectPath("/this/is/unlikely/to/exist/Service")); |
918 | + skeleton->install_method_handler<test::Service::Method>( |
919 | + [bus, skeleton, expected_value, &chaos_monkey](const dbus::Message::Ptr& msg) |
920 | + { |
921 | + // Let's add some randomness to this test and see if things are still stable. |
922 | + // In this case, chaos means an early return and no reply being sent. |
923 | + if (chaos_monkey.thinks_that_something_should_go_wrong()) |
924 | + return; |
925 | + |
926 | + auto reply = dbus::Message::make_method_return(msg); |
927 | + reply->writer() << expected_value; |
928 | + bus->send(reply); |
929 | + }); |
930 | + |
931 | + cross_process_sync.try_signal_ready_for(std::chrono::milliseconds{500}); |
932 | + |
933 | + std::thread w1([bus]() { bus->run(); }); |
934 | + std::thread w2([bus]() { bus->run(); }); |
935 | + |
936 | + sc.wait_for_signal(); |
937 | + |
938 | + bus->stop(); |
939 | + |
940 | + if (w1.joinable()) |
941 | + w1.join(); |
942 | + |
943 | + if (w2.joinable()) |
944 | + w2.join(); |
945 | + |
946 | + return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success; |
947 | + }; |
948 | + |
949 | + auto client = [this, expected_value, &cross_process_sync]() -> core::posix::exit::Status |
950 | + { |
951 | + auto bus = session_bus(); |
952 | + bus->install_executor(dbus::asio::make_executor(bus)); |
953 | + std::thread t{[bus](){bus->run();}}; |
954 | + |
955 | + // If you encounter failures in the client, uncomment the following two lines |
956 | + // and follow the instructions on the terminal. |
957 | + // std::cout << "Invoke gdb with: sudo gdb -p " << getpid() << std::endl; |
958 | + // sleep(10); |
959 | + |
960 | + EXPECT_EQ(std::uint32_t(1), cross_process_sync.wait_for_signal_ready_for(std::chrono::milliseconds{500})); |
961 | + |
962 | + auto stub_service = dbus::Service::use_service(bus, dbus::traits::Service<test::Service>::interface_name()); |
963 | + auto stub = stub_service->object_for_path(dbus::types::ObjectPath("/this/is/unlikely/to/exist/Service")); |
964 | + |
965 | + std::thread updater1([bus, stub]() |
966 | + { |
967 | + for (unsigned int counter = 0; counter < 1000; counter++) |
968 | + { |
969 | + try |
970 | + { |
971 | + auto result = stub->transact_method<test::Service::Method, int64_t>(); |
972 | + |
973 | + if (result.is_error()) |
974 | + std::cout << result.error().print() << std::endl; |
975 | + else |
976 | + EXPECT_EQ(42, result.value()); |
977 | + |
978 | + } catch(const std::runtime_error& e) |
979 | + { |
980 | + std::cout << e.what() << std::endl; |
981 | + } |
982 | + } |
983 | + }); |
984 | + |
985 | + std::thread updater2([bus, stub]() |
986 | + { |
987 | + for (unsigned int counter = 0; counter < 1000; counter++) |
988 | + { |
989 | + try |
990 | + { |
991 | + auto result = stub->transact_method<test::Service::Method, int64_t>(); |
992 | + |
993 | + if (result.is_error()) |
994 | + std::cout << result.error().print() << std::endl; |
995 | + else |
996 | + EXPECT_EQ(42, result.value()); |
997 | + |
998 | + } catch(const std::runtime_error& e) |
999 | + { |
1000 | + std::cout << e.what() << std::endl; |
1001 | + } |
1002 | + } |
1003 | + }); |
1004 | + |
1005 | + if (updater1.joinable()) |
1006 | + updater1.join(); |
1007 | + |
1008 | + if (updater2.joinable()) |
1009 | + updater2.join(); |
1010 | + |
1011 | + bus->stop(); |
1012 | + |
1013 | + if (t.joinable()) |
1014 | + t.join(); |
1015 | + |
1016 | + return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success; |
1017 | + }; |
1018 | + |
1019 | + EXPECT_EQ(core::testing::ForkAndRunResult::empty, core::testing::fork_and_run(service, client)); |
1020 | +} |
1021 | + |
1022 | + |
1023 | + |
1024 | /*TEST(Bus, TimeoutThrowsForNullDBusWatch) |
1025 | { |
1026 | boost::asio::io_service io_service; |
1027 | |
1028 | === modified file 'tests/signal_delivery_test.cpp' |
1029 | --- tests/signal_delivery_test.cpp 2014-02-18 06:43:55 +0000 |
1030 | +++ tests/signal_delivery_test.cpp 2014-03-05 07:16:59 +0000 |
1031 | @@ -323,8 +323,8 @@ |
1032 | if (t.joinable()) |
1033 | t.join(); |
1034 | |
1035 | - EXPECT_EQ(std::uint32_t(1), received1); |
1036 | - EXPECT_EQ(std::uint32_t(1), received2); |
1037 | + EXPECT_EQ(std::uint32_t(2), received1); |
1038 | + EXPECT_EQ(std::uint32_t(2), received2); |
1039 | |
1040 | return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success; |
1041 | }; |
1042 | |
1043 | === modified file 'tests/test_service.h' |
1044 | --- tests/test_service.h 2014-02-18 06:43:55 +0000 |
1045 | +++ tests/test_service.h 2014-03-05 07:16:59 +0000 |
1046 | @@ -38,7 +38,7 @@ |
1047 | |
1048 | inline static const std::chrono::milliseconds default_timeout() |
1049 | { |
1050 | - return std::chrono::seconds{1}; |
1051 | + return std::chrono::milliseconds{10}; |
1052 | } |
1053 | }; |
1054 | |
1055 | @@ -126,7 +126,6 @@ |
1056 | { |
1057 | "this.is.unlikely.to.exist.Service" |
1058 | }; |
1059 | - std::cout << __PRETTY_FUNCTION__ << ": " << s << std::endl; |
1060 | return s; |
1061 | } |
1062 | }; |