2015-12-23 15:22:33 +01:00
|
|
|
/*
|
|
|
|
* \brief Entrypoint for serving RPC requests and dispatching signals
|
|
|
|
* \author Norman Feske
|
|
|
|
* \author Christian Helmuth
|
|
|
|
* \date 2015-12-17
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2019-02-05 15:31:21 +01:00
|
|
|
* Copyright (C) 2015-2019 Genode Labs GmbH
|
2015-12-23 15:22:33 +01:00
|
|
|
*
|
|
|
|
* This file is part of the Genode OS framework, which is distributed
|
2017-02-20 13:23:52 +01:00
|
|
|
* under the terms of the GNU Affero General Public License version 3.
|
2015-12-23 15:22:33 +01:00
|
|
|
*/
|
|
|
|
|
2016-05-04 12:27:17 +02:00
|
|
|
/* Genode includes */
|
2015-12-23 15:22:33 +01:00
|
|
|
#include <base/entrypoint.h>
|
|
|
|
#include <base/component.h>
|
2017-02-14 17:00:53 +01:00
|
|
|
#include <cpu/atomic.h>
|
2015-12-23 15:22:33 +01:00
|
|
|
#include <util/retry.h>
|
2019-01-30 17:53:16 +01:00
|
|
|
#include <base/rpc_client.h>
|
2015-12-23 15:22:33 +01:00
|
|
|
|
2016-05-04 12:27:17 +02:00
|
|
|
/* base-internal includes */
|
|
|
|
#include <base/internal/globals.h>
|
2015-12-23 15:22:33 +01:00
|
|
|
|
|
|
|
using namespace Genode;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX move declarations to base-internal headers
|
|
|
|
*/
|
|
|
|
namespace Genode {
|
|
|
|
|
|
|
|
extern bool inhibit_tracing;
|
|
|
|
void call_global_static_constructors();
|
|
|
|
void destroy_signal_thread();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-09 15:04:37 +02:00
|
|
|
/**
|
|
|
|
* Return thread name used for the component's initial entrypoint
|
|
|
|
*/
|
|
|
|
static char const *initial_ep_name() { return "ep"; }
|
|
|
|
|
|
|
|
|
2017-04-03 10:45:51 +02:00
|
|
|
void Entrypoint::Signal_proxy_component::signal()
|
|
|
|
{
|
2020-03-23 16:25:31 +01:00
|
|
|
/* signal delivered successfully */
|
|
|
|
ep._signal_proxy_delivers_signal = false;
|
|
|
|
|
2019-02-05 15:31:21 +01:00
|
|
|
ep._process_deferred_signals();
|
|
|
|
|
|
|
|
bool io_progress = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try to dispatch one pending signal picked-up by the signal-proxy thread.
|
|
|
|
* Note, we handle only one signal here to ensure fairness between RPCs and
|
|
|
|
* signals.
|
|
|
|
*/
|
2017-04-03 10:45:51 +02:00
|
|
|
try {
|
|
|
|
Signal sig = ep._sig_rec->pending_signal();
|
|
|
|
ep._dispatch_signal(sig);
|
2019-02-05 15:31:21 +01:00
|
|
|
|
|
|
|
if (sig.context()->level() == Signal_context::Level::Io) {
|
|
|
|
/* trigger the progress handler */
|
|
|
|
io_progress = true;
|
|
|
|
}
|
2017-04-03 10:45:51 +02:00
|
|
|
} catch (Signal_receiver::Signal_not_pending) { }
|
|
|
|
|
2019-02-05 15:31:21 +01:00
|
|
|
if (io_progress)
|
|
|
|
ep._handle_io_progress();
|
2017-04-03 10:45:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-23 15:22:33 +01:00
|
|
|
void Entrypoint::_dispatch_signal(Signal &sig)
|
|
|
|
{
|
|
|
|
Signal_dispatcher_base *dispatcher = 0;
|
|
|
|
dispatcher = dynamic_cast<Signal_dispatcher_base *>(sig.context());
|
|
|
|
|
|
|
|
if (!dispatcher)
|
|
|
|
return;
|
|
|
|
|
|
|
|
dispatcher->dispatch(sig.num());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-04-03 10:45:51 +02:00
|
|
|
void Entrypoint::_defer_signal(Signal &sig)
|
|
|
|
{
|
|
|
|
Signal_context *context = sig.context();
|
|
|
|
|
2020-02-19 16:26:40 +01:00
|
|
|
Mutex::Guard guard(_deferred_signals_mutex);
|
2017-04-03 10:45:51 +02:00
|
|
|
_deferred_signals.remove(context->deferred_le());
|
|
|
|
_deferred_signals.insert(context->deferred_le());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Entrypoint::_process_deferred_signals()
|
|
|
|
{
|
|
|
|
for (;;) {
|
|
|
|
Signal_context *context = nullptr;
|
|
|
|
{
|
2020-02-19 16:26:40 +01:00
|
|
|
Mutex::Guard guard(_deferred_signals_mutex);
|
2017-04-03 10:45:51 +02:00
|
|
|
if (!_deferred_signals.first()) return;
|
|
|
|
|
|
|
|
context = _deferred_signals.first()->object();
|
|
|
|
_deferred_signals.remove(_deferred_signals.first());
|
|
|
|
}
|
|
|
|
|
|
|
|
Signal_dispatcher_base *dispatcher =
|
|
|
|
dynamic_cast<Signal_dispatcher_base *>(context);
|
|
|
|
if (dispatcher) dispatcher->dispatch(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-23 15:22:33 +01:00
|
|
|
void Entrypoint::_process_incoming_signals()
|
|
|
|
{
|
|
|
|
for (;;) {
|
|
|
|
|
|
|
|
do {
|
2017-02-24 14:29:29 +01:00
|
|
|
{
|
2020-03-23 16:25:31 +01:00
|
|
|
/* see documentation in 'wait_and_dispatch_one_io_signal()' */
|
|
|
|
Mutex::Guard guard { _block_for_signal_mutex };
|
2017-02-24 14:29:29 +01:00
|
|
|
|
2020-03-23 16:25:31 +01:00
|
|
|
_signal_proxy_delivers_signal = true;
|
|
|
|
|
|
|
|
_sig_rec->block_for_signal();
|
2017-02-14 17:00:53 +01:00
|
|
|
}
|
2019-02-15 14:39:27 +01:00
|
|
|
|
2020-03-23 16:25:31 +01:00
|
|
|
/*
|
|
|
|
* It might happen that we try to forward a signal to the
|
|
|
|
* entrypoint, while the context of that signal is already
|
|
|
|
* destroyed. In that case we will get an ipc error exception
|
|
|
|
* as result, which has to be caught.
|
|
|
|
*/
|
|
|
|
try {
|
|
|
|
retry<Blocking_canceled>(
|
|
|
|
[&] () { _signal_proxy_cap.call<Signal_proxy::Rpc_signal>(); },
|
|
|
|
[] () { warning("blocking canceled during signal processing"); });
|
|
|
|
} catch (Genode::Ipc_error) { /* ignore - context got destroyed in meantime */ }
|
|
|
|
|
2019-02-15 14:39:27 +01:00
|
|
|
/* entrypoint destructor requested to stop signal handling */
|
|
|
|
if (_stop_signal_proxy) {
|
|
|
|
return;
|
|
|
|
}
|
2016-12-16 21:08:00 +01:00
|
|
|
} while (!_suspended);
|
2015-12-23 15:22:33 +01:00
|
|
|
|
2017-04-03 10:45:51 +02:00
|
|
|
_deferred_signal_handler.destruct();
|
2015-12-23 15:22:33 +01:00
|
|
|
_suspend_dispatcher.destruct();
|
|
|
|
_sig_rec.destruct();
|
|
|
|
dissolve(_signal_proxy);
|
2018-11-14 16:19:30 +01:00
|
|
|
deinit_heartbeat_monitoring();
|
2015-12-23 15:22:33 +01:00
|
|
|
_signal_proxy_cap = Capability<Signal_proxy>();
|
|
|
|
_rpc_ep.destruct();
|
|
|
|
destroy_signal_thread();
|
|
|
|
|
|
|
|
/* execute fork magic in noux plugin */
|
|
|
|
_suspended_callback();
|
|
|
|
|
2016-05-04 12:27:17 +02:00
|
|
|
init_signal_thread(_env);
|
2016-11-06 14:26:34 +01:00
|
|
|
|
2016-05-09 15:04:37 +02:00
|
|
|
_rpc_ep.construct(&_env.pd(), Component::stack_size(), initial_ep_name());
|
2018-11-14 16:19:30 +01:00
|
|
|
init_heartbeat_monitoring(_env);
|
2015-12-23 15:22:33 +01:00
|
|
|
_signal_proxy_cap = manage(_signal_proxy);
|
|
|
|
_sig_rec.construct();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Before calling the resumed callback, we reset the callback pointer
|
|
|
|
* as these may be set again in the resumed code to initiate the next
|
|
|
|
* suspend-resume cycle (e.g., exit()).
|
|
|
|
*/
|
|
|
|
void (*resumed_callback)() = _resumed_callback;
|
|
|
|
_suspended_callback = nullptr;
|
|
|
|
_resumed_callback = nullptr;
|
2016-12-16 21:08:00 +01:00
|
|
|
_suspended = false;
|
2015-12-23 15:22:33 +01:00
|
|
|
|
|
|
|
resumed_callback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-05-18 16:04:21 +02:00
|
|
|
bool Entrypoint::_wait_and_dispatch_one_io_signal(bool const dont_block)
|
2017-02-14 17:00:53 +01:00
|
|
|
{
|
2017-05-02 15:22:23 +02:00
|
|
|
if (!_rpc_ep->is_myself())
|
|
|
|
warning(__func__, " called from non-entrypoint thread \"",
|
|
|
|
Thread::myself()->name(), "\"");
|
|
|
|
|
2017-02-14 17:00:53 +01:00
|
|
|
for (;;) {
|
|
|
|
|
|
|
|
try {
|
2017-02-24 14:29:29 +01:00
|
|
|
Signal sig =_sig_rec->pending_signal();
|
2017-02-14 17:00:53 +01:00
|
|
|
|
2017-04-03 10:45:51 +02:00
|
|
|
/* defer application-level signals */
|
|
|
|
if (sig.context()->level() == Signal_context::Level::App) {
|
|
|
|
_defer_signal(sig);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-02-24 14:29:29 +01:00
|
|
|
_dispatch_signal(sig);
|
|
|
|
break;
|
2017-02-14 17:00:53 +01:00
|
|
|
|
|
|
|
} catch (Signal_receiver::Signal_not_pending) {
|
2020-03-23 16:25:31 +01:00
|
|
|
if (dont_block)
|
2017-05-18 16:04:21 +02:00
|
|
|
return false;
|
2020-03-23 16:25:31 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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();
|
2017-05-18 16:04:21 +02:00
|
|
|
}
|
2017-02-14 17:00:53 +01:00
|
|
|
}
|
|
|
|
}
|
2017-02-24 14:29:29 +01:00
|
|
|
|
2019-02-05 15:31:21 +01:00
|
|
|
_handle_io_progress();
|
2017-04-03 10:45:51 +02:00
|
|
|
|
|
|
|
/* initiate potential deferred-signal handling in entrypoint */
|
|
|
|
if (_deferred_signals.first()) {
|
|
|
|
/* construct the handler on demand (otherwise we break core) */
|
|
|
|
if (!_deferred_signal_handler.constructed())
|
|
|
|
_deferred_signal_handler.construct(*this, *this,
|
|
|
|
&Entrypoint::_handle_deferred_signals);
|
|
|
|
Signal_transmitter(*_deferred_signal_handler).submit();
|
|
|
|
}
|
2017-05-18 16:04:21 +02:00
|
|
|
|
|
|
|
return true;
|
2017-02-14 17:00:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-23 15:22:33 +01:00
|
|
|
void Entrypoint::schedule_suspend(void (*suspended)(), void (*resumed)())
|
|
|
|
{
|
|
|
|
_suspended_callback = suspended;
|
|
|
|
_resumed_callback = resumed;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We always construct the dispatcher when the suspend is scheduled and
|
|
|
|
* destruct it when the suspend is executed.
|
|
|
|
*/
|
|
|
|
_suspend_dispatcher.construct(*this, *this, &Entrypoint::_handle_suspend);
|
|
|
|
|
|
|
|
/* trigger wakeup of the signal-dispatch loop for suspend */
|
2017-02-14 17:00:53 +01:00
|
|
|
Signal_transmitter(*_suspend_dispatcher).submit();
|
2015-12-23 15:22:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Signal_context_capability Entrypoint::manage(Signal_dispatcher_base &dispatcher)
|
|
|
|
{
|
2016-12-16 21:08:00 +01:00
|
|
|
/* _sig_rec is invalid for a small window in _process_incoming_signals */
|
|
|
|
return _sig_rec.constructed() ? _sig_rec->manage(&dispatcher)
|
|
|
|
: Signal_context_capability();
|
2015-12-23 15:22:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Genode::Entrypoint::dissolve(Signal_dispatcher_base &dispatcher)
|
|
|
|
{
|
2016-12-16 21:08:00 +01:00
|
|
|
/* _sig_rec is invalid for a small window in _process_incoming_signals */
|
|
|
|
if (_sig_rec.constructed())
|
|
|
|
_sig_rec->dissolve(&dispatcher);
|
2017-04-03 10:45:51 +02:00
|
|
|
|
|
|
|
/* also remove context from deferred signal list */
|
|
|
|
{
|
2020-02-19 16:26:40 +01:00
|
|
|
Mutex::Guard guard(_deferred_signals_mutex);
|
2017-04-03 10:45:51 +02:00
|
|
|
_deferred_signals.remove(dispatcher.deferred_le());
|
|
|
|
}
|
2015-12-23 15:22:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
Follow practices suggested by "Effective C++"
The patch adjust the code of the base, base-<kernel>, and os repository.
To adapt existing components to fix violations of the best practices
suggested by "Effective C++" as reported by the -Weffc++ compiler
argument. The changes follow the patterns outlined below:
* A class with virtual functions can no longer publicly inherit base
classed without a vtable. The inherited object may either be moved
to a member variable, or inherited privately. The latter would be
used for classes that inherit 'List::Element' or 'Avl_node'. In order
to enable the 'List' and 'Avl_tree' to access the meta data, the
'List' must become a friend.
* Instead of adding a virtual destructor to abstract base classes,
we inherit the new 'Interface' class, which contains a virtual
destructor. This way, single-line abstract base classes can stay
as compact as they are now. The 'Interface' utility resides in
base/include/util/interface.h.
* With the new warnings enabled, all member variables must be explicitly
initialized. Basic types may be initialized with '='. All other types
are initialized with braces '{ ... }' or as class initializers. If
basic types and non-basic types appear in a row, it is nice to only
use the brace syntax (also for basic types) and align the braces.
* If a class contains pointers as members, it must now also provide a
copy constructor and assignment operator. In the most cases, one
would make them private, effectively disallowing the objects to be
copied. Unfortunately, this warning cannot be fixed be inheriting
our existing 'Noncopyable' class (the compiler fails to detect that
the inheriting class cannot be copied and still gives the error).
For now, we have to manually add declarations for both the copy
constructor and assignment operator as private class members. Those
declarations should be prepended with a comment like this:
/*
* Noncopyable
*/
Thread(Thread const &);
Thread &operator = (Thread const &);
In the future, we should revisit these places and try to replace
the pointers with references. In the presence of at least one
reference member, the compiler would no longer implicitly generate
a copy constructor. So we could remove the manual declaration.
Issue #465
2017-12-21 15:42:15 +01:00
|
|
|
struct Constructor : Interface
|
2015-12-23 15:22:33 +01:00
|
|
|
{
|
|
|
|
GENODE_RPC(Rpc_construct, void, construct);
|
|
|
|
GENODE_RPC_INTERFACE(Rpc_construct);
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Constructor_component : Rpc_object<Constructor, Constructor_component>
|
|
|
|
{
|
2016-04-27 22:11:38 +02:00
|
|
|
Env &env;
|
|
|
|
Constructor_component(Env &env) : env(env) { }
|
2015-12-23 15:22:33 +01:00
|
|
|
|
|
|
|
void construct()
|
|
|
|
{
|
|
|
|
/* enable tracing support */
|
|
|
|
Genode::inhibit_tracing = false;
|
|
|
|
|
|
|
|
Genode::call_global_static_constructors();
|
2017-05-10 20:11:38 +02:00
|
|
|
Genode::init_signal_transmitter(env);
|
2019-01-30 17:53:16 +01:00
|
|
|
Genode::init_tracing(env);
|
2015-12-23 15:22:33 +01:00
|
|
|
|
2017-06-13 13:18:11 +02:00
|
|
|
/*
|
|
|
|
* Now, as signaling is available, initialize the asynchronous
|
|
|
|
* parent resource mechanism
|
|
|
|
*/
|
|
|
|
init_parent_resource_requests(env);
|
|
|
|
|
2018-11-14 16:19:30 +01:00
|
|
|
init_heartbeat_monitoring(env);
|
|
|
|
|
2016-12-22 15:01:19 +01:00
|
|
|
Component::construct(env);
|
2015-12-23 15:22:33 +01:00
|
|
|
}
|
|
|
|
};
|
2019-01-30 17:53:16 +01:00
|
|
|
|
|
|
|
void invoke_constructor_at_entrypoint(Capability<Constructor> cap)
|
|
|
|
{
|
|
|
|
cap.call<Constructor::Rpc_construct>();
|
|
|
|
}
|
2015-12-23 15:22:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-04-27 22:11:38 +02:00
|
|
|
Entrypoint::Entrypoint(Env &env)
|
2015-12-23 15:22:33 +01:00
|
|
|
:
|
|
|
|
_env(env),
|
2017-05-10 20:11:38 +02:00
|
|
|
_rpc_ep(&env.pd(), Component::stack_size(), initial_ep_name()),
|
2015-12-23 15:22:33 +01:00
|
|
|
|
2017-05-10 20:11:38 +02:00
|
|
|
/* initialize signalling before creating the first signal receiver */
|
|
|
|
_signalling_initialized((init_signal_thread(env), true))
|
|
|
|
{
|
2016-11-06 14:26:34 +01:00
|
|
|
/* initialize emulation of the original synchronous root interface */
|
|
|
|
init_root_proxy(_env);
|
|
|
|
|
2015-12-23 15:22:33 +01:00
|
|
|
/*
|
|
|
|
* Invoke Component::construct function in the context of the entrypoint.
|
|
|
|
*/
|
|
|
|
Constructor_component constructor(env);
|
|
|
|
|
2019-01-30 17:53:16 +01:00
|
|
|
_env.ep().manage(constructor);
|
2015-12-23 15:22:33 +01:00
|
|
|
|
|
|
|
try {
|
2019-01-30 17:53:16 +01:00
|
|
|
invoke_constructor_at_entrypoint(constructor.cap());
|
2017-02-14 17:00:53 +01:00
|
|
|
} catch (Blocking_canceled) {
|
base: avoid use of deprecated base/printf.h
Besides adapting the components to the use of base/log.h, the patch
cleans up a few base headers, i.e., it removes unused includes from
root/component.h, specifically base/heap.h and
ram_session/ram_session.h. Hence, components that relied on the implicit
inclusion of those headers have to manually include those headers now.
While adjusting the log messages, I repeatedly stumbled over the problem
that printing char * arguments is ambiguous. It is unclear whether to
print the argument as pointer or null-terminated string. To overcome
this problem, the patch introduces a new type 'Cstring' that allows the
caller to express that the argument should be handled as null-terminated
string. As a nice side effect, with this type in place, the optional len
argument of the 'String' class could be removed. Instead of supplying a
pair of (char const *, size_t), the constructor accepts a 'Cstring'.
This, in turn, clears the way let the 'String' constructor use the new
output mechanism to assemble a string from multiple arguments (and
thereby getting rid of snprintf within Genode in the near future).
To enforce the explicit resolution of the char * ambiguity, the 'char *'
overload of the 'print' function is marked as deleted.
Issue #1987
2016-07-13 19:07:09 +02:00
|
|
|
warning("blocking canceled in entrypoint constructor");
|
2015-12-23 15:22:33 +01:00
|
|
|
}
|
|
|
|
|
2019-01-30 17:53:16 +01:00
|
|
|
_env.ep().dissolve(constructor);
|
2015-12-23 15:22:33 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The calling initial thread becomes the signal proxy thread for this
|
|
|
|
* entrypoint
|
|
|
|
*/
|
|
|
|
_process_incoming_signals();
|
|
|
|
}
|
|
|
|
|
2019-02-15 14:39:27 +01:00
|
|
|
|
2018-10-02 16:27:35 +02:00
|
|
|
Entrypoint::Entrypoint(Env &env, size_t stack_size, char const *name,
|
|
|
|
Affinity::Location location)
|
2015-12-23 15:22:33 +01:00
|
|
|
:
|
|
|
|
_env(env),
|
2018-10-02 16:27:35 +02:00
|
|
|
_rpc_ep(&env.pd(), stack_size, name, true, location),
|
|
|
|
_signalling_initialized(true)
|
2015-12-23 15:22:33 +01:00
|
|
|
{
|
2018-10-02 16:27:35 +02:00
|
|
|
_signal_proxy_thread.construct(env, *this, location,
|
|
|
|
Thread::Weight(), env.cpu());
|
2015-12-23 15:22:33 +01:00
|
|
|
}
|
|
|
|
|
2019-02-15 14:39:27 +01:00
|
|
|
Entrypoint::~Entrypoint()
|
|
|
|
{
|
|
|
|
/* stop the signal proxy before destruction */
|
|
|
|
_stop_signal_proxy_handler.construct(
|
|
|
|
*this, *this, &Entrypoint::_handle_stop_signal_proxy);
|
|
|
|
Signal_transmitter(*_stop_signal_proxy_handler).submit();
|
|
|
|
_signal_proxy_thread->join();
|
|
|
|
_stop_signal_proxy_handler.destruct();
|
|
|
|
|
|
|
|
_rpc_ep->dissolve(&_signal_proxy);
|
|
|
|
}
|