base: rework interplay of signal-proxy and entrypoint

The former scheme left open a race window between
_process_incoming_signals() and wait_and_dispatch_one_io_signal()
resulting in both threads calling block_for_signal() and blocking
forever with one unprocessed signal.

Fixes #3704
This commit is contained in:
Christian Helmuth
2020-03-23 16:25:31 +01:00
parent 69080014b0
commit e63c5e6c69
2 changed files with 54 additions and 52 deletions

View File

@@ -19,6 +19,7 @@
#include <base/rpc_server.h> #include <base/rpc_server.h>
#include <base/signal.h> #include <base/signal.h>
#include <base/thread.h> #include <base/thread.h>
#include <base/mutex.h>
namespace Genode { namespace Genode {
class Startup; class Startup;
@@ -101,13 +102,8 @@ class Genode::Entrypoint : Noncopyable
void (*_suspended_callback) () = nullptr; void (*_suspended_callback) () = nullptr;
void (*_resumed_callback) () = nullptr; void (*_resumed_callback) () = nullptr;
enum Signal_recipient { bool _signal_proxy_delivers_signal { false };
NONE = 0, ENTRYPOINT = 1, SIGNAL_PROXY = 2 Genode::Mutex _block_for_signal_mutex { };
};
int _signal_recipient { NONE };
Genode::Lock _signal_pending_lock { };
Genode::Lock _signal_pending_ack_lock { };
Io_progress_handler *_io_progress_handler { nullptr }; Io_progress_handler *_io_progress_handler { nullptr };

View File

@@ -43,6 +43,9 @@ static char const *initial_ep_name() { return "ep"; }
void Entrypoint::Signal_proxy_component::signal() void Entrypoint::Signal_proxy_component::signal()
{ {
/* signal delivered successfully */
ep._signal_proxy_delivers_signal = false;
ep._process_deferred_signals(); ep._process_deferred_signals();
bool io_progress = false; bool io_progress = false;
@@ -113,41 +116,27 @@ void Entrypoint::_process_incoming_signals()
for (;;) { for (;;) {
do { do {
_sig_rec->block_for_signal();
int success;
{ {
Lock::Guard guard(_signal_pending_lock); /* see documentation in 'wait_and_dispatch_one_io_signal()' */
success = cmpxchg(&_signal_recipient, NONE, SIGNAL_PROXY);
Mutex::Guard guard { _block_for_signal_mutex };
_signal_proxy_delivers_signal = true;
_sig_rec->block_for_signal();
} }
/* common case, entrypoint is not in 'wait_and_dispatch_one_io_signal' */ /*
if (success) { * It might happen that we try to forward a signal to the
/* * entrypoint, while the context of that signal is already
* It might happen that we try to forward a signal to the * destroyed. In that case we will get an ipc error exception
* entrypoint, while the context of that signal is already * as result, which has to be caught.
* destroyed. In that case we will get an ipc error exception */
* as result, which has to be caught. try {
*/ retry<Blocking_canceled>(
try { [&] () { _signal_proxy_cap.call<Signal_proxy::Rpc_signal>(); },
retry<Blocking_canceled>( [] () { warning("blocking canceled during signal processing"); });
[&] () { _signal_proxy_cap.call<Signal_proxy::Rpc_signal>(); }, } catch (Genode::Ipc_error) { /* ignore - context got destroyed in meantime */ }
[] () { warning("blocking canceled during signal processing"); });
} catch (Genode::Ipc_error) { /* ignore - context got destroyed in meantime */ }
cmpxchg(&_signal_recipient, SIGNAL_PROXY, NONE);
} else {
/*
* Entrypoint is in 'wait_and_dispatch_one_io_signal', wakup it up and
* block for next signal
*/
_sig_rec->unblock_signal_waiter(*_rpc_ep);
/*
* wait for the acknowledgment by the entrypoint
*/
_signal_pending_ack_lock.lock();
}
/* entrypoint destructor requested to stop signal handling */ /* entrypoint destructor requested to stop signal handling */
if (_stop_signal_proxy) { if (_stop_signal_proxy) {
@@ -198,15 +187,7 @@ bool Entrypoint::_wait_and_dispatch_one_io_signal(bool const dont_block)
for (;;) { for (;;) {
try { try {
_signal_pending_lock.lock();
cmpxchg(&_signal_recipient, NONE, ENTRYPOINT);
Signal sig =_sig_rec->pending_signal(); Signal sig =_sig_rec->pending_signal();
cmpxchg(&_signal_recipient, ENTRYPOINT, NONE);
_signal_pending_lock.unlock();
_signal_pending_ack_lock.unlock();
/* defer application-level signals */ /* defer application-level signals */
if (sig.context()->level() == Signal_context::Level::App) { if (sig.context()->level() == Signal_context::Level::App) {
@@ -218,13 +199,38 @@ bool Entrypoint::_wait_and_dispatch_one_io_signal(bool const dont_block)
break; break;
} catch (Signal_receiver::Signal_not_pending) { } catch (Signal_receiver::Signal_not_pending) {
_signal_pending_lock.unlock(); if (dont_block)
if (dont_block) {
/* indicate that we leave wait_and_dispatch_one_io_signal */
cmpxchg(&_signal_recipient, ENTRYPOINT, NONE);
return false; return false;
{
/*
* The signal-proxy thread as well as the entrypoint via
* 'wait_and_dispatch_one_io_signal' never call
* 'block_for_signal()' without the '_block_for_signal_mutex'
* aquired. The signal-proxy thread also flags when it was
* unblocked by an incoming signal and delivers the signal via
* RPC in '_signal_proxy_delivers_signal'.
*/
Mutex::Guard guard { _block_for_signal_mutex };
/*
* If the signal proxy is blocked in the signal-delivery
* RPC but the call did not yet arrive in the entrypoint
* (_signal_proxy_delivers_signal == true), we acknowledge the
* delivery here (like in 'Signal_proxy_component::signal()') and
* retry to fetch one pending signal at the beginning of the
* loop above. Otherwise, we block for the next incoming
* signal.
*
* There exist cases were we already processed the signal
* flagged in '_signal_proxy_delivers_signal' and will end
* up here again. In these cases we also 'block_for_signal()'.
*/
if (_signal_proxy_delivers_signal)
_signal_proxy_delivers_signal = false;
else
_sig_rec->block_for_signal();
} }
_sig_rec->block_for_signal();
} }
} }