Mir

Merge lp:~afrantzis/mir/fix-1189770 into lp:mir

Proposed by Alexandros Frantzis
Status: Merged
Approved by: Cemil Azizoglu
Approved revision: no longer in the source branch.
Merged at revision: 1605
Proposed branch: lp:~afrantzis/mir/fix-1189770
Merge into: lp:mir
Diff against target: 405 lines (+197/-38)
7 files modified
3rd_party/android-deps/std/Thread.h (+14/-2)
include/server/mir/run_mir.h (+2/-0)
include/test/mir_test/fake_event_hub.h (+3/-0)
src/server/compositor/multi_threaded_compositor.cpp (+11/-0)
src/server/run_mir.cpp (+22/-0)
tests/acceptance-tests/test_server_shutdown.cpp (+135/-36)
tests/mir_test_doubles/fake_event_hub.cpp (+10/-0)
To merge this branch: bzr merge lp:~afrantzis/mir/fix-1189770
Reviewer Review Type Date Requested Status
Cemil Azizoglu (community) Approve
Alan Griffiths Approve
PS Jenkins bot (community) continuous-integration Approve
Daniel van Vugt Needs Fixing
Alberto Aguirre (community) Approve
Kevin DuBois (community) Approve
Robert Carr (community) Approve
Review via email: mp+217652@code.launchpad.net

Commit message

server: Gracefully handle exceptions thrown from server threads

Description of the change

server: Gracefully handle exceptions thrown from server threads

This MP solves issues with incomplete server shutdown in case an exception is thrown from a server component thread. It achieves this by catching and storing exceptions that are still unhandled at thread boundaries, then terminating the server gracefully and finally rethrowing the unhandled exception from the mir::run_mir() level (because we still need to fail, e.g., for apport to catch the problem). This MP adds graceful termination for the input and compositor threads.

I am not completely happy with the indirect dependence of 3rd-party android Thread class on mir functionality, but I guess it will do for now.

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

LGTM

review: Approve
Revision history for this message
Kevin DuBois (kdub) wrote :

okay, although it does seem like a future maintainer/improver would have to figure out the terminate_with_current_exception linking. "I guess it will do for now." is how I felt when searching for a different way too, so +1.

review: Approve
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

Perhaps we can explore a custom terminate_handler (std::set_terminate)? But that probably won't help much...

Otherwise...this....will...do for now.

review: Approve
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Unfortunately this is a regression on bug 1237332, and worse because core files are not longer produced at all.

Not regressing on bug 1237332 is more important than resolving bug 1189770 right now. The ability to get crash dumps from users is critical. Or else you can never fully see what's going wrong and never fix it.

review: Needs Fixing
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> Unfortunately this is a regression on bug 1237332, and worse because core files are not longer produced at all.

We are rethrowing the exception from mir::run_mir() so if that's not caught we will abort() and get a core.

The way I see it, it makes sense to rethrow the exception from mir::run_mir(), since we can gracefully terminate instead, and we should allow our users to do the same. The current situation where we abort() in such cases is just a side effect of an implementation detail (i.e. that we use threads).

I agree that the core file produced (if produced) in such situations is not useful since we are capturing state after teardown. We could use "gcore" to capture the state in mir::terminate_with_exception() and somehow pass that information to interested parties. One way would be to read the command or file pattern from /proc/sys/kernel/core_pattern, and send the gcore output to that. Unfortunately, there are some problems there: we may need root privileges to write or pipe the gcore output to the core_pattern target and also we can't find some core_pattern parameters from userspace (like %P, although this only seem to matter when running in a container).

Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> Unfortunately, there are some problems there: we may need root privileges to write or pipe the
> gcore output to the core_pattern target

Talked to pitti, and at least for the apport case this is fixable.

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

I think there are too many serious problems with rethrowing. You lose the original stack trace and therefore critically lose the memory contents and access to the variables leading to the exception. So you can't debug your cores and bugs don't get fixed. And too many users continue to indefinitely experience the same crashes as we experienced in the public testing of XMir.

