Skip to content

Commit 442ad90

Browse files
authored
Partial fix of exception safety in the event loop (#224)
Previously, if an exception was thrown from the event loop, the whole app would hang in `EventLoop::~EventLoop()`. Fix that by adding a simple scopeguard for the loop flag in `EventLoop::loop()`. Also superficially fix another similar bug in `doRunInLoopFuncs()`, although as the comments point out, it's exception safety is still under question. Overall, this exposes another bug whose source is still unclear: if an exception is thrown in a loop function, the program ultimately exits with `FATAL It is forbidden to run loop on threads other than event-loop thread - EventLoop.cc:258. However, this is still better than the previous hang.
1 parent 473059b commit 442ad90

File tree

1 file changed

+78
-21
lines changed

1 file changed

+78
-21
lines changed

trantor/net/EventLoop.cc

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,44 +170,96 @@ void EventLoop::quit()
170170
wakeup();
171171
}
172172
}
173+
174+
// The event loop needs a scope exit, so here's the simplest most limited
175+
// C++14 scope exit available (from
176+
// https://stackoverflow.com/a/42506763/3173540)
177+
//
178+
// TODO: If this is needed anywhere else, introduce a proper on_exit from, for
179+
// example, the GSL library
180+
namespace
181+
{
182+
template <typename F>
183+
struct ScopeExit
184+
{
185+
ScopeExit(F &&f) : f_(std::forward<F>(f))
186+
{
187+
}
188+
~ScopeExit()
189+
{
190+
f_();
191+
}
192+
F f_;
193+
};
194+
195+
template <typename F>
196+
ScopeExit<F> makeScopeExit(F &&f)
197+
{
198+
return ScopeExit<F>(std::forward<F>(f));
199+
};
200+
} // namespace
201+
173202
void EventLoop::loop()
174203
{
175204
assert(!looping_);
176205
assertInLoopThread();
177206
looping_.store(true, std::memory_order_release);
178207
quit_.store(false, std::memory_order_release);
179208

180-
while (!quit_.load(std::memory_order_acquire))
181-
{
182-
activeChannels_.clear();
209+
std::exception_ptr loopException;
210+
try
211+
{ // Scope where the loop flag is set
212+
213+
auto loopFlagCleaner = makeScopeExit(
214+
[this]() { looping_.store(false, std::memory_order_release); });
215+
while (!quit_.load(std::memory_order_acquire))
216+
{
217+
activeChannels_.clear();
183218
#ifdef __linux__
184-
poller_->poll(kPollTimeMs, &activeChannels_);
219+
poller_->poll(kPollTimeMs, &activeChannels_);
185220
#else
186-
poller_->poll(static_cast<int>(timerQueue_->getTimeout()),
187-
&activeChannels_);
188-
timerQueue_->processTimers();
221+
poller_->poll(static_cast<int>(timerQueue_->getTimeout()),
222+
&activeChannels_);
223+
timerQueue_->processTimers();
189224
#endif
190-
// TODO sort channel by priority
191-
// std::cout<<"after ->poll()"<<std::endl;
192-
eventHandling_ = true;
193-
for (auto it = activeChannels_.begin(); it != activeChannels_.end();
194-
++it)
195-
{
196-
currentActiveChannel_ = *it;
197-
currentActiveChannel_->handleEvent();
225+
// TODO sort channel by priority
226+
// std::cout<<"after ->poll()"<<std::endl;
227+
eventHandling_ = true;
228+
for (auto it = activeChannels_.begin(); it != activeChannels_.end();
229+
++it)
230+
{
231+
currentActiveChannel_ = *it;
232+
currentActiveChannel_->handleEvent();
233+
}
234+
currentActiveChannel_ = nullptr;
235+
eventHandling_ = false;
236+
// std::cout << "looping" << endl;
237+
doRunInLoopFuncs();
198238
}
199-
currentActiveChannel_ = nullptr;
200-
eventHandling_ = false;
201-
// std::cout << "looping" << endl;
202-
doRunInLoopFuncs();
239+
// loopFlagCleaner clears the loop flag here
240+
}
241+
catch (std::exception &e)
242+
{
243+
LOG_WARN << "Exception thrown from event loop, rethrowing after "
244+
"running functions on quit";
245+
loopException = std::current_exception();
203246
}
204-
looping_.store(false, std::memory_order_release);
205247

248+
// Run the quit functions even if exceptions were thrown
249+
// TODO: if more exceptions are thrown in the quit functions, some are left
250+
// un-run. Can this be made exception safe?
206251
Func f;
207252
while (funcsOnQuit_.dequeue(f))
208253
{
209254
f();
210255
}
256+
257+
// Throw the exception from the end
258+
if (loopException)
259+
{
260+
LOG_WARN << "Rethrowing exception from event loop";
261+
std::rethrow_exception(loopException);
262+
}
211263
}
212264
void EventLoop::abortNotInLoopThread()
213265
{
@@ -283,8 +335,14 @@ void EventLoop::doRunInLoopFuncs()
283335
{
284336
callingFuncs_ = true;
285337
{
338+
// Assure the flag is cleared even if func throws
339+
auto callingFlagCleaner =
340+
makeScopeExit([this]() { callingFuncs_ = false; });
286341
// the destructor for the Func may itself insert a new entry into the
287342
// queue
343+
// TODO: The following is exception-unsafe. If one of the funcs throws,
344+
// the remaining ones will not get run. The simplest fix is to catch any
345+
// exceptions and rethrow them later, but somehow that seems fishy...
288346
while (!funcs_.empty())
289347
{
290348
Func func;
@@ -294,7 +352,6 @@ void EventLoop::doRunInLoopFuncs()
294352
}
295353
}
296354
}
297-
callingFuncs_ = false;
298355
}
299356
void EventLoop::wakeup()
300357
{

0 commit comments

Comments
 (0)