Merge lp:~thomas-voss/location-service/factor-out-runtime into lp:location-service/15.04

Proposed by Thomas Voß
Status: Superseded
Proposed branch: lp:~thomas-voss/location-service/factor-out-runtime
Merge into: lp:location-service/15.04
Prerequisite: lp:~thomas-voss/location-service/robustify-event-propagation-in-case-of-multiple-providers-running
Diff against target: 596 lines (+343/-104)
9 files modified
src/location_service/com/ubuntu/location/CMakeLists.txt (+1/-0)
src/location_service/com/ubuntu/location/service/daemon.cpp (+8/-85)
src/location_service/com/ubuntu/location/service/provider_daemon.cpp (+11/-16)
src/location_service/com/ubuntu/location/service/runtime.cpp (+109/-0)
src/location_service/com/ubuntu/location/service/runtime.h (+90/-0)
tests/CMakeLists.txt (+1/-0)
tests/acceptance_tests.cpp (+4/-2)
tests/position_test.cpp (+1/-1)
tests/runtime_test.cpp (+118/-0)
To merge this branch: bzr merge lp:~thomas-voss/location-service/factor-out-runtime
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+277955@code.launchpad.net

This proposal has been superseded by a proposal from 2015-11-26.

Commit message

- Factor out service::Runtime from daemon.cpp into its own .h/.cpp pair of files.
- Add test cases around correct operation of service::Runtime.

Description of the change

- Factor out service::Runtime from daemon.cpp into its own .h/.cpp pair of files.
- Add test cases around correct operation of service::Runtime.

To post a comment you must log in.
134. By Thomas Voß

Reuse service::Runtime in RemoteProviderDaemon.

135. By Thomas Voß

Support explicit starting of service::Runtime.

136. By Thomas Voß

Fix service::Runtime test cases to explicitly start the runtime.

137. By Thomas Voß

Ensure two distinct bus instances.

138. By Thomas Voß