Plus, all theory aside, when I manually tested this branch no core files came out of crashes. And I think it's naive to assume you can manually craft a core file (of yourself) as reliably and useful as the kernel will produce for you.

But the real solution could be very simple... If we carefully convert unrecoverable fatal exceptions into clean abort()'s with clean core and stack dumping (bug 1285084), then a change like this one could be fine. But we're not there yet.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

At the heart of the above disagreement is the idea that there can be "unrecoverable fatal exceptions" - that is just bad design. (Even Java's questionable "Throwable" hierarchy makes unrecoverable "Error"s something different to "Exception"s.)

I think the requirement for an orderly shutdown and "correct" behavior of the executable beats that of having a core from the throw site. But it is close and I'd very much like to have both.

At yesterday's "standup" we discussed introduced a "core_and_throw" option to some throw sites - but I don't see that as a prerequisite for fixing lp:1189770.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

134 +std::exception_ptr termination_exception;

AFAICS this can be accessed on multiple threads and therefore needs synchronization.

review: Needs Fixing
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Not being able to analyse crashes is an absolutely blocking issue because it prevents resolution of critical bugs. We must ensure we don't go backwards in this area.

"core and throw" is a naive idea. I've seen many attempts to do that on many platforms over the years but nothing ever beats letting the kernel produce clean, complete and accurate cores for you. Most attempts to dump cores of self or stack trace of self make accurate debugging more difficult. You should not do it.

This proposal could be OK only after fatal errors have been converted to abort/raise rather than exceptions.

review: Needs Fixing
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> 134 +std::exception_ptr termination_exception;
>
> AFAICS this can be accessed on multiple threads and therefore needs
> synchronization.

Fixed, and also changed it so that only the first terminate_with_current_exception request is handled. The rationale is that we can get multiple requests for two reasons:

1. Code in two or more threads throwing at the same time (possibly because of the same underlying reason).
2. Code in one thread throwing, then during shutdown another thread throws.

In case (1) it doesn't matter which exception we catch (which comes first depends on timing anyway). In case (2) we want to catch the original exception more than we want to catch the shutdown exception.

Ideally we would want to record all, and that's possible by storing all exceptions and throwing an exception with all of the original exceptions nested, but that's not critical, so leaving it for the future, unless the team prefers to have it now.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

141 +std::exception_ptr termination_exception;
142 +std::mutex termination_exception_mutex;

termination_exception is accessed in run_mir() without locking the mutex.

review: Needs Fixing
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> termination_exception is accessed in run_mir() without locking the mutex.

I was thinking that our internal threads would have either not started or finished in the two points we access termination_exception in run_mir(). I guess an external user could be using mir::terminate_with_exception(), although I can't think of a valid use case for it. In any case, fixed.

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

LGTM

review: Approve
Revision history for this message
Cemil Azizoglu (cemil-azizoglu) wrote :

> I think the requirement for an orderly shutdown and "correct" behavior of the
> executable beats that of having a core from the throw site. But it is close
> and I'd very much like to have both.

+1 indeed!

IMHO for a user not being able to recover her display is several orders of magnitude worse than a frustrated development team having a hard time root-causing bugs due to lack of sane core dumps :-).

