sintonia/library/ecasound-2.7.2/ecasound/ecasound.cpp

926 lines
27 KiB
C++

// ------------------------------------------------------------------------
// ecasound.cpp: Console mode user interface to ecasound.
// Copyright (C) 2002-2010 Kai Vehmanen
//
// Attributes:
// eca-style-version: 3 (see Ecasound Programmer's Guide)
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// ------------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include <signal.h> /* POSIX: various signal functions */
#include <unistd.h> /* POSIX: sleep() */
#include <kvu_dbc.h>
#include <kvu_com_line.h>
#include <kvu_utils.h>
#include <eca-control-main.h>
#include <eca-control-mt.h>
#include <eca-error.h>
#include <eca-logger.h>
#include <eca-logger-default.h>
#include <eca-session.h>
#include <eca-version.h>
#ifdef ECA_USE_LIBLO
#include <eca-osc.h>
#endif
#include "eca-comhelp.h"
#include "eca-console.h"
#include "eca-curses.h"
#include "eca-neteci-server.h"
#include "eca-plaintext.h"
#include "textdebug.h"
#include "ecasound.h"
/**
* Check build time reqs
*/
#undef SIGNALS_CAN_BE_BLOCKED
#if defined(HAVE_SIGWAIT)
# if defined(HAVE_SIGPROCMASK) || defined(HAVE_PTHREAD_SIGMASK)
# define SIGNALS_CAN_BE_BLOCKED
# else
# error "Either pthread_sigmask() or sigprocmask() needs to be supported."
# error "One option to try is to undefine HAVE_SIGWAIT and retry compilation "
# error "with plain pause() support."
# endif
#elif defined(HAVE_PAUSE)
/* no op*/
#else
# error ""
#endif
/**
* Static/private function definitions
*/
static void ecasound_create_eca_objects(ECASOUND_RUN_STATE* state, COMMAND_LINE& cline);
static void ecasound_launch_neteci(ECASOUND_RUN_STATE* state);
static void ecasound_launch_osc(ECASOUND_RUN_STATE* state);
static int ecasound_pass_at_launch_commands(ECASOUND_RUN_STATE* state);
static void ecasound_main_loop_interactive(ECASOUND_RUN_STATE* state);
static void ecasound_main_loop_batch(ECASOUND_RUN_STATE* state);
void ecasound_parse_command_line(ECASOUND_RUN_STATE* state,
const COMMAND_LINE& clinein,
COMMAND_LINE* clineout);
static void ecasound_print_usage(void);
static void ecasound_print_version_banner(void);
static void ecasound_signal_setup(ECASOUND_RUN_STATE* state);
static void ecasound_signal_unblock(ECASOUND_RUN_STATE* state);
extern "C" {
static void* ecasound_watchdog_thread(void* arg);
static void ecasound_signal_handler(int signal);
}
/* Define to get exit debug traces */
// #define ENABLE_ECASOUND_EXIT_PROCESS_TRACES 1
/* Exit process has not yet started. */
#define ECASOUND_EXIT_PHASE_NONE 0
/* Termination will block until signal watchdog thread
* has exited. */
#define ECASOUND_EXIT_PHASE_WAIT_FOR_WD 1
/* Watchdog has terminated, only a single thread
* is left running. */
#define ECASOUND_EXIT_PHASE_ONE_THREAD 2
/* All resources freed, about to return from main(). */
#define ECASOUND_EXIT_PHASE_DONE 3
/**
* Global variable that is set to one of 'ECASOUND_EXIT_PHASE_*'
*
* When ecasound's main()
* function is about to exit (all engine threads have
* been terminated and only single thread aside the
* watchdog is left, and to '2' when all running state
* has been clearer in ~ECASOUND_RUN_STATE().
*/
static sig_atomic_t glovar_ecasound_exit_phase = ECASOUND_EXIT_PHASE_NONE;
/* Global variable that is set to one, when common
* POSIX signals are blocked and watchdog thread is
* blocking on a call to pthread_sigmask(), sigprocmask(),
* or pause(). */
static sig_atomic_t glovar_wd_signals_blocked = 0;
/* Global variable counting how many SIGINT signals
* have been ignored. */
static sig_atomic_t glovar_soft_signals_ignored = 0;
/* Debuggin macros, see ENABLE_ECASOUND_EXIT_PROCESS_TRACES
* above */
#ifdef ENABLE_ECASOUND_EXIT_PROCESS_TRACES
#define TRACE_EXIT(x) do { x; } while(0)
#else
#define TRACE_EXIT(x) while(0) { x; }
#endif
/**
* Namespace imports
*/
using namespace std;
ECASOUND_RUN_STATE::ECASOUND_RUN_STATE(void)
: console(0),
control(0),
logger(0),
eciserver(0),
osc(0),
session(0),
launchcmds(0),
neteci_thread(0),
watchdog_thread(0),
lock(0),
exit_request(0),
signalset(0),
retval(ECASOUND_RETVAL_SUCCESS),
neteci_mode(false),
neteci_tcp_port(2868),
osc_mode(false),
osc_udp_port(-1),
keep_running_mode(false),
cerr_output_only_mode(false),
interactive_mode(false),
quiet_mode(false)
{
}
ECASOUND_RUN_STATE::~ECASOUND_RUN_STATE(void)
{
/* note: global variable */
DBC_CHECK(glovar_ecasound_exit_phase == ECASOUND_EXIT_PHASE_ONE_THREAD);
if (control != 0) { delete control; control = 0; }
if (session != 0) { delete session; session = 0; }
if (launchcmds != 0) { delete launchcmds; launchcmds = 0; }
if (eciserver != 0) { delete eciserver; eciserver = 0; }
#ifdef ECA_USE_LIBLO
if (osc != 0) { delete osc; osc = 0; }
#endif
if (console != 0) { delete console; console = 0; }
if (neteci_thread != 0) { delete neteci_thread; neteci_thread = 0; }
if (watchdog_thread != 0) { delete watchdog_thread; watchdog_thread = 0; }
if (lock != 0) { delete lock; lock = 0; }
if (signalset != 0) { delete signalset; signalset = 0; }
glovar_ecasound_exit_phase = ECASOUND_EXIT_PHASE_DONE;
}
/**
* Function definitions
*/
int main(int argc, char *argv[])
{
ECASOUND_RUN_STATE state;
bool have_curses =
#if defined(ECA_PLATFORM_CURSES)
true
#else
false
#endif
;
/* 1. setup signals and the signal watchdog thread */
ecasound_signal_setup(&state);
/* 2. parse command-line args */
COMMAND_LINE* cline = new COMMAND_LINE(argc, argv);
COMMAND_LINE* clineout = new COMMAND_LINE();
ecasound_parse_command_line(&state, *cline, clineout);
delete cline; cline = 0;
/* 3. create console interface */
if (state.retval == ECASOUND_RETVAL_SUCCESS) {
if (have_curses == true &&
state.quiet_mode != true &&
state.cerr_output_only_mode != true) {
state.console = new ECA_CURSES();
state.logger = new TEXTDEBUG();
}
else {
ostream* ostr = (state.cerr_output_only_mode == true) ? &cerr : &cout;
state.console = new ECA_PLAIN_TEXT(ostr);
state.logger = new ECA_LOGGER_DEFAULT(*ostr);
}
ECA_LOGGER::attach_logger(state.logger);
if (state.quiet_mode != true) {
/* 4. print banner */
state.console->print_banner();
}
/* 5. set default debug levels */
ECA_LOGGER::instance().set_log_level(ECA_LOGGER::errors, true);
ECA_LOGGER::instance().set_log_level(ECA_LOGGER::info, true);
ECA_LOGGER::instance().set_log_level(ECA_LOGGER::subsystems, true);
ECA_LOGGER::instance().set_log_level(ECA_LOGGER::eiam_return_values, true);
ECA_LOGGER::instance().set_log_level(ECA_LOGGER::module_names, true);
/* 6. create eca objects */
ecasound_create_eca_objects(&state, *clineout);
delete clineout; clineout = 0;
/* 7. enable remote control over socket connection */
if (state.retval == ECASOUND_RETVAL_SUCCESS) {
/* 7.a) ... ECI over socket connection */
if (state.neteci_mode == true) {
ecasound_launch_neteci(&state);
}
/* 7.b) ... over OSC */
if (state.osc_mode == true) {
ecasound_launch_osc(&state);
}
}
/* 8. pass launch commands */
ecasound_pass_at_launch_commands(&state);
/* 9. start processing */
if (state.retval == ECASOUND_RETVAL_SUCCESS) {
if (state.interactive_mode == true)
ecasound_main_loop_interactive(&state);
else
ecasound_main_loop_batch(&state);
}
}
TRACE_EXIT(cerr << endl << "ecasound: out of mainloop..." << endl);
/* step: terminate neteci thread */
if (state.neteci_mode == true) {
/* wait until the NetECI thread has exited */
state.exit_request = 1;
if (state.neteci_thread)
pthread_join(*state.neteci_thread, NULL);
}
/* step: terminate the engine thread */
if (state.control != 0) {
if (state.control->is_running() == true) {
state.control->stop_on_condition();
}
if (state.control->is_connected() == true) {
state.control->disconnect_chainsetup();
}
}
glovar_ecasound_exit_phase = ECASOUND_EXIT_PHASE_WAIT_FOR_WD;
TRACE_EXIT(cerr << endl << "ecasound: joining watchdog..." << endl);
/* step: Send a signal to the watchdog thread to wake it uP */
pthread_kill(*state.watchdog_thread, SIGHUP);
/* step: Unblock signals for the main thread as well. At this
* point the engine threads have been already terminated,
* so we don't have to anymore worry about which thread
* gets the signals. */
ecasound_signal_unblock(&state);
pthread_join(*state.watchdog_thread, NULL);
TRACE_EXIT(cerr << endl << "ecasound: joined watchdog..." << endl);
glovar_ecasound_exit_phase = ECASOUND_EXIT_PHASE_ONE_THREAD;
DBC_CHECK(state.retval == ECASOUND_RETVAL_SUCCESS ||
state.retval == ECASOUND_RETVAL_INIT_FAILURE ||
state.retval == ECASOUND_RETVAL_START_ERROR ||
state.retval == ECASOUND_RETVAL_RUNTIME_ERROR);
TRACE_EXIT(cerr << endl << "ecasound: main() exiting with code " << state.retval << endl);
return state.retval;
}
/**
* Enters the main processing loop.
*/
void ecasound_create_eca_objects(ECASOUND_RUN_STATE* state,
COMMAND_LINE& cline)
{
DBC_REQUIRE(state != 0);
DBC_REQUIRE(state->console != 0);
try {
state->session = new ECA_SESSION(cline);
state->control = new ECA_CONTROL_MT(state->session);
DBC_ENSURE(state->session != 0);
DBC_ENSURE(state->control != 0);
}
catch(ECA_ERROR& e) {
state->console->print("---\necasound: ERROR: [" + e.error_section() + "] : \"" + e.error_message() + "\"\n");
state->retval = ECASOUND_RETVAL_INIT_FAILURE;
}
}
/**
* Launches a background thread that allows NetECI
* clients to connect to the current ecasound
* session.
*/
void ecasound_launch_neteci(ECASOUND_RUN_STATE* state)
{
DBC_REQUIRE(state != 0);
// DBC_REQUIRE(state->console != 0);
// state->console->print("ecasound: starting the NetECI server.");
state->neteci_thread = new pthread_t;
state->lock = new pthread_mutex_t;
pthread_mutex_init(state->lock, NULL);
state->eciserver = new ECA_NETECI_SERVER(state);
int res = pthread_create(state->neteci_thread,
NULL,
ECA_NETECI_SERVER::launch_server_thread,
reinterpret_cast<void*>(state->eciserver));
if (res != 0) {
cerr << "ecasound: Warning! Unable to create a thread for control over socket connection (NetECI)." << endl;
delete state->neteci_thread; state->neteci_thread = 0;
delete state->lock; state->lock = 0;
delete state->eciserver; state->eciserver = 0;
}
// state->console->print("ecasound: NetECI server started");
}
/**
* Sets up and activates Ecasound OSC interface
*/
void ecasound_launch_osc(ECASOUND_RUN_STATE* state)
{
#ifdef ECA_USE_LIBLO
DBC_REQUIRE(state != 0);
state->osc = new ECA_OSC_INTERFACE (state->control, state->osc_udp_port);
if (state->osc)
state->osc->start();
#endif
}
static int ecasound_pass_at_launch_commands(ECASOUND_RUN_STATE* state)
{
if (state->launchcmds) {
std::vector<std::string>::const_iterator p = state->launchcmds->begin();
while(p != state->launchcmds->end()) {
struct eci_return_value retval;
state->control->command(*p, &retval);
state->control->print_last_value(&retval);
++p;
}
}
return 0;
}
static void ecasound_check_for_quit(ECASOUND_RUN_STATE* state, const string& cmd)
{
if (cmd == "quit" || cmd == "q") {
state->console->print("---\necasound: Exiting...");
state->exit_request = 1;
ECA_LOGGER::instance().flush();
}
}
/**
* The main processing loop for interactive use.
*/
void ecasound_main_loop_interactive(ECASOUND_RUN_STATE* state)
{
DBC_REQUIRE(state != 0);
DBC_REQUIRE(state->console != 0);
ECA_CONTROL_MAIN* ctrl = state->control;
while(state->exit_request == 0) {
state->console->read_command("ecasound ('h' for help)> ");
const string& cmd = state->console->last_command();
if (cmd.size() > 0 && state->exit_request == 0) {
struct eci_return_value retval;
ctrl->command(cmd, &retval);
ctrl->print_last_value(&retval);
ecasound_check_for_quit(state, cmd);
}
}
}
/**
* The main processing loop for noninteractive use.
*/
void ecasound_main_loop_batch(ECASOUND_RUN_STATE* state)
{
DBC_REQUIRE(state != 0);
DBC_REQUIRE(state->console != 0);
ECA_CONTROL_MAIN* ctrl = state->control;
struct eci_return_value connect_retval;
if (ctrl->is_selected() == true &&
ctrl->is_valid() == true) {
ctrl->connect_chainsetup(&connect_retval);
}
if (state->neteci_mode != true &&
state->osc_mode != true) {
/* case: 2.1: non-interactive, neither NetECI or OSC is used,
* so this thread can use 'ctrl' exclusively */
if (ctrl->is_connected() == true) {
if (!state->exit_request) {
int res = ctrl->run(!state->keep_running_mode);
if (res < 0) {
state->retval = ECASOUND_RETVAL_RUNTIME_ERROR;
cerr << "ecasound: Warning! Errors detected during processing." << endl;
}
}
}
else {
ctrl->print_last_value(&connect_retval);
state->retval = ECASOUND_RETVAL_START_ERROR;
}
}
else {
/* case: 2.2: non-interactive, NetECI active
*
* (special handling is needed as NetECI needs
* to submit atomic bundles of ECI commands and thus
* needs to be able to lock the ECA_CONTROL object
* for itself) */
int res = -1;
if (ctrl->is_connected() == true) {
res = ctrl->start();
}
/* note: if keep_running_mode is enabled, we do not
* exit even if there are errors during startup */
if (state->keep_running_mode != true &&
res < 0) {
state->retval = ECASOUND_RETVAL_START_ERROR;
state->exit_request = 1;
}
while(state->exit_request == 0) {
if (state->keep_running_mode != true &&
ctrl->is_finished() == true)
break;
/* note: sleep for one second and let the NetECI thread
* access the ECA_CONTROL object for a while */
kvu_sleep(1, 0);
}
ecasound_check_for_quit(state, "quit");
}
// cerr << endl << "ecasound: mainloop exiting..." << endl;
}
/**
* Parses the command lines options in 'cline'.
*/
void ecasound_parse_command_line(ECASOUND_RUN_STATE* state,
const COMMAND_LINE& cline,
COMMAND_LINE* clineout)
{
if (cline.size() < 2) {
ecasound_print_usage();
state->retval = ECASOUND_RETVAL_INIT_FAILURE;
}
else {
cline.begin();
while(cline.end() != true) {
if (cline.current() == "-o:stdout" ||
cline.current() == "stdout") {
state->cerr_output_only_mode = true;
/* pass option to libecasound */
clineout->push_back(cline.current());
}
else if (cline.current() == "-d:0" ||
cline.current() == "-q") {
state->quiet_mode = true;
/* pass option to libecasound */
clineout->push_back(cline.current());
}
else if (cline.current() == "-c") {
state->interactive_mode = true;
}
else if (cline.current() == "-C") {
state->interactive_mode = false;
}
else if (cline.current() == "-D") {
state->cerr_output_only_mode = true;
}
else if (cline.current() == "--server" ||
cline.current() == "--daemon") {
/* note: --daemon* deprecated as of 2.6.0 */
state->neteci_mode = true;
}
else if (cline.current().compare(0, 2, "-E") == 0) {
cline.next();
if (cline.end() != true) {
state->launchcmds =
new std::vector<std::string>
(kvu_string_to_vector(cline.current(), ';'));
}
}
else if (cline.current().find("--server-tcp-port") != string::npos ||
cline.current().find("--daemon-port") != string::npos) {
std::vector<std::string> argpair =
kvu_string_to_vector(cline.current(), '=');
if (argpair.size() > 1) {
/* --server-tcp-port=XXXX */
/* note: --daemon* deprecated as of 2.6.0 */
state->neteci_tcp_port = atoi(argpair[1].c_str());
}
}
else if (cline.current() == "--no-server" ||
cline.current() == "--nodaemon") {
/* note: --daemon deprecated as of 2.6.0 */
state->neteci_mode = false;
}
else if (cline.current().find("--osc-udp-port") != string::npos) {
std::vector<std::string> argpair =
kvu_string_to_vector(cline.current(), '=');
if (argpair.size() > 1) {
/* --osc-udp-port=XXXX */
state->osc_udp_port = atoi(argpair[1].c_str());
fprintf(stdout,
"set UDP port based on %s to %d.\n", cline.current().c_str(), state->osc_udp_port);
}
#ifdef ECA_USE_LIBLO
state->osc_mode = true;
#else
state->osc_mode = false;
cerr << "ERROR: ecasound was built without OSC support" << endl;
#endif
}
else if (cline.current() == "-h" ||
cline.current() == "--help") {
ecasound_print_usage();
state->retval = ECASOUND_RETVAL_INIT_FAILURE;
break;
}
else if (cline.current() == "-K" ||
cline.current() == "--keep-running") {
state->keep_running_mode = true;
}
else if (cline.current() == "--version") {
ecasound_print_version_banner();
state->retval = ECASOUND_RETVAL_INIT_FAILURE;
break;
}
else {
/* pass rest of the options to libecasound */
clineout->push_back(cline.current());
}
cline.next();
}
}
}
void ecasound_print_usage(void)
{
cout << ecasound_parameter_help();
}
void ecasound_print_version_banner(void)
{
cout << "ecasound v" << ecasound_library_version << endl;
cout << "Copyright (C) 1997-2010 Kai Vehmanen and others." << endl;
cout << "Ecasound comes with ABSOLUTELY NO WARRANTY." << endl;
cout << "You may redistribute copies of ecasound under the terms of the GNU" << endl;
cout << "General Public License. For more information about these matters, see" << endl;
cout << "the file named COPYING." << endl;
}
static void ecasound_signal_handler(int signal)
{
/* note: If either sigprocmask() or pthread_sigmask()
* is available, all relevant signals should be blocked
* and this handler should be never called until the final
* phase (ECASOUND_EXIT_PHASE_WAIT_FOR_WD) of the process
* termination starts.
*/
#if defined(SIGNALS_CAN_BE_BLOCKED)
if (glovar_wd_signals_blocked) {
TRACE_EXIT(cerr << "WARNING: ecasound_signal_handler entered, this should _NOT_ happen!";
cerr << " pid=" << getpid() << endl);
}
#else /* !SIGNALS_CAN_BE_BLOCKED */
{
static int ignored = 0;
TRACE_EXIT(cerr << "ecasound_signal_handler, built with pause(), ignore count "
<< ignored << endl);
/* note: When signal blocking is not possible, ignore
* the first two signal (1st is the user sent signal,
* and second is the SIGHUP from ecasound main(). If
* we receive a third one, only then it's time for
* the emergency exit. */
if (++ignored <= 2) {
return;
}
}
#endif
TRACE_EXIT(cerr << "Signal "
<< signal
<< " received in exit phase "
<< glovar_ecasound_exit_phase << endl);
/* note: In ECASOUND_EXIT_PHASE_WAIT_FOR_WD, the main() thread
* will send a signal to the watchdog and we need to
* ignore this properly (as it's an internally generated
* signal, not sent from an external source. */
if (glovar_ecasound_exit_phase == ECASOUND_EXIT_PHASE_NONE ||
glovar_ecasound_exit_phase == ECASOUND_EXIT_PHASE_ONE_THREAD ||
glovar_ecasound_exit_phase == ECASOUND_EXIT_PHASE_DONE) {
if (signal == SIGINT &&
glovar_soft_signals_ignored == 0) {
cerr << endl
<< "NOTICE: SIGINT (ctrl-c) was received while terminating ecasound. If\n"
<< " another signal is received, the normal cleanup procedure will\n"
<< " be skipped and process will terminate immediately.\n";
glovar_soft_signals_ignored = 1;
return;
}
/* note: user needs to see this, not using TRACE_EXIT() macro */
cerr << endl
<< "WARNING: Signal was received while terminating ecasound, so exiting immediately!\n"
<< " Normal exit process is skipped, which may have some side-effects\n"
<< " (e.g. file header information not updated).\n";
/* step: Make sure the watchdog is woken up (a hack, but it seems
* exit() can in some cases be blocked when watchdog is
* still in sigwait() at this point. */
if (signal != SIGHUP)
kill(0, SIGHUP);
exit(ECASOUND_RETVAL_CLEANUP_ERROR);
}
}
/**
* Sets up a signal mask with sigaction() that blocks
* all common signals, and then launces a signal watchdog
* thread that waits on the blocked signals using
* sigwait().
*
* This design causes all non-fatal termination signals
* to be routed through a single thread. This signal watchdog
* in turn performs a clean exit upon receiving a signal.
* Without this setup, interactions between threads when handling
* would be harder to control (especially considering that
* ecasound needs to work on various different platforms).
*/
void ecasound_signal_setup(ECASOUND_RUN_STATE* state)
{
sigset_t* signalset;
/* man pthread_sigmask:
* "...signal actions and signal handlers, as set with
* sigaction(2), are shared between all threads"
*/
/* handle the following signals explicitly */
signalset = new sigset_t;
state->signalset = signalset;
sigemptyset(signalset);
sigaddset(signalset, SIGTERM);
sigaddset(signalset, SIGINT);
sigaddset(signalset, SIGHUP);
sigaddset(signalset, SIGPIPE);
sigaddset(signalset, SIGQUIT);
/* create a dummy signal handler */
struct sigaction blockaction;
blockaction.sa_handler = ecasound_signal_handler;
sigemptyset(&blockaction.sa_mask);
blockaction.sa_flags = 0;
/* attach the dummy handler to the following signals */
sigaction(SIGTERM, &blockaction, 0);
sigaction(SIGINT, &blockaction, 0);
sigaction(SIGHUP, &blockaction, 0);
sigaction(SIGPIPE, &blockaction, 0);
sigaction(SIGQUIT, &blockaction, 0);
#ifdef __FreeBSD__
/* ignore signals instead of passing them to our handler */
blockaction.sa_handler = SIG_IGN;
sigaction(SIGFPE, &blockaction, 0);
#endif
state->watchdog_thread = new pthread_t;
int res = pthread_create(state->watchdog_thread,
NULL,
ecasound_watchdog_thread,
reinterpret_cast<void*>(state));
if (res != 0) {
cerr << "ecasound: Warning! Unable to create watchdog thread." << endl;
}
/* block all signals in 'signalset' (see above) */
#if defined(HAVE_PTHREAD_SIGMASK)
pthread_sigmask(SIG_BLOCK, signalset, NULL);
#elif defined(HAVE_SIGPROCMASK)
sigprocmask(SIG_BLOCK, signalset, NULL);
#endif
}
static void ecasound_wd_wait_for_signals(ECASOUND_RUN_STATE* state)
{
#ifdef HAVE_SIGWAIT
{
/********************************************/
/* impl 1: sigwait() */
/********************************************/
int signalno = 0;
# if defined(HAVE_PTHREAD_SIGMASK)
pthread_sigmask(SIG_BLOCK, state->signalset, NULL);
# elif defined(HAVE_SIGPROCMASK)
/* the set of signals must be blocked before entering sigwait() */
sigprocmask(SIG_BLOCK, state->signalset, NULL);
# else
# error "Build environment error."
# endif
/* note: specific to sigwait() logic */
glovar_wd_signals_blocked = 1;
sigwait(state->signalset, &signalno);
TRACE_EXIT(cerr << endl << "(ecasound-watchdog) Received signal " << signalno << ". Cleaning up and exiting..." << endl);
}
#elif HAVE_PAUSE /* !HAVE_SIGWAIT */
/**************************************************/
/* impl 2: pause() (alternative to sigwait())
***************************************************/
/* note: with pause() we don't set 'glovar_ecasound_signal_blocked' as
* it is normal to get signals when watchdog is running */
pause();
TRACE_EXIT(cerr << endl << "(ecasound-watchdog) Received signal and returned from pause(). Cleaning up and Exiting..." << endl);
#else /* !HAVE_SIGWAIT && !HAVE_PAUSE */
# error "Build environment error."
#endif
}
/**
* Unblocks signals defined for 'state' for the calling thread,
* or with pthread_sigmask() is not supported, for the whole
* process.
*/
static void ecasound_signal_unblock(ECASOUND_RUN_STATE* state)
{
#ifdef HAVE_SIGWAIT
# if defined(HAVE_PTHREAD_SIGMASK)
pthread_sigmask(SIG_UNBLOCK, state->signalset, NULL);
# elif defined(HAVE_SIGPROCMASK)
/* the set of signals must be blocked before entering sigwait() */
sigprocmask(SIG_UNBLOCK, state->signalset, NULL);
# else
# error "Build environment error."
# endif
#endif
}
/**
* Runs a watchdog thread that centrally catches signals that
* will cause ecasound to exit.
*/
void* ecasound_watchdog_thread(void* arg)
{
ECASOUND_RUN_STATE* state = reinterpret_cast<ECASOUND_RUN_STATE*>(arg);
/* step: announce we are alive */
// cerr << "Watchdog-thread created, pid=" << getpid() << "." << endl;
/* step: block until a signal is received */
ecasound_wd_wait_for_signals(state);
/* step: unblock signals for watchdog thread after process
* termination has been started */
ecasound_signal_unblock(state);
glovar_wd_signals_blocked = 0;
/* step: signal the mainloop that process should terminate */
state->exit_request = 1;
/* step: in case mainloop is blocked running a batch job, we signal
* the engine thread directly and force it to terminate */
if (state->interactive_mode != true &&
state->control)
state->control->quit_async();
TRACE_EXIT(cerr << endl << "(ecasound-watchdog) looping until main reaches join point..." << endl);
while(glovar_ecasound_exit_phase != 1) {
TRACE_EXIT(cerr << "(ecasound-watchdog) watchdog thread exiting (looping)..." << endl);
/* sleep for one 200ms */
kvu_sleep(0, 200000000);
/* note: A race condition exists between ECA_CONTROL_BASE
* quit_async() and run(): if quit_async() is called
* after run() has been entered, but before run()
* has managed to start the engine, it is possible engine
* may still be started.
*
* Thus we will keep checking the engine status until
* shutdown is really completed.
*
* For robustness, this check is also done when in
* interactive mode (in case the mainloop does not for
* some reason react to our exit request).
*/
if (state->control) {
if (state->control->is_engine_running() == true) {
state->control->quit_async();
}
}
}
/* note: this function should always exit before main() */
DBC_CHECK(glovar_ecasound_exit_phase == ECASOUND_EXIT_PHASE_WAIT_FOR_WD);
TRACE_EXIT(cerr << endl << "(ecasound-watchdog) thread exiting..." << endl);
return 0;
}