Merge lp:~afrantzis/mir/fix-1189770 into lp:mir
- fix-1189770
- Merge into development-branch
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 |
Related bugs: |
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
Kevin DuBois (kdub) wrote : | # |
okay, although it does seem like a future maintainer/improver would have to figure out the terminate_
Alberto Aguirre (albaguirre) wrote : | # |
Perhaps we can explore a custom terminate_handler (std::set_
Otherwise.
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.
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_
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.
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.
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.
Alan Griffiths (alan-griffiths) wrote : | # |
134 +std::exception_ptr termination_
AFAICS this can be accessed on multiple threads and therefore needs synchronization.
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.
Alexandros Frantzis (afrantzis) wrote : | # |
> 134 +std::exception_ptr termination_
>
> AFAICS this can be accessed on multiple threads and therefore needs
> synchronization.
Fixed, and also changed it so that only the first terminate_
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.
Alan Griffiths (alan-griffiths) wrote : | # |
141 +std::exception_ptr termination_
142 +std::mutex termination_
termination_
Alexandros Frantzis (afrantzis) wrote : | # |
> termination_
I was thinking that our internal threads would have either not started or finished in the two points we access termination_
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1585
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1587
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
ABORTED: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1587
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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-
Preview Diff
1 | === modified file '3rd_party/android-deps/std/Thread.h' | |||
2 | --- 3rd_party/android-deps/std/Thread.h 2013-05-06 22:22:39 +0000 | |||
3 | +++ 3rd_party/android-deps/std/Thread.h 2014-05-02 15:53:48 +0000 | |||
4 | @@ -32,6 +32,11 @@ | |||
5 | 32 | #include <pthread.h> | 32 | #include <pthread.h> |
6 | 33 | #endif | 33 | #endif |
7 | 34 | 34 | ||
8 | 35 | namespace mir | ||
9 | 36 | { | ||
10 | 37 | void terminate_with_current_exception(); | ||
11 | 38 | } | ||
12 | 39 | |||
13 | 35 | namespace mir_input | 40 | namespace mir_input |
14 | 36 | { | 41 | { |
15 | 37 | class Thread : virtual public RefBase | 42 | class Thread : virtual public RefBase |
16 | @@ -56,8 +61,15 @@ | |||
17 | 56 | 61 | ||
18 | 57 | thread = std::thread([this]() -> void | 62 | thread = std::thread([this]() -> void |
19 | 58 | { | 63 | { |
22 | 59 | if (auto result = readyToRun()) status.store(result); | 64 | try |
23 | 60 | else while (!exitPending() && threadLoop()); | 65 | { |
24 | 66 | if (auto result = readyToRun()) status.store(result); | ||
25 | 67 | else while (!exitPending() && threadLoop()); | ||
26 | 68 | } | ||
27 | 69 | catch (...) | ||
28 | 70 | { | ||
29 | 71 | mir::terminate_with_current_exception(); | ||
30 | 72 | } | ||
31 | 61 | }); | 73 | }); |
32 | 62 | 74 | ||
33 | 63 | #ifdef HAVE_PTHREADS | 75 | #ifdef HAVE_PTHREADS |
34 | 64 | 76 | ||
35 | === modified file 'include/server/mir/run_mir.h' | |||
36 | --- include/server/mir/run_mir.h 2013-04-25 16:52:27 +0000 | |||
37 | +++ include/server/mir/run_mir.h 2014-05-02 15:53:48 +0000 | |||
38 | @@ -39,6 +39,8 @@ | |||
39 | 39 | std::function<void(DisplayServer&)> init); | 39 | std::function<void(DisplayServer&)> init); |
40 | 40 | 40 | ||
41 | 41 | void report_exception(std::ostream& out); | 41 | void report_exception(std::ostream& out); |
42 | 42 | |||
43 | 43 | void terminate_with_current_exception(); | ||
44 | 42 | } | 44 | } |
45 | 43 | 45 | ||
46 | 44 | 46 | ||
47 | 45 | 47 | ||
48 | === modified file 'include/test/mir_test/fake_event_hub.h' | |||
49 | --- include/test/mir_test/fake_event_hub.h 2013-10-01 17:29:49 +0000 | |||
50 | +++ include/test/mir_test/fake_event_hub.h 2014-05-02 15:53:48 +0000 | |||
51 | @@ -142,6 +142,8 @@ | |||
52 | 142 | FakeDevice* getDevice(int32_t deviceId); | 142 | FakeDevice* getDevice(int32_t deviceId); |
53 | 143 | size_t eventsQueueSize() const; | 143 | size_t eventsQueueSize() const; |
54 | 144 | 144 | ||
55 | 145 | void throw_exception_in_next_get_events(); | ||
56 | 146 | |||
57 | 145 | // list of RawEvents available for consumption via getEvents | 147 | // list of RawEvents available for consumption via getEvents |
58 | 146 | std::mutex guard; | 148 | std::mutex guard; |
59 | 147 | std::list<droidinput::RawEvent> events_available; | 149 | std::list<droidinput::RawEvent> events_available; |
60 | @@ -155,6 +157,7 @@ | |||
61 | 155 | 157 | ||
62 | 156 | private: | 158 | private: |
63 | 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; |
64 | 160 | bool throw_in_get_events = false; | ||
65 | 158 | 161 | ||
66 | 159 | }; | 162 | }; |
67 | 160 | } | 163 | } |
68 | 161 | 164 | ||
69 | === modified file 'src/server/compositor/multi_threaded_compositor.cpp' | |||
70 | --- src/server/compositor/multi_threaded_compositor.cpp 2014-04-30 04:46:52 +0000 | |||
71 | +++ src/server/compositor/multi_threaded_compositor.cpp 2014-05-02 15:53:48 +0000 | |||
72 | @@ -26,6 +26,7 @@ | |||
73 | 26 | #include "mir/scene/legacy_scene_change_notification.h" | 26 | #include "mir/scene/legacy_scene_change_notification.h" |
74 | 27 | #include "mir/scene/surface_observer.h" | 27 | #include "mir/scene/surface_observer.h" |
75 | 28 | #include "mir/scene/surface.h" | 28 | #include "mir/scene/surface.h" |
76 | 29 | #include "mir/run_mir.h" | ||
77 | 29 | 30 | ||
78 | 30 | #include <thread> | 31 | #include <thread> |
79 | 31 | #include <condition_variable> | 32 | #include <condition_variable> |
80 | @@ -161,6 +162,7 @@ | |||
81 | 161 | } | 162 | } |
82 | 162 | 163 | ||
83 | 163 | void operator()() noexcept // noexcept is important! (LP: #1237332) | 164 | void operator()() noexcept // noexcept is important! (LP: #1237332) |
84 | 165 | try | ||
85 | 164 | { | 166 | { |
86 | 165 | /* | 167 | /* |
87 | 166 | * Make the buffer the current rendering target, and release | 168 | * Make the buffer the current rendering target, and release |
88 | @@ -180,6 +182,10 @@ | |||
89 | 180 | 182 | ||
90 | 181 | run_compositing_loop([&] { return display_buffer_compositor->composite();}); | 183 | run_compositing_loop([&] { return display_buffer_compositor->composite();}); |
91 | 182 | } | 184 | } |
92 | 185 | catch (...) | ||
93 | 186 | { | ||
94 | 187 | mir::terminate_with_current_exception(); | ||
95 | 188 | } | ||
96 | 183 | 189 | ||
97 | 184 | private: | 190 | private: |
98 | 185 | std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory; | 191 | std::shared_ptr<mc::DisplayBufferCompositorFactory> const display_buffer_compositor_factory; |
99 | @@ -196,6 +202,7 @@ | |||
100 | 196 | } | 202 | } |
101 | 197 | 203 | ||
102 | 198 | void operator()() noexcept // noexcept is important! (LP: #1237332) | 204 | void operator()() noexcept // noexcept is important! (LP: #1237332) |
103 | 205 | try | ||
104 | 199 | { | 206 | { |
105 | 200 | run_compositing_loop( | 207 | run_compositing_loop( |
106 | 201 | [this] | 208 | [this] |
107 | @@ -215,6 +222,10 @@ | |||
108 | 215 | return false; | 222 | return false; |
109 | 216 | }); | 223 | }); |
110 | 217 | } | 224 | } |
111 | 225 | catch (...) | ||
112 | 226 | { | ||
113 | 227 | mir::terminate_with_current_exception(); | ||
114 | 228 | } | ||
115 | 218 | 229 | ||
116 | 219 | private: | 230 | private: |
117 | 220 | void wait_until_next_fake_vsync() | 231 | void wait_until_next_fake_vsync() |
118 | 221 | 232 | ||
119 | === modified file 'src/server/run_mir.cpp' | |||
120 | --- src/server/run_mir.cpp 2013-10-21 08:17:25 +0000 | |||
121 | +++ src/server/run_mir.cpp 2014-05-02 15:53:48 +0000 | |||
122 | @@ -23,6 +23,8 @@ | |||
123 | 23 | #include "mir/frontend/connector.h" | 23 | #include "mir/frontend/connector.h" |
124 | 24 | #include "mir/raii.h" | 24 | #include "mir/raii.h" |
125 | 25 | 25 | ||
126 | 26 | #include <exception> | ||
127 | 27 | #include <mutex> | ||
128 | 26 | #include <csignal> | 28 | #include <csignal> |
129 | 27 | #include <cstdlib> | 29 | #include <cstdlib> |
130 | 28 | #include <cassert> | 30 | #include <cassert> |
131 | @@ -32,6 +34,8 @@ | |||
132 | 32 | auto const intercepted = { SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS }; | 34 | auto const intercepted = { SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS }; |
133 | 33 | 35 | ||
134 | 34 | std::weak_ptr<mir::frontend::Connector> weak_connector; | 36 | std::weak_ptr<mir::frontend::Connector> weak_connector; |
135 | 37 | std::exception_ptr termination_exception; | ||
136 | 38 | std::mutex termination_exception_mutex; | ||
137 | 35 | 39 | ||
138 | 36 | extern "C" void delete_endpoint() | 40 | extern "C" void delete_endpoint() |
139 | 37 | { | 41 | { |
140 | @@ -58,6 +62,10 @@ | |||
141 | 58 | void mir::run_mir(ServerConfiguration& config, std::function<void(DisplayServer&)> init) | 62 | void mir::run_mir(ServerConfiguration& config, std::function<void(DisplayServer&)> init) |
142 | 59 | { | 63 | { |
143 | 60 | DisplayServer* server_ptr{nullptr}; | 64 | DisplayServer* server_ptr{nullptr}; |
144 | 65 | { | ||
145 | 66 | std::lock_guard<std::mutex> lock{termination_exception_mutex}; | ||
146 | 67 | termination_exception = nullptr; | ||
147 | 68 | } | ||
148 | 61 | auto main_loop = config.the_main_loop(); | 69 | auto main_loop = config.the_main_loop(); |
149 | 62 | 70 | ||
150 | 63 | main_loop->register_signal_handler( | 71 | main_loop->register_signal_handler( |
151 | @@ -88,4 +96,18 @@ | |||
152 | 88 | 96 | ||
153 | 89 | init(server); | 97 | init(server); |
154 | 90 | server.run(); | 98 | server.run(); |
155 | 99 | |||
156 | 100 | std::lock_guard<std::mutex> lock{termination_exception_mutex}; | ||
157 | 101 | if (termination_exception) | ||
158 | 102 | std::rethrow_exception(termination_exception); | ||
159 | 103 | } | ||
160 | 104 | |||
161 | 105 | void mir::terminate_with_current_exception() | ||
162 | 106 | { | ||
163 | 107 | std::lock_guard<std::mutex> lock{termination_exception_mutex}; | ||
164 | 108 | if (!termination_exception) | ||
165 | 109 | { | ||
166 | 110 | termination_exception = std::current_exception(); | ||
167 | 111 | raise(SIGTERM); | ||
168 | 112 | } | ||
169 | 91 | } | 113 | } |
170 | 92 | 114 | ||
171 | === modified file 'tests/acceptance-tests/test_server_shutdown.cpp' | |||
172 | --- tests/acceptance-tests/test_server_shutdown.cpp 2014-03-06 06:05:17 +0000 | |||
173 | +++ tests/acceptance-tests/test_server_shutdown.cpp 2014-05-02 15:53:48 +0000 | |||
174 | @@ -19,10 +19,14 @@ | |||
175 | 19 | #include "mir_toolkit/mir_client_library.h" | 19 | #include "mir_toolkit/mir_client_library.h" |
176 | 20 | #include "mir/compositor/renderer.h" | 20 | #include "mir/compositor/renderer.h" |
177 | 21 | #include "mir/compositor/renderer_factory.h" | 21 | #include "mir/compositor/renderer_factory.h" |
178 | 22 | #include "mir/compositor/display_buffer_compositor.h" | ||
179 | 23 | #include "mir/compositor/display_buffer_compositor_factory.h" | ||
180 | 22 | #include "mir/input/composite_event_filter.h" | 24 | #include "mir/input/composite_event_filter.h" |
181 | 25 | #include "mir/run_mir.h" | ||
182 | 23 | 26 | ||
183 | 24 | #include "mir_test_framework/display_server_test_fixture.h" | 27 | #include "mir_test_framework/display_server_test_fixture.h" |
184 | 25 | #include "mir_test/fake_event_hub_input_configuration.h" | 28 | #include "mir_test/fake_event_hub_input_configuration.h" |
185 | 29 | #include "mir_test/fake_event_hub.h" | ||
186 | 26 | #include "mir_test_framework/cross_process_sync.h" | 30 | #include "mir_test_framework/cross_process_sync.h" |
187 | 27 | #include "mir_test_doubles/stub_renderer.h" | 31 | #include "mir_test_doubles/stub_renderer.h" |
188 | 28 | 32 | ||
189 | @@ -55,6 +59,25 @@ | |||
190 | 55 | } | 59 | } |
191 | 56 | }; | 60 | }; |
192 | 57 | 61 | ||
193 | 62 | class ExceptionThrowingDisplayBufferCompositorFactory : public mc::DisplayBufferCompositorFactory | ||
194 | 63 | { | ||
195 | 64 | public: | ||
196 | 65 | std::unique_ptr<mc::DisplayBufferCompositor> | ||
197 | 66 | create_compositor_for(mg::DisplayBuffer&) override | ||
198 | 67 | { | ||
199 | 68 | struct ExceptionThrowingDisplayBufferCompositor : mc::DisplayBufferCompositor | ||
200 | 69 | { | ||
201 | 70 | bool composite() override | ||
202 | 71 | { | ||
203 | 72 | throw std::runtime_error("ExceptionThrowingDisplayBufferCompositor"); | ||
204 | 73 | } | ||
205 | 74 | }; | ||
206 | 75 | |||
207 | 76 | return std::unique_ptr<mc::DisplayBufferCompositor>( | ||
208 | 77 | new ExceptionThrowingDisplayBufferCompositor{}); | ||
209 | 78 | } | ||
210 | 79 | }; | ||
211 | 80 | |||
212 | 58 | void null_surface_callback(MirSurface*, void*) | 81 | void null_surface_callback(MirSurface*, void*) |
213 | 59 | { | 82 | { |
214 | 60 | } | 83 | } |
215 | @@ -94,6 +117,46 @@ | |||
216 | 94 | std::string const flag_file; | 117 | std::string const flag_file; |
217 | 95 | }; | 118 | }; |
218 | 96 | 119 | ||
219 | 120 | struct FakeEventHubServerConfig : TestingServerConfiguration | ||
220 | 121 | { | ||
221 | 122 | std::shared_ptr<mi::InputConfiguration> the_input_configuration() override | ||
222 | 123 | { | ||
223 | 124 | if (!input_configuration) | ||
224 | 125 | { | ||
225 | 126 | input_configuration = | ||
226 | 127 | std::make_shared<mtd::FakeEventHubInputConfiguration>( | ||
227 | 128 | the_composite_event_filter(), | ||
228 | 129 | the_input_region(), | ||
229 | 130 | std::shared_ptr<mi::CursorListener>(), | ||
230 | 131 | the_input_report()); | ||
231 | 132 | } | ||
232 | 133 | |||
233 | 134 | return input_configuration; | ||
234 | 135 | } | ||
235 | 136 | |||
236 | 137 | std::shared_ptr<mi::InputManager> the_input_manager() override | ||
237 | 138 | { | ||
238 | 139 | return DefaultServerConfiguration::the_input_manager(); | ||
239 | 140 | } | ||
240 | 141 | |||
241 | 142 | std::shared_ptr<mir::shell::InputTargeter> the_input_targeter() override | ||
242 | 143 | { | ||
243 | 144 | return DefaultServerConfiguration::the_input_targeter(); | ||
244 | 145 | } | ||
245 | 146 | std::shared_ptr<mir::scene::InputRegistrar> the_input_registrar() override | ||
246 | 147 | { | ||
247 | 148 | return DefaultServerConfiguration::the_input_registrar(); | ||
248 | 149 | } | ||
249 | 150 | |||
250 | 151 | mia::FakeEventHub* the_fake_event_hub() | ||
251 | 152 | { | ||
252 | 153 | the_input_configuration(); | ||
253 | 154 | return input_configuration->the_fake_event_hub(); | ||
254 | 155 | } | ||
255 | 156 | |||
256 | 157 | std::shared_ptr<mtd::FakeEventHubInputConfiguration> input_configuration; | ||
257 | 158 | }; | ||
258 | 159 | |||
259 | 97 | } | 160 | } |
260 | 98 | 161 | ||
261 | 99 | using ServerShutdown = BespokeDisplayServerTestFixture; | 162 | using ServerShutdown = BespokeDisplayServerTestFixture; |
262 | @@ -194,42 +257,7 @@ | |||
263 | 194 | Flag resources_freed_success{"resources_free_success_7e9c69fc.tmp"}; | 257 | Flag resources_freed_success{"resources_free_success_7e9c69fc.tmp"}; |
264 | 195 | Flag resources_freed_failure{"resources_free_failure_7e9c69fc.tmp"}; | 258 | Flag resources_freed_failure{"resources_free_failure_7e9c69fc.tmp"}; |
265 | 196 | 259 | ||
302 | 197 | /* Use the real input manager, but with a fake event hub */ | 260 | auto server_config = std::make_shared<FakeEventHubServerConfig>(); |
267 | 198 | struct ServerConfig : TestingServerConfiguration | ||
268 | 199 | { | ||
269 | 200 | std::shared_ptr<mi::InputConfiguration> the_input_configuration() override | ||
270 | 201 | { | ||
271 | 202 | if (!input_configuration) | ||
272 | 203 | { | ||
273 | 204 | input_configuration = | ||
274 | 205 | std::make_shared<mtd::FakeEventHubInputConfiguration>( | ||
275 | 206 | the_composite_event_filter(), | ||
276 | 207 | the_input_region(), | ||
277 | 208 | std::shared_ptr<mi::CursorListener>(), | ||
278 | 209 | the_input_report()); | ||
279 | 210 | } | ||
280 | 211 | |||
281 | 212 | return input_configuration; | ||
282 | 213 | } | ||
283 | 214 | |||
284 | 215 | std::shared_ptr<mi::InputManager> the_input_manager() override | ||
285 | 216 | { | ||
286 | 217 | return DefaultServerConfiguration::the_input_manager(); | ||
287 | 218 | } | ||
288 | 219 | |||
289 | 220 | std::shared_ptr<mir::shell::InputTargeter> the_input_targeter() override | ||
290 | 221 | { | ||
291 | 222 | return DefaultServerConfiguration::the_input_targeter(); | ||
292 | 223 | } | ||
293 | 224 | std::shared_ptr<mir::scene::InputRegistrar> the_input_registrar() override | ||
294 | 225 | { | ||
295 | 226 | return DefaultServerConfiguration::the_input_registrar(); | ||
296 | 227 | } | ||
297 | 228 | |||
298 | 229 | std::shared_ptr<mi::InputConfiguration> input_configuration; | ||
299 | 230 | }; | ||
300 | 231 | |||
301 | 232 | auto server_config = std::make_shared<ServerConfig>(); | ||
303 | 233 | launch_server_process(*server_config); | 261 | launch_server_process(*server_config); |
304 | 234 | 262 | ||
305 | 235 | struct ClientConfig : TestingClientConfiguration | 263 | struct ClientConfig : TestingClientConfiguration |
306 | @@ -427,3 +455,74 @@ | |||
307 | 427 | INSTANTIATE_TEST_CASE_P(ServerShutdown, | 455 | INSTANTIATE_TEST_CASE_P(ServerShutdown, |
308 | 428 | OnSignal, | 456 | OnSignal, |
309 | 429 | ::testing::Values(SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS)); | 457 | ::testing::Values(SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGBUS)); |
310 | 458 | |||
311 | 459 | TEST(ServerShutdownWithThreadException, | ||
312 | 460 | server_releases_resources_on_abnormal_input_thread_termination) | ||
313 | 461 | { | ||
314 | 462 | auto server_config = std::make_shared<FakeEventHubServerConfig>(); | ||
315 | 463 | auto fake_event_hub = server_config->the_fake_event_hub(); | ||
316 | 464 | |||
317 | 465 | std::thread server{ | ||
318 | 466 | [&] | ||
319 | 467 | { | ||
320 | 468 | EXPECT_THROW( | ||
321 | 469 | mir::run_mir(*server_config, [](mir::DisplayServer&){}), | ||
322 | 470 | std::runtime_error); | ||
323 | 471 | }}; | ||
324 | 472 | |||
325 | 473 | fake_event_hub->throw_exception_in_next_get_events(); | ||
326 | 474 | server.join(); | ||
327 | 475 | |||
328 | 476 | std::weak_ptr<mir::graphics::Display> display = server_config->the_display(); | ||
329 | 477 | std::weak_ptr<mir::compositor::Compositor> compositor = server_config->the_compositor(); | ||
330 | 478 | std::weak_ptr<mir::frontend::Connector> connector = server_config->the_connector(); | ||
331 | 479 | std::weak_ptr<mir::input::InputManager> input_manager = server_config->the_input_manager(); | ||
332 | 480 | |||
333 | 481 | server_config.reset(); | ||
334 | 482 | |||
335 | 483 | EXPECT_EQ(0, display.use_count()); | ||
336 | 484 | EXPECT_EQ(0, compositor.use_count()); | ||
337 | 485 | EXPECT_EQ(0, connector.use_count()); | ||
338 | 486 | EXPECT_EQ(0, input_manager.use_count()); | ||
339 | 487 | } | ||
340 | 488 | |||
341 | 489 | TEST(ServerShutdownWithThreadException, | ||
342 | 490 | server_releases_resources_on_abnormal_compositor_thread_termination) | ||
343 | 491 | { | ||
344 | 492 | struct ServerConfig : TestingServerConfiguration | ||
345 | 493 | { | ||
346 | 494 | std::shared_ptr<mc::DisplayBufferCompositorFactory> | ||
347 | 495 | the_display_buffer_compositor_factory() override | ||
348 | 496 | { | ||
349 | 497 | return display_buffer_compositor_factory( | ||
350 | 498 | [this]() | ||
351 | 499 | { | ||
352 | 500 | return std::make_shared<ExceptionThrowingDisplayBufferCompositorFactory>(); | ||
353 | 501 | }); | ||
354 | 502 | } | ||
355 | 503 | }; | ||
356 | 504 | |||
357 | 505 | auto server_config = std::make_shared<ServerConfig>(); | ||
358 | 506 | |||
359 | 507 | std::thread server{ | ||
360 | 508 | [&] | ||
361 | 509 | { | ||
362 | 510 | EXPECT_THROW( | ||
363 | 511 | mir::run_mir(*server_config, [](mir::DisplayServer&){}), | ||
364 | 512 | std::runtime_error); | ||
365 | 513 | }}; | ||
366 | 514 | |||
367 | 515 | server.join(); | ||
368 | 516 | |||
369 | 517 | std::weak_ptr<mir::graphics::Display> display = server_config->the_display(); | ||
370 | 518 | std::weak_ptr<mir::compositor::Compositor> compositor = server_config->the_compositor(); | ||
371 | 519 | std::weak_ptr<mir::frontend::Connector> connector = server_config->the_connector(); | ||
372 | 520 | std::weak_ptr<mir::input::InputManager> input_manager = server_config->the_input_manager(); | ||
373 | 521 | |||
374 | 522 | server_config.reset(); | ||
375 | 523 | |||
376 | 524 | EXPECT_EQ(0, display.use_count()); | ||
377 | 525 | EXPECT_EQ(0, compositor.use_count()); | ||
378 | 526 | EXPECT_EQ(0, connector.use_count()); | ||
379 | 527 | EXPECT_EQ(0, input_manager.use_count()); | ||
380 | 528 | } | ||
381 | 430 | 529 | ||
382 | === modified file 'tests/mir_test_doubles/fake_event_hub.cpp' | |||
383 | --- tests/mir_test_doubles/fake_event_hub.cpp 2013-10-02 11:04:00 +0000 | |||
384 | +++ tests/mir_test_doubles/fake_event_hub.cpp 2014-05-02 15:53:48 +0000 | |||
385 | @@ -221,6 +221,10 @@ | |||
386 | 221 | (void) timeoutMillis; | 221 | (void) timeoutMillis; |
387 | 222 | { | 222 | { |
388 | 223 | std::lock_guard<std::mutex> lg(guard); | 223 | std::lock_guard<std::mutex> lg(guard); |
389 | 224 | |||
390 | 225 | if (throw_in_get_events) | ||
391 | 226 | throw std::runtime_error("FakeEventHub::getEvents() exception"); | ||
392 | 227 | |||
393 | 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) |
394 | 225 | { | 229 | { |
395 | 226 | buffer[i] = events_available.front(); | 230 | buffer[i] = events_available.front(); |
396 | @@ -688,3 +692,9 @@ | |||
397 | 688 | { | 692 | { |
398 | 689 | return events_available.size(); | 693 | return events_available.size(); |
399 | 690 | } | 694 | } |
400 | 695 | |||
401 | 696 | void FakeEventHub::throw_exception_in_next_get_events() | ||
402 | 697 | { | ||
403 | 698 | std::lock_guard<std::mutex> lg(guard); | ||
404 | 699 | throw_in_get_events = true; | ||
405 | 700 | } |
PASSED: Continuous integration, rev:1584 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- ci/1433/ jenkins. qa.ubuntu. com/job/ mir-android- trusty- i386-build/ 1751 jenkins. qa.ubuntu. com/job/ mir-clang- trusty- amd64-build/ 1749 jenkins. qa.ubuntu. com/job/ mir-mediumtests -trusty- touch/1321 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- amd64-ci/ 1165 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- amd64-ci/ 1165/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- armhf-ci/ 1170 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- armhf-ci/ 1170/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- trusty- armhf/1324 jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- trusty- armhf/1324/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/1219 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 6516
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- team-mir- development- branch- ci/1433/ rebuild
http://