But let's not frustrate ourselves either. Even though programmer-assembled dumps may be difficult, we should put in that functionality. Soon after this one gets merged.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '3rd_party/android-deps/std/Thread.h'
--- 3rd_party/android-deps/std/Thread.h 2013-05-06 22:22:39 +0000
+++ 3rd_party/android-deps/std/Thread.h 2014-05-02 15:53:48 +0000
@@ -32,6 +32,11 @@
32#include <pthread.h>32#include <pthread.h>
33#endif33#endif
3434
35namespace mir
36{
37void terminate_with_current_exception();
38}
39
35namespace mir_input40namespace mir_input
36{41{
37class Thread : virtual public RefBase42class Thread : virtual public RefBase
@@ -56,8 +61,15 @@
5661
57 thread = std::thread([this]() -> void62 thread = std::thread([this]() -> void
58 {63 {
59 if (auto result = readyToRun()) status.store(result);64 try
60 else while (!exitPending() && threadLoop());65 {
66 if (auto result = readyToRun()) status.store(result);
67 else while (!exitPending() && threadLoop());
68 }
69 catch (...)
70 {
71 mir::terminate_with_current_exception();
72 }
61 });73 });
6274
63#ifdef HAVE_PTHREADS75#ifdef HAVE_PTHREADS
6476
=== modified file 'include/server/mir/run_mir.h'
--- include/server/mir/run_mir.h 2013-04-25 16:52:27 +0000
+++ include/server/mir/run_mir.h 2014-05-02 15:53:48 +0000
@@ -39,6 +39,8 @@
39 std::function<void(DisplayServer&)> init);39 std::function<void(DisplayServer&)> init);
4040
41void report_exception(std::ostream& out);41void report_exception(std::ostream& out);
42
43void terminate_with_current_exception();
42}44}
4345
4446
4547
=== modified file 'include/test/mir_test/fake_event_hub.h'
--- include/test/mir_test/fake_event_hub.h 2013-10-01 17:29:49 +0000
+++ include/test/mir_test/fake_event_hub.h 2014-05-02 15:53:48 +0000
@@ -142,6 +142,8 @@
142 FakeDevice* getDevice(int32_t deviceId);142 FakeDevice* getDevice(int32_t deviceId);
143 size_t eventsQueueSize() const;143 size_t eventsQueueSize() const;
144144
145 void throw_exception_in_next_get_events();
146
145 // list of RawEvents available for consumption via getEvents147 // list of RawEvents available for consumption via getEvents
146 std::mutex guard;148 std::mutex guard;
147 std::list<droidinput::RawEvent> events_available;149 std::list<droidinput::RawEvent> events_available;
@@ -155,6 +157,7 @@
155157
156private:158private:
157 const KeyInfo* getKey(const FakeDevice* device, int32_t scanCode, int32_t usageCode) const;159 const KeyInfo* getKey(const FakeDevice* device, int32_t scanCode, int32_t usageCode) const;
160 bool throw_in_get_events = false;
158161
159};162};
160}163}
161164
=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
--- src/server/compositor/multi_threaded_compositor.cpp 2014-04-30 04:46:52 +0000
+++ src/server/compositor/multi_threaded_compositor.cpp 2014-05-02 15:53:48 +0000
@@ -26,6 +26,7 @@
26#include "mir/scene/legacy_scene_change_notification.h"26#include "mir/scene/legacy_scene_change_notification.h"
27#include "mir/scene/surface_observer.h"27#include "mir/scene/surface_observer.h"
28#include "mir/scene/surface.h"28#include "mir/scene/surface.h"
29#include "mir/run_mir.h"
2930
30#include <thread>31#include <thread>
31#include <condition_variable>32#include <condition_variable>
@@ -161,6 +162,7 @@
161 }162 }
162163
163 void operator()() noexcept // noexcept is important! (LP: #1237332)164 void operator()() noexcept // noexcept is important! (LP: #1237332)
165 try
164 {166 {
165 /*167 /*
166 * Make the buffer the current rendering target, and release168 * Make the buffer the current rendering target, and release
@@ -180,6 +182,10 @@
180182
181 run_compositing_loop([&] { return display_buffer_compositor->composite();});183 run_compositing_loop([&] { return display_buffer_compositor->composite();});
182 }184 }
185 catch (...)
186 {
187 mir::terminate_with_current_exception();
188 }
183189
184private:190private:
185 std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;191 std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
@@ -196,6 +202,7 @@
196 }202 }
197203
198 void operator()() noexcept // noexcept is important! (LP: #1237332)204 void operator()() noexcept // noexcept is important! (LP: #1237332)
205 try
199 {206 {
200 run_compositing_loop(207 run_compositing_loop(
201 [this]208 [this]
@@ -215,6 +222,10 @@
215 return false;222 return false;
216 });223 });
217 }224 }
225 catch (...)
226 {
227 mir::terminate_with_current_exception();
228 }
218229
219private:230private:
220 void wait_until_next_fake_vsync()231 void wait_until_next_fake_vsync()
221232
=== modified file 'src/server/run_mir.cpp'
--- src/server/run_mir.cpp 2013-10-21 08:17:25 +0000
+++ src/server/run_mir.cpp 2014-05-02 15:53:48 +0000
@@ -23,6 +23,8 @@
23#include "mir/frontend/connector.h"23#include "mir/frontend/connector.h"
24#include "mir/raii.h"24#include "mir/raii.h"
2525
26#include <exception>
27#include <mutex>
26#include <csignal>28#include <csignal>
27#include <cstdlib>29#include <cstdlib>
28#include <cassert>30#include <cassert>
@@ -32,6 +34,8 @@
32auto const intercepted = { SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS };34auto const intercepted = { SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS };
3335
34std::weak_ptr<mir::frontend::Connector> weak_connector;36std::weak_ptr<mir::frontend::Connector> weak_connector;
37std::exception_ptr termination_exception;
38std::mutex termination_exception_mutex;
3539
36extern "C" void delete_endpoint()40extern "C" void delete_endpoint()
37{41{
@@ -58,6 +62,10 @@
58void mir::run_mir(ServerConfiguration& config, std::function<void(DisplayServer&)> init)62void mir::run_mir(ServerConfiguration& config, std::function<void(DisplayServer&)> init)
59{63{
60 DisplayServer* server_ptr{nullptr};64 DisplayServer* server_ptr{nullptr};
65 {
66 std::lock_guard<std::mutex> lock{termination_exception_mutex};
67 termination_exception = nullptr;
68 }
61 auto main_loop = config.the_main_loop();69 auto main_loop = config.the_main_loop();
6270
63 main_loop->register_signal_handler(71 main_loop->register_signal_handler(
@@ -88,4 +96,18 @@
8896
89 init(server);97 init(server);
90 server.run();98 server.run();
99
100 std::lock_guard<std::mutex> lock{termination_exception_mutex};
101 if (termination_exception)
102 std::rethrow_exception(termination_exception);
103}
104
105void mir::terminate_with_current_exception()
106{
107 std::lock_guard<std::mutex> lock{termination_exception_mutex};
108 if (!termination_exception)
109 {
110 termination_exception = std::current_exception();
111 raise(SIGTERM);
112 }
91}113}
92114
=== modified file 'tests/acceptance-tests/test_server_shutdown.cpp'
--- tests/acceptance-tests/test_server_shutdown.cpp 2014-03-06 06:05:17 +0000
+++ tests/acceptance-tests/test_server_shutdown.cpp 2014-05-02 15:53:48 +0000
@@ -19,10 +19,14 @@
19#include "mir_toolkit/mir_client_library.h"19#include "mir_toolkit/mir_client_library.h"
20#include "mir/compositor/renderer.h"20#include "mir/compositor/renderer.h"
21#include "mir/compositor/renderer_factory.h"21#include "mir/compositor/renderer_factory.h"
22#include "mir/compositor/display_buffer_compositor.h"
23#include "mir/compositor/display_buffer_compositor_factory.h"
22#include "mir/input/composite_event_filter.h"24#include "mir/input/composite_event_filter.h"
25#include "mir/run_mir.h"
2326
24#include "mir_test_framework/display_server_test_fixture.h"27#include "mir_test_framework/display_server_test_fixture.h"
25#include "mir_test/fake_event_hub_input_configuration.h"28#include "mir_test/fake_event_hub_input_configuration.h"
29#include "mir_test/fake_event_hub.h"
26#include "mir_test_framework/cross_process_sync.h"30#include "mir_test_framework/cross_process_sync.h"
27#include "mir_test_doubles/stub_renderer.h"31#include "mir_test_doubles/stub_renderer.h"
2832
@@ -55,6 +59,25 @@
55 }59 }
56};60};
5761
62class ExceptionThrowingDisplayBufferCompositorFactory : public mc::DisplayBufferCompositorFactory
63{
64public:
65 std::unique_ptr<mc::DisplayBufferCompositor>
66 create_compositor_for(mg::DisplayBuffer&) override
67 {
68 struct ExceptionThrowingDisplayBufferCompositor : mc::DisplayBufferCompositor
69 {
70 bool composite() override
71 {
72 throw std::runtime_error("ExceptionThrowingDisplayBufferCompositor");
73 }
74 };
75
76 return std::unique_ptr<mc::DisplayBufferCompositor>(
77 new ExceptionThrowingDisplayBufferCompositor{});
78 }
79};
80
58void null_surface_callback(MirSurface*, void*)81void null_surface_callback(MirSurface*, void*)
59{82{
60}83}
@@ -94,6 +117,46 @@
94 std::string const flag_file;117 std::string const flag_file;
95};118};
96119
120struct FakeEventHubServerConfig : TestingServerConfiguration
121{
122 std::shared_ptr<mi::InputConfiguration> the_input_configuration() override
123 {
124 if (!input_configuration)
125 {
126 input_configuration =
127 std::make_shared<mtd::FakeEventHubInputConfiguration>(
128 the_composite_event_filter(),
129 the_input_region(),
130 std::shared_ptr<mi::CursorListener>(),
131 the_input_report());
132 }
133
134 return input_configuration;
135 }
136
137 std::shared_ptr<mi::InputManager> the_input_manager() override
138 {
139 return DefaultServerConfiguration::the_input_manager();
140 }
141
142 std::shared_ptr<mir::shell::InputTargeter> the_input_targeter() override
143 {
144 return DefaultServerConfiguration::the_input_targeter();
145 }
146 std::shared_ptr<mir::scene::InputRegistrar> the_input_registrar() override
147 {
148 return DefaultServerConfiguration::the_input_registrar();
149 }
150
151 mia::FakeEventHub* the_fake_event_hub()
152 {
153 the_input_configuration();
154 return input_configuration->the_fake_event_hub();
155 }
156
157 std::shared_ptr<mtd::FakeEventHubInputConfiguration> input_configuration;
158};
159
97}160}
98161
99using ServerShutdown = BespokeDisplayServerTestFixture;162using ServerShutdown = BespokeDisplayServerTestFixture;
@@ -194,42 +257,7 @@
194 Flag resources_freed_success{"resources_free_success_7e9c69fc.tmp"};257 Flag resources_freed_success{"resources_free_success_7e9c69fc.tmp"};
195 Flag resources_freed_failure{"resources_free_failure_7e9c69fc.tmp"};258 Flag resources_freed_failure{"resources_free_failure_7e9c69fc.tmp"};
196259
197 /* Use the real input manager, but with a fake event hub */260 auto server_config = std::make_shared<FakeEventHubServerConfig>();
198 struct ServerConfig : TestingServerConfiguration
199 {
200 std::shared_ptr<mi::InputConfiguration> the_input_configuration() override
201 {
202 if (!input_configuration)
203 {
204 input_configuration =
205 std::make_shared<mtd::FakeEventHubInputConfiguration>(
206 the_composite_event_filter(),
207 the_input_region(),
208 std::shared_ptr<mi::CursorListener>(),
209 the_input_report());
210 }
211
212 return input_configuration;
213 }
214
215 std::shared_ptr<mi::InputManager> the_input_manager() override
216 {
217 return DefaultServerConfiguration::the_input_manager();
218 }
219
220 std::shared_ptr<mir::shell::InputTargeter> the_input_targeter() override
221 {
222 return DefaultServerConfiguration::the_input_targeter();
223 }
224 std::shared_ptr<mir::scene::InputRegistrar> the_input_registrar() override
225 {
226 return DefaultServerConfiguration::the_input_registrar();
227 }
228
229 std::shared_ptr<mi::InputConfiguration> input_configuration;
230 };
231
232 auto server_config = std::make_shared<ServerConfig>();
233 launch_server_process(*server_config);261 launch_server_process(*server_config);
234262
235 struct ClientConfig : TestingClientConfiguration263 struct ClientConfig : TestingClientConfiguration
@@ -427,3 +455,74 @@
427INSTANTIATE_TEST_CASE_P(ServerShutdown,455INSTANTIATE_TEST_CASE_P(ServerShutdown,
428 OnSignal,456 OnSignal,
429 ::testing::Values(SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS));457 ::testing::Values(SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS));
458
459TEST(ServerShutdownWithThreadException,
460 server_releases_resources_on_abnormal_input_thread_termination)
461{
462 auto server_config = std::make_shared<FakeEventHubServerConfig>();
463 auto fake_event_hub = server_config->the_fake_event_hub();
464
465 std::thread server{
466 [&]
467 {
468 EXPECT_THROW(
469 mir::run_mir(*server_config, [](mir::DisplayServer&){}),
470 std::runtime_error);
471 }};
472
473 fake_event_hub->throw_exception_in_next_get_events();
474 server.join();
475
476 std::weak_ptr<mir::graphics::Display> display = server_config->the_display();
477 std::weak_ptr<mir::compositor::Compositor> compositor = server_config->the_compositor();
478 std::weak_ptr<mir::frontend::Connector> connector = server_config->the_connector();
479 std::weak_ptr<mir::input::InputManager> input_manager = server_config->the_input_manager();
480
481 server_config.reset();
482
483 EXPECT_EQ(0, display.use_count());
484 EXPECT_EQ(0, compositor.use_count());
485 EXPECT_EQ(0, connector.use_count());
486 EXPECT_EQ(0, input_manager.use_count());
487}
488
489TEST(ServerShutdownWithThreadException,
490 server_releases_resources_on_abnormal_compositor_thread_termination)
491{
492 struct ServerConfig : TestingServerConfiguration
493 {
494 std::shared_ptr<mc::DisplayBufferCompositorFactory>
495 the_display_buffer_compositor_factory() override
496 {
497 return display_buffer_compositor_factory(
498 [this]()
499 {
500 return std::make_shared<ExceptionThrowingDisplayBufferCompositorFactory>();
501 });
502 }
503 };
504
505 auto server_config = std::make_shared<ServerConfig>();
506
507 std::thread server{
508 [&]
509 {
510 EXPECT_THROW(
511 mir::run_mir(*server_config, [](mir::DisplayServer&){}),
512 std::runtime_error);
513 }};
514
515 server.join();
516
517 std::weak_ptr<mir::graphics::Display> display = server_config->the_display();
518 std::weak_ptr<mir::compositor::Compositor> compositor = server_config->the_compositor();
519 std::weak_ptr<mir::frontend::Connector> connector = server_config->the_connector();
520 std::weak_ptr<mir::input::InputManager> input_manager = server_config->the_input_manager();
521
522 server_config.reset();
523
524 EXPECT_EQ(0, display.use_count());
525 EXPECT_EQ(0, compositor.use_count());
526 EXPECT_EQ(0, connector.use_count());
527 EXPECT_EQ(0, input_manager.use_count());
528}
430529
=== modified file 'tests/mir_test_doubles/fake_event_hub.cpp'
--- tests/mir_test_doubles/fake_event_hub.cpp 2013-10-02 11:04:00 +0000
+++ tests/mir_test_doubles/fake_event_hub.cpp 2014-05-02 15:53:48 +0000
@@ -221,6 +221,10 @@
221 (void) timeoutMillis;221 (void) timeoutMillis;
222 {222 {
223 std::lock_guard<std::mutex> lg(guard);223 std::lock_guard<std::mutex> lg(guard);
224
225 if (throw_in_get_events)
226 throw std::runtime_error("FakeEventHub::getEvents() exception");
227
224 for (size_t i = 0; i < bufferSize && events_available.size() > 0; ++i)228 for (size_t i = 0; i < bufferSize && events_available.size() > 0; ++i)
225 {229 {
226 buffer[i] = events_available.front();230 buffer[i] = events_available.front();
@@ -688,3 +692,9 @@
688{692{
689 return events_available.size();693 return events_available.size();
690}694}
695
696void FakeEventHub::throw_exception_in_next_get_events()
697{
698 std::lock_guard<std::mutex> lg(guard);
699 throw_in_get_events = true;
700}

Subscribers

People subscribed via source and target branches