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

Proposed by Thomas Voß
Status: Merged
Approved by: Alberto Mardegan
Approved revision: 138
Merged at revision: 205
Proposed branch: lp:~thomas-voss/location-service/factor-out-runtime
Merge into: lp:location-service/15.04
Diff against target: 613 lines (+351/-72)
9 files modified
src/location_service/com/ubuntu/location/CMakeLists.txt (+1/-0)
src/location_service/com/ubuntu/location/service/daemon.cpp (+10/-43)
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 (+10/-12)
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
Alberto Mardegan (community) Approve
Review via email: mp+278674@code.launchpad.net

This proposal supersedes a proposal from 2015-11-19.

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.
138. By Thomas Voß

Remove dependency on robustify-...

Revision history for this message
Alberto Mardegan (mardy) wrote :

LGTM!

review: Approve

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

Subscribers

People subscribed via source and target branches