Remove dependency on robustify-...

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/location_service/com/ubuntu/location/CMakeLists.txt'
2--- src/location_service/com/ubuntu/location/CMakeLists.txt 2015-11-25 16:02:34 +0000
3+++ src/location_service/com/ubuntu/location/CMakeLists.txt 2015-11-25 16:02:35 +0000
4@@ -39,6 +39,7 @@
5 service/harvester.cpp
6 service/demultiplexing_reporter.h
7 service/demultiplexing_reporter.cpp
8+ service/runtime.cpp
9 service/runtime_tests.h
10 service/runtime_tests.cpp
11 service/trust_store_permission_manager.cpp
12
13=== modified file 'src/location_service/com/ubuntu/location/service/daemon.cpp'
14--- src/location_service/com/ubuntu/location/service/daemon.cpp 2015-11-25 16:02:34 +0000
15+++ src/location_service/com/ubuntu/location/service/daemon.cpp 2015-11-25 16:02:35 +0000
16@@ -34,6 +34,7 @@
17
18 #include "program_options.h"
19 #include "daemon.h"
20+#include "runtime.h"
21
22 #include <core/dbus/announcer.h>
23 #include <core/dbus/resolver.h>
24@@ -78,88 +79,6 @@
25 }
26 };
27
28-// We bundle our "global" runtime dependencies here, specifically
29-// a dispatcher to decouple multiple in-process providers from one
30-// another , forcing execution to a well known set of threads.
31-struct Runtime
32-{
33- // Our default concurrency setup.
34- static constexpr const std::uint32_t worker_threads = 2;
35-
36- // Our global singleton instance.
37- static Runtime& instance()
38- {
39- static Runtime runtime;
40- return runtime;
41- }
42-
43- Runtime()
44- : running{true},
45- service{worker_threads},
46- strand{service},
47- keep_alive{service}
48- {
49- for (unsigned int i = 0; i < worker_threads; i++)
50- workers.push_back(std::thread
51- {
52- [this]()
53- {
54- while(running)
55- {
56- try
57- {
58- service.run();
59- break;
60- }
61- catch (const std::exception& e)
62- {
63- LOG(WARNING) << e.what();
64- }
65- catch (...)
66- {
67- LOG(WARNING) << "Unknown exception caught while executing boost::asio::io_service";
68- }
69- }
70- }
71- });
72- }
73-
74- ~Runtime()
75- {
76- stop();
77- }
78-
79- void stop()
80- {
81- VLOG(1) << __PRETTY_FUNCTION__;
82- running = false;
83- service.stop();
84- VLOG(1) << "\t Service stopped.";
85-
86- for (auto& worker : workers)
87- if (worker.joinable())
88- worker.join();
89-
90- VLOG(1) << "\t Worker threads joined.";
91- }
92-
93- // Allows for reusing the runtime in components that require a dispatcher
94- // to control execution of tasks.
95- std::function<void(std::function<void()>)> to_dispatcher_functional()
96- {
97- return [this](std::function<void()> task)
98- {
99- strand.post(task);
100- };
101- }
102-
103- bool running;
104- boost::asio::io_service service;
105- boost::asio::io_service::strand strand;
106- boost::asio::io_service::work keep_alive;
107- std::vector<std::thread> workers;
108-};
109-
110 location::ProgramOptions init_daemon_options()
111 {
112 location::ProgramOptions options;
113@@ -263,6 +182,8 @@
114 trap->stop();
115 });
116
117+ auto runtime = location::service::Runtime::create();
118+
119 const location::Configuration empty_provider_configuration;
120
121 std::set<location::Provider::Ptr> instantiated_providers;
122@@ -281,7 +202,7 @@
123 if (p)
124 instantiated_providers.insert(
125 location::DispatchingProvider::create(
126- Runtime::instance().to_dispatcher_functional(), p));
127+ runtime->to_dispatcher_functional(), p));
128 else
129 throw std::runtime_error("Problem instantiating provider");
130
131@@ -291,8 +212,10 @@
132 }
133 }
134
135- config.incoming->install_executor(dbus::asio::make_executor(config.incoming, Runtime::instance().service));
136- config.outgoing->install_executor(dbus::asio::make_executor(config.outgoing, Runtime::instance().service));
137+ config.incoming->install_executor(dbus::asio::make_executor(config.incoming, runtime->service()));
138+ config.outgoing->install_executor(dbus::asio::make_executor(config.outgoing, runtime->service()));
139+
140+ runtime->start();
141
142 location::service::DefaultConfiguration dc;
143
144
145=== modified file 'src/location_service/com/ubuntu/location/service/provider_daemon.cpp'
146--- src/location_service/com/ubuntu/location/service/provider_daemon.cpp 2014-10-27 21:58:16 +0000
147+++ src/location_service/com/ubuntu/location/service/provider_daemon.cpp 2015-11-25 16:02:35 +0000
148@@ -25,6 +25,7 @@
149
150 #include <com/ubuntu/location/service/configuration.h>
151 #include <com/ubuntu/location/service/program_options.h>
152+#include <com/ubuntu/location/service/runtime.h>
153
154 #include <core/dbus/asio/executor.h>
155
156@@ -65,7 +66,6 @@
157 location::service::ProviderDaemon::Configuration result;
158
159 result.connection = factory(mutable_daemon_options().bus());
160- result.connection->install_executor(core::dbus::asio::make_executor(result.connection));
161
162 auto service = core::dbus::Service::add_service(
163 result.connection,
164@@ -108,7 +108,7 @@
165 return result;
166 }
167
168-int location::service::ProviderDaemon::main(const location::service::ProviderDaemon::Configuration& configuration)
169+int location::service::ProviderDaemon::main(const location::service::ProviderDaemon::Configuration& config)
170 {
171 auto trap = core::posix::trap_signals_for_all_subsequent_threads(
172 {
173@@ -121,27 +121,22 @@
174 trap->stop();
175 });
176
177- std::thread worker
178- {
179- [configuration]()
180- {
181- configuration.connection->run();
182- }
183- };
184+ auto runtime = location::service::Runtime::create(1);
185+
186+ config.connection->install_executor(core::dbus::asio::make_executor(config.connection, runtime->service()));
187
188 auto skeleton = location::providers::remote::skeleton::create_with_configuration(location::providers::remote::skeleton::Configuration
189 {
190- configuration.object,
191- configuration.connection,
192- configuration.provider
193+ config.object,
194+ config.connection,
195+ config.provider
196 });
197
198+ runtime->start();
199+
200 trap->run();
201
202- configuration.connection->stop();
203-
204- if (worker.joinable())
205- worker.join();
206+ config.connection->stop();
207
208 return EXIT_SUCCESS;
209 }
210
211=== added file 'src/location_service/com/ubuntu/location/service/runtime.cpp'
212--- src/location_service/com/ubuntu/location/service/runtime.cpp 1970-01-01 00:00:00 +0000
213+++ src/location_service/com/ubuntu/location/service/runtime.cpp 2015-11-25 16:02:35 +0000
214@@ -0,0 +1,109 @@
215+/*
216+ * Copyright © 2015 Canonical Ltd.
217+ *
218+ * This program is free software: you can redistribute it and/or modify it
219+ * under the terms of the GNU Lesser General Public License version 3,
220+ * as published by the Free Software Foundation.
221+ *
222+ * This program is distributed in the hope that it will be useful,
223+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
224+ * MERCHANTABILITY or FITNESS FOR A PARTIlocationAR PURPOSE. See the
225+ * GNU Lesser General Public License for more details.
226+ *
227+ * You should have received a copy of the GNU Lesser General Public License
228+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
229+ *
230+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
231+ */
232+#include <com/ubuntu/location/service/runtime.h>
233+
234+#include <com/ubuntu/location/logging.h>
235+
236+namespace culs = com::ubuntu::location::service;
237+
238+namespace
239+{
240+// exception_safe_run runs service, catching all exceptions and
241+// restarting operation until an explicit shutdown has been requested.
242+//
243+// TODO(tvoss): Catching all exceptions is risky as they might signal unrecoverable
244+// errors. We should enable calling code to decide whether an exception should be considered
245+// fatal or not.
246+void exception_safe_run(boost::asio::io_service& service)
247+{
248+ while (true)
249+ {
250+ try
251+ {
252+ service.run();
253+ // a clean return from run only happens in case of
254+ // stop() being called (we are keeping the service alive with
255+ // a service::work instance).
256+ break;
257+ }
258+ catch (const std::exception& e)
259+ {
260+ LOG(WARNING) << e.what();
261+ }
262+ catch (...)
263+ {
264+ LOG(WARNING) << "Unknown exception caught while executing boost::asio::io_service";
265+ }
266+ }
267+}
268+}
269+
270+std::shared_ptr<culs::Runtime> culs::Runtime::create(std::uint32_t pool_size)
271+{
272+ return std::shared_ptr<culs::Runtime>(new culs::Runtime(pool_size));
273+}
274+
275+culs::Runtime::Runtime(std::uint32_t pool_size)
276+ : pool_size_{pool_size},
277+ service_{pool_size_},
278+ strand_{service_},
279+ keep_alive_{service_}
280+{
281+}
282+
283+culs::Runtime::~Runtime()
284+{
285+ try
286+ {
287+ stop();
288+ } catch(...)
289+ {
290+ // Dropping all exceptions to satisfy the nothrow guarantee.
291+ }
292+}
293+
294+void culs::Runtime::start()
295+{
296+ for (unsigned int i = 0; i < pool_size_; i++)
297+ workers_.push_back(std::thread{exception_safe_run, std::ref(service_)});
298+}
299+
300+void culs::Runtime::stop()
301+{
302+ service_.stop();
303+
304+ for (auto& worker : workers_)
305+ if (worker.joinable())
306+ worker.join();
307+}
308+
309+std::function<void(std::function<void()>)> culs::Runtime::to_dispatcher_functional()
310+{
311+ // We have to make sure that we stay alive for as long as
312+ // calling code requires the dispatcher to work.
313+ auto sp = shared_from_this();
314+ return [sp](std::function<void()> task)
315+ {
316+ sp->strand_.post(task);
317+ };
318+}
319+
320+boost::asio::io_service& culs::Runtime::service()
321+{
322+ return service_;
323+}
324
325=== added file 'src/location_service/com/ubuntu/location/service/runtime.h'
326--- src/location_service/com/ubuntu/location/service/runtime.h 1970-01-01 00:00:00 +0000
327+++ src/location_service/com/ubuntu/location/service/runtime.h 2015-11-25 16:02:35 +0000
328@@ -0,0 +1,90 @@
329+/*
330+ * Copyright © 2015 Canonical Ltd.
331+ *
332+ * This program is free software: you can redistribute it and/or modify it
333+ * under the terms of the GNU Lesser General Public License version 3,
334+ * as published by the Free Software Foundation.
335+ *
336+ * This program is distributed in the hope that it will be useful,
337+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
338+ * MERCHANTABILITY or FITNESS FOR A PARTIlocationAR PURPOSE. See the
339+ * GNU Lesser General Public License for more details.
340+ *
341+ * You should have received a copy of the GNU Lesser General Public License
342+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
343+ *
344+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
345+ */
346+#ifndef COM_UBUNTU_LOCATION_SERVICE_RUNTIME_H_
347+#define COM_UBUNTU_LOCATION_SERVICE_RUNTIME_H_
348+
349+#include <boost/asio.hpp>
350+
351+#include <functional>
352+#include <memory>
353+#include <thread>
354+#include <vector>
355+
356+#include <cstdint>
357+
358+namespace com
359+{
360+namespace ubuntu
361+{
362+namespace location
363+{
364+namespace service
365+{
366+// We bundle our "global" runtime dependencies here, specifically
367+// a dispatcher to decouple multiple in-process providers from one
368+// another , forcing execution to a well known set of threads.
369+class Runtime : public std::enable_shared_from_this<Runtime>
370+{
371+public:
372+ // Our default concurrency setup.
373+ static constexpr const std::uint32_t worker_threads = 2;
374+
375+ // create returns a Runtime instance with pool_size worker threads
376+ // executing the underlying service.
377+ static std::shared_ptr<Runtime> create(std::uint32_t pool_size = worker_threads);
378+
379+ Runtime(const Runtime&) = delete;
380+ Runtime(Runtime&&) = delete;
381+ // Tears down the runtime, stopping all worker threads.
382+ ~Runtime() noexcept(true);
383+ Runtime& operator=(const Runtime&) = delete;
384+ Runtime& operator=(Runtime&&) = delete;
385+
386+ // start executes the underlying io_service on a thread pool with
387+ // the size configured at creation time.
388+ void start();
389+
390+ // stop cleanly shuts down a Runtime instance,
391+ // joining all worker threads.
392+ void stop();
393+
394+ // to_dispatcher_functional returns a function for integration
395+ // with components that expect a dispatcher for operation.
396+ std::function<void(std::function<void()>)> to_dispatcher_functional();
397+
398+ // service returns the underlying boost::asio::io_service that is executed
399+ // by the Runtime.
400+ boost::asio::io_service& service();
401+
402+private:
403+ // Runtime constructs a new instance, firing up pool_size
404+ // worker threads.
405+ Runtime(std::uint32_t pool_size);
406+
407+ std::uint32_t pool_size_;
408+ boost::asio::io_service service_;
409+ boost::asio::io_service::strand strand_;
410+ boost::asio::io_service::work keep_alive_;
411+ std::vector<std::thread> workers_;
412+};
413+}
414+}
415+}
416+}
417+
418+#endif // COM_UBUNTU_LOCATION_SERVICE_RUNTIME_H_
419
420=== modified file 'tests/CMakeLists.txt'
421--- tests/CMakeLists.txt 2015-04-23 17:04:09 +0000
422+++ tests/CMakeLists.txt 2015-11-25 16:02:35 +0000
423@@ -92,6 +92,7 @@
424 LOCATION_SERVICE_ADD_TEST(provider_test provider_test.cpp)
425 LOCATION_SERVICE_ADD_TEST(wgs84_test wgs84_test.cpp)
426 LOCATION_SERVICE_ADD_TEST(trust_store_permission_manager_test trust_store_permission_manager_test.cpp)
427+LOCATION_SERVICE_ADD_TEST(runtime_test runtime_test.cpp)
428
429 # Provider-specific test-cases go here.
430 if (LOCATION_SERVICE_ENABLE_GPS_PROVIDER)
431
432=== modified file 'tests/acceptance_tests.cpp'
433--- tests/acceptance_tests.cpp 2015-11-25 16:02:34 +0000
434+++ tests/acceptance_tests.cpp 2015-11-25 16:02:35 +0000
435@@ -38,12 +38,14 @@
436 #include <com/ubuntu/location/service/stub.h>
437
438 #include <core/dbus/announcer.h>
439+#include <core/dbus/bus.h>
440 #include <core/dbus/fixture.h>
441 #include <core/dbus/resolver.h>
442
443 #include <core/dbus/asio/executor.h>
444
445 #include <core/posix/signal.h>
446+#include <core/posix/this_process.h>
447
448 #include <core/testing/cross_process_sync.h>
449 #include <core/testing/fork_and_run.h>
450@@ -803,8 +805,8 @@
451 };
452
453 cul::service::Daemon::Configuration config;
454- config.incoming = session_bus();
455- config.outgoing = session_bus();
456+ config.incoming = std::make_shared<core::dbus::Bus>(core::posix::this_process::env::get_or_throw("DBUS_SESSION_BUS_ADDRESS"));
457+ config.outgoing = std::make_shared<core::dbus::Bus>(core::posix::this_process::env::get_or_throw("DBUS_SESSION_BUS_ADDRESS"));
458 config.is_testing_enabled = false;
459 config.providers =
460 {
461
462=== modified file 'tests/position_test.cpp'
463--- tests/position_test.cpp 2014-06-20 07:40:34 +0000
464+++ tests/position_test.cpp 2015-11-25 16:02:35 +0000
465@@ -40,7 +40,7 @@
466 cul::wgs84::Latitude{},
467 cul::wgs84::Longitude{},
468 cul::wgs84::Altitude{}};
469- EXPECT_TRUE(p.altitude);
470+ EXPECT_TRUE(p.altitude ? true : false);
471 }
472
473 #include <com/ubuntu/location/codec.h>
474
475=== added file 'tests/runtime_test.cpp'
476--- tests/runtime_test.cpp 1970-01-01 00:00:00 +0000
477+++ tests/runtime_test.cpp 2015-11-25 16:02:35 +0000
478@@ -0,0 +1,118 @@
479+/*
480+ * Copyright © 2015Canonical Ltd.
481+ *
482+ * This program is free software: you can redistribute it and/or modify it
483+ * under the terms of the GNU Lesser General Public License version 3,
484+ * as published by the Free Software Foundation.
485+ *
486+ * This program is distributed in the hope that it will be useful,
487+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
488+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
489+ * GNU Lesser General Public License for more details.
490+ *
491+ * You should have received a copy of the GNU Lesser General Public License
492+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
493+ *
494+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
495+ */
496+#include <com/ubuntu/location/service/runtime.h>
497+
498+#include <gtest/gtest.h>
499+
500+#include <condition_variable>
501+#include <thread>
502+
503+namespace culs = com::ubuntu::location::service;
504+
505+TEST(Runtime, cleanly_shuts_down_threads)
506+{
507+ culs::Runtime::create();
508+}
509+
510+TEST(Runtime, executes_service)
511+{
512+ std::mutex m;
513+ std::unique_lock<std::mutex> ul{m};
514+ std::condition_variable wc;
515+
516+ bool signaled = false;
517+
518+ auto rt = culs::Runtime::create();
519+ rt->start();
520+ boost::asio::deadline_timer timer{rt->service(), boost::posix_time::milliseconds(500)};
521+ timer.async_wait([&wc, &signaled](const boost::system::error_code&)
522+ {
523+ signaled = true;
524+ wc.notify_all();
525+ });
526+
527+ auto result = wc.wait_for(ul, std::chrono::seconds{1}, [&signaled]() { return signaled; });
528+ EXPECT_TRUE(result);
529+}
530+
531+TEST(Runtime, catches_exceptions_thrown_from_handlers)
532+{
533+ std::mutex m;
534+ std::unique_lock<std::mutex> ul{m};
535+ std::condition_variable wc;
536+
537+ bool signaled = false;
538+
539+ auto rt = culs::Runtime::create();
540+ rt->start();
541+ boost::asio::deadline_timer fast{rt->service(), boost::posix_time::milliseconds(100)};
542+ fast.async_wait([](const boost::system::error_code&)
543+ {
544+ throw std::runtime_error{"Should not propagate"};
545+ });
546+
547+ boost::asio::deadline_timer slow{rt->service(), boost::posix_time::milliseconds(500)};
548+ slow.async_wait([&wc, &signaled](const boost::system::error_code&)
549+ {
550+ signaled = true;
551+ wc.notify_all();
552+ });
553+
554+ auto result = wc.wait_for(ul, std::chrono::seconds{1}, [&signaled]() { return signaled; });
555+ EXPECT_TRUE(result);
556+}
557+
558+// sets_up_pool_of_threads ensures that the pool size
559+// passed to the Runtime on construction is honored. The idea is simple:
560+// We set up two deadline timers, fast and slow. fast fires before slow,
561+// with fast blocking in a wait on a common condition_variable. When slow
562+// fires, it notifies the condition variable, thereby unblocking the handler of fast,
563+// enabling clean shutdown without errors and timeouts. This only works if the
564+// pool contains at least 2 threads. Otherwise, the handler of slow would not be executed
565+// until the handler of fast times out in the wait, marking the test as failed.
566+TEST(Runtime, sets_up_pool_of_threads)
567+{
568+ struct State
569+ {
570+ bool signaled{false};
571+ std::mutex m;
572+ std::condition_variable wc;
573+ };
574+
575+ auto state = std::make_shared<State>();
576+
577+ auto rt = culs::Runtime::create(2);
578+ rt->start();
579+ boost::asio::deadline_timer fast{rt->service(), boost::posix_time::milliseconds(100)};
580+ fast.async_wait([state](const boost::system::error_code&)
581+ {
582+ std::unique_lock<std::mutex> ul{state->m};
583+ EXPECT_TRUE(state->wc.wait_for(ul, std::chrono::seconds{1}, [state]() { return state->signaled; }));
584+ });
585+
586+ boost::asio::deadline_timer slow{rt->service(), boost::posix_time::milliseconds(500)};
587+ slow.async_wait([state](const boost::system::error_code&)
588+ {
589+ state->signaled = true;
590+ state->wc.notify_all();
591+ });
592+
593+ std::unique_lock<std::mutex> ul{state->m};
594+ auto result = state->wc.wait_for(ul, std::chrono::seconds{1}, [state]() { return state->signaled; });
595+ EXPECT_TRUE(result);
596+}

Subscribers

People subscribed via source and target branches