rewrote signal handling

Having the signal handling code in SyncContext created an unnecessary
dependency of some classes (in particular the transports) on
SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files.

Cleaning up when the caller is done with signal handling is now part
of the utility class (removed automatically when guard instance is
freed).

The signal handlers now push one byte for each caught signal into a
pipe. That byte tells the rest of the code which message it needs to
print, which cannot be done in the signal handlers (because the
logging code is not reentrant and thus not safe to call from a signal
handler).

Compared to the previous solution, this solves several problems:
- no more race condition between setting and printing the message
- the pipe can be watched in a glib event loop, thus removing
  the need to poll at regular intervals; polling is still possible
  (and necessary) in those transports which do not integrate with
  the event loop (CurlTransport) while it can be removed from
  others (SoupTransport, OBEXTransport)

A boost::signal is emitted when the global SuspendFlags change.
Automatic connection management is used to disconnect instances which
are managed by boost::shared_ptr. For example, the current transport's
cancel() method is called when the state changes to "aborted".

The early connection phase of the OBEX transport now also can be
aborted (required cleaning up that transport!).

Currently watching for aborts via the event loop only works for real
Unix signals, but not for "abort" flags set in derived SyncContext
instances. The plan is to change that by allowing a "set abort" on
SuspendFlags and thus making
SyncContext::checkForSuspend/checkForAbort() redundant.

The new class is used as follows:
- syncevolution command line without daemon uses it to control
  suspend/abort directly
- syncevolution command line as client of syncevo-dbus-server
  connects to the state change signal and relays it to the
  syncevo-dbus-server session via D-Bus; now all operations
  are protected like that, not just syncing
- syncevo-dbus-server installs its own handlers for SIGINT
  and SIGTERM and tries to shut down when either of them
  is received. SuspendFlags then doesn't activate its own
  handler. Instead that handler is invoked by the
  syncevo-dbus-server niam() handler, to suspend or abort
  a running sync. Once syncs run in a separate process, the
  syncevo-dbus-server should request that these processes
  suspend or abort before shutting down itself.
- The syncevo-local-sync helper ignores SIGINT after a sync
  has started. It would receive that signal when forked by
  syncevolution in non-daemon mode and the user presses
  CTRL-C. Now the signal is only handled in the parent
  process, which suspends as part of its own side of
  the SyncML session and aborts by sending a SIGTERM+SIGINT
  to syncevo-local-sync. SIGTERM in syncevo-local-sync is
  handled by SuspendFlags and is meant to abort whatever
  is going on there at the moment (see below).

Aborting long-running operations like import/export or communication
via CardDAV or ActiveSync still needs further work. The backends need
to check the abort state and return early instead of continuing.
This commit is contained in:
Patrick Ohly 2012-01-19 16:11:22 +01:00
parent 92993d1125
commit da28994840
12 changed files with 451 additions and 270 deletions

View File

@ -27,7 +27,7 @@
#include "restart.h"
#include <syncevo/SyncContext.h>
#include <syncevo/SuspendFlags.h>
using namespace SyncEvo;
using namespace GDBusCXX;
@ -40,7 +40,7 @@ namespace {
void niam(int sig)
{
shutdownRequested = true;
SyncContext::handleSignal(sig);
SuspendFlags::getSuspendFlags().handleSignal(sig);
g_main_loop_quit (loop);
}

View File

@ -18,7 +18,7 @@
*/
#include <syncevo/CurlTransportAgent.h>
#include <syncevo/SyncContext.h>
#include <syncevo/SuspendFlags.h>
#ifdef ENABLE_LIBCURL
@ -288,9 +288,10 @@ void CurlTransportAgent::checkCurl(CURLcode code, bool exception)
int CurlTransportAgent::progressCallback(void* transport, double, double, double, double)
{
CurlTransportAgent *agent = static_cast<CurlTransportAgent *> (transport);
const SuspendFlags &s_flags = SyncContext::getSuspendFlags();
//abort transfer
if (s_flags.state == SuspendFlags::CLIENT_ABORT){
SuspendFlags &flags = SuspendFlags::getSuspendFlags();
// check signals and abort transfer?
flags.printSignals();
if (flags.getState() == SuspendFlags::ABORT) {
agent->setAborting (true);
return -1;
}

View File

@ -731,6 +731,15 @@ public:
step("waiting for Sync() call from parent");
}
try {
// ignore SIGINT signal in local sync helper from now on:
// the parent process will handle those and tell us when
// we are expected to abort by sending a SIGTERM
struct sigaction new_action;
memset(&new_action, 0, sizeof(new_action));
new_action.sa_handler = SIG_IGN;
sigemptyset(&new_action.sa_mask);
sigaction(SIGINT, &new_action, NULL);
m_client->sync(&m_clientReport);
} catch (...) {
string explanation;
@ -909,8 +918,6 @@ int LocalTransportMain(int argc, char **argv)
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);

View File

@ -34,7 +34,7 @@
#include <errno.h>
#include <sys/socket.h>
#include <glib.h>
#include <syncevo/SyncContext.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/ObexTransportAgent.h>
#include <syncevo/declarations.h>
@ -604,19 +604,6 @@ gboolean ObexTransportAgent::obex_fd_source_cb_impl (GIOChannel *io, GIOConditio
}
}
SuspendFlags s_flags = SyncContext::getSuspendFlags();
//abort transfer, only process the abort one time.
if (s_flags.state == SuspendFlags::CLIENT_ABORT && !m_disconnecting){
//first check abort flag
SE_LOG_INFO (NULL, NULL, "ObexTransport aborting.");
//do not send the disconnect cmd, close the connection directly.
m_disconnecting = true;
m_sock = sockObj;
m_channel = channel;
cancel();
return TRUE;
}
time_t now = time(NULL);
if (m_timeoutSeconds &&
(m_requestStart != 0) &&

View File

@ -18,7 +18,6 @@
*/
#include <syncevo/SoupTransportAgent.h>
#include <syncevo/SyncContext.h>
#ifdef ENABLE_LIBSOUP
@ -143,7 +142,6 @@ void SoupTransportAgent::send(const char *data, size_t len)
soup_message_set_request(message.get(), m_contentType.c_str(),
SOUP_MEMORY_TEMPORARY, data, len);
m_status = ACTIVE;
m_abortEventSource = g_timeout_add_seconds(ABORT_CHECK_INTERVAL, AbortCallback, static_cast<gpointer> (this));
if (m_timeoutSeconds) {
m_message = message.get();
m_timeoutEventSource = g_timeout_add_seconds(m_timeoutSeconds, TimeoutCallback, static_cast<gpointer> (this));
@ -194,7 +192,6 @@ TransportAgent::Status SoupTransportAgent::wait(bool noReply)
SE_THROW_EXCEPTION(TransportException, failure);
}
m_abortEventSource.set(0);
m_timeoutEventSource.set(0);
return m_status;
}
@ -253,18 +250,6 @@ void SoupTransportAgent::HandleSessionCallback(SoupSession *session,
g_main_loop_quit(m_loop.get());
}
gboolean SoupTransportAgent::AbortCallback(gpointer transport)
{
const SuspendFlags &s_flags = SyncContext::getSuspendFlags();
if (s_flags.state == SuspendFlags::CLIENT_ABORT)
{
static_cast<SoupTransportAgent *>(transport)->cancel();
return FALSE;
}
return TRUE;
}
gboolean SoupTransportAgent::processCallback()
{
//stop the message processing and mark status as timeout

View File

@ -84,16 +84,11 @@ class SoupTransportAgent : public HTTPTransportAgent
eptr<GMainLoop, GMainLoop, GLibUnref> m_loop;
Status m_status;
std::string m_failure;
GLibEvent m_abortEventSource;
SoupMessage *m_message;
GLibEvent m_timeoutEventSource;
int m_timeoutSeconds;
/** User Abort check interval */
static const gint ABORT_CHECK_INTERVAL = 1;
/** This function is called regularly to check user abort event */
static gboolean AbortCallback (gpointer data);
/** This function is called regularly to detect timeout */
static gboolean TimeoutCallback (gpointer data);

View File

@ -0,0 +1,227 @@
/*
* Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
* Copyright (C) 2009 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <syncevo/SuspendFlags.h>
#include <syncevo/util.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <glib.h>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
SuspendFlags::SuspendFlags() :
m_state(NORMAL),
m_lastSuspend(0),
m_senderFD(-1),
m_receiverFD(-1)
{
}
SuspendFlags::~SuspendFlags()
{
deactivate();
}
SuspendFlags &SuspendFlags::getSuspendFlags()
{
static SuspendFlags flags;
return flags;
}
static gboolean SignalChannelReadyCB(GIOChannel *source,
GIOCondition condition,
gpointer data) throw()
{
try {
SuspendFlags &me = SuspendFlags::getSuspendFlags();
me.printSignals();
} catch (...) {
Exception::handle();
}
return TRUE;
}
/**
* Own glib IO watch for file descriptor
* which calls printSignals()
*/
class GLibGuard : public SuspendFlags::Guard
{
GIOChannel *m_channel;
guint m_channelReady;
public:
GLibGuard(int fd)
{
// glib watch which calls printSignals()
m_channel = g_io_channel_unix_new(fd);
m_channelReady = g_io_add_watch(m_channel, G_IO_IN, SignalChannelReadyCB, NULL);
}
~GLibGuard()
{
if (m_channelReady) {
g_source_remove(m_channelReady);
m_channelReady = 0;
}
if (m_channel) {
g_io_channel_unref(m_channel);
m_channel = NULL;
}
}
};
boost::shared_ptr<SuspendFlags::Guard> SuspendFlags::activate()
{
int fds[2];
if (pipe(fds)) {
SE_THROW(StringPrintf("allocating pipe for signals failed: %s", strerror(errno)));
}
// nonblocking, to avoid deadlocks when the pipe's buffer overflows
fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK);
m_senderFD = fds[1];
m_receiverFD = fds[0];
sigaction(SIGINT, NULL, &m_oldSigInt);
sigaction(SIGTERM, NULL, &m_oldSigTerm);
struct sigaction new_action;
memset(&new_action, 0, sizeof(new_action));
new_action.sa_handler = handleSignal;
sigemptyset(&new_action.sa_mask);
// don't let processing of SIGINT be interrupted
// of SIGTERM and vice versa, if we are doing the
// handling
if (m_oldSigInt.sa_handler == SIG_DFL) {
sigaddset(&new_action.sa_mask, SIGINT);
}
if (m_oldSigTerm.sa_handler == SIG_DFL) {
sigaddset(&new_action.sa_mask, SIGTERM);
}
if (m_oldSigInt.sa_handler == SIG_DFL) {
sigaction(SIGINT, &new_action, NULL);
}
if (m_oldSigTerm.sa_handler == SIG_DFL) {
sigaction(SIGTERM, &new_action, NULL);
}
return boost::shared_ptr<Guard>(new GLibGuard(m_receiverFD));
}
void SuspendFlags::deactivate()
{
if (m_receiverFD >= 0) {
sigaction(SIGTERM, &m_oldSigTerm, NULL);
sigaction(SIGINT, &m_oldSigInt, NULL);
close(m_receiverFD);
close(m_senderFD);
m_receiverFD = -1;
m_senderFD = -1;
}
}
void SuspendFlags::handleSignal(int sig)
{
SuspendFlags &me(getSuspendFlags());
unsigned char msg;
switch (sig) {
case SIGTERM:
switch (me.m_state) {
case ABORT:
msg = ABORT_AGAIN;
break;
default:
msg = me.m_state = ABORT;
break;
}
break;
case SIGINT: {
time_t current;
time (&current);
switch (me.m_state) {
case NORMAL:
// first time suspend or already aborted
msg = me.m_state = SUSPEND;
me.m_lastSuspend = current;
break;
case SUSPEND:
// turn into abort?
if (current - me.m_lastSuspend < ABORT_INTERVAL) {
msg = me.m_state = ABORT;
} else {
me.m_lastSuspend = current;
msg = SUSPEND_AGAIN;
}
break;
case SuspendFlags::ABORT:
msg = ABORT_AGAIN;
break;
case SuspendFlags::ABORT_AGAIN:
case SuspendFlags::SUSPEND_AGAIN:
// shouldn't happen
break;
}
break;
}
}
if (me.m_senderFD >= 0) {
write(me.m_senderFD, &msg, 1);
}
}
void SuspendFlags::printSignals()
{
if (m_receiverFD >= 0) {
unsigned char msg;
while (read(m_receiverFD, &msg, 1) == 1) {
const char *str = NULL;
switch (msg) {
case SUSPEND:
str = "Asking to suspend...\nPress CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!)";
break;
case SUSPEND_AGAIN:
str = "Suspend in progress...\nPress CTRL-C again quickly (within 2s) to stop immediately (can cause problems in the future!)";
break;
case ABORT:
str = "Aborting immediately ...";
break;
case ABORT_AGAIN:
str = "Already aborting as requested earlier ...";
break;
}
if (!str) {
SE_LOG_DEBUG(NULL, NULL, "internal error: received invalid signal msg %d", msg);
} else {
SE_LOG_INFO(NULL, NULL, "%s", str);
}
m_stateChanged(*this);
}
}
}
SE_END_CXX

131
src/syncevo/SuspendFlags.h Normal file
View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2012 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef INCL_SUSPENDFLAGS
#define INCL_SUSPENDFLAGS
#include <signal.h>
#include <boost/smart_ptr.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
/**
* A singleton which is responsible for signal handling. Supports
* "SIGINT = suspend sync" and "two quick successive SIGINT = abort"
* semantic. SIGTERM is always aborting.
*
* Can be polled and in addition, flags state changes by writing to a
* file descriptor for integration into an event loop.
*/
class SuspendFlags
{
public:
/** SIGINT twice within this amount of seconds aborts the sync */
static const time_t ABORT_INTERVAL = 2;
enum State
{
/** keep sync running */
NORMAL,
/** suspend sync */
SUSPEND,
/** suspend sync request received again (only written to event FD, not returned by getState()) */
SUSPEND_AGAIN,
/** abort sync */
ABORT,
/** abort sync request received again (only written to event FD, not returned by getState()) */
ABORT_AGAIN
};
/** access to singleton */
static SuspendFlags &getSuspendFlags();
State getState() const { return m_state; }
/**
* Users of this class can read a single char for each received
* signal from this file descriptor. The char is the State that
* was entered by that signal. This can be used to be notified
* immediately about changes, without having to poll.
*
* -1 if not activated.
*/
int getEventFD() const { return m_receiverFD; }
class Guard {
public:
virtual ~Guard() { getSuspendFlags().deactivate(); }
};
/**
* Allocate file descriptors, set signal handlers for SIGINT and
* SIGTERM. Once the returned guard is freed, it will
* automatically deactivate signal handling.
*/
boost::shared_ptr<Guard> activate();
/** free file descriptors, restore signal handlers */
void deactivate();
/**
* Retrieve state changes pushed into pipe by signal
* handler and write user-visible messages for them.
* Guaranteed to not block. Triggers the m_stateChanged
* Boost signal.
*/
void printSignals();
/**
* Triggered inside the main thread when the state
* changes. Either printSignals() needs to be called
* or directly or a glib watch must be activated which
* does that.
*/
typedef boost::signals2::signal<void (SuspendFlags &)> StateChanged_t;
StateChanged_t m_stateChanged;
/**
* React to a SIGINT or SIGTERM.
*
* Installed as signal handler by activate() if no other signal
* handler was set. May also be called by other signal handlers,
* regardless whether activated or not.
*/
static void handleSignal(int sig);
private:
SuspendFlags();
~SuspendFlags();
/** state as observed by signal handler */
State m_state;
/** time is measured inside signal handler */
time_t m_lastSuspend;
int m_senderFD, m_receiverFD;
struct sigaction m_oldSigInt, m_oldSigTerm;
};
SE_END_CXX
#endif // INCL_SUSPENDFLAGS

View File

@ -26,6 +26,7 @@
#include <syncevo/SyncContext.h>
#include <syncevo/SyncSource.h>
#include <syncevo/util.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/SafeConfigNode.h>
#include <syncevo/FileConfigNode.h>
@ -76,64 +77,9 @@ using namespace std;
SE_BEGIN_CXX
SyncContext *SyncContext::m_activeContext;
SuspendFlags SyncContext::s_flags;
static const char *LogfileBasename = "syncevolution-log";
void SyncContext::handleSignal(int sig)
{
switch (sig) {
case SIGTERM:
switch (s_flags.state) {
case SuspendFlags::CLIENT_ABORT:
s_flags.message = "Already aborting sync as requested earlier ...";
break;
default:
s_flags.state = SuspendFlags::CLIENT_ABORT;
s_flags.message = "Aborting sync immediately via SIGTERM ...";
break;
}
break;
case SIGINT: {
time_t current;
time (&current);
switch (s_flags.state) {
case SuspendFlags::CLIENT_NORMAL:
// first time suspend or already aborted
s_flags.state = SuspendFlags::CLIENT_SUSPEND;
s_flags.message = "Asking server to suspend...\nPress CTRL-C again quickly (within 2s) to stop sync immediately (can cause problems during next sync!)";
s_flags.last_suspend = current;
break;
case SuspendFlags::CLIENT_SUSPEND:
// turn into abort?
if (current - s_flags.last_suspend
< s_flags.ABORT_INTERVAL) {
s_flags.state = SuspendFlags::CLIENT_ABORT;
s_flags.message = "Aborting sync as requested via CTRL-C ...";
} else {
s_flags.last_suspend = current;
s_flags.message = "Suspend in progress...\nPress CTRL-C again quickly (within 2s) to stop sync immediately (can cause problems during next sync!)";
}
break;
case SuspendFlags::CLIENT_ABORT:
s_flags.message = "Already aborting sync as requested earlier ...";
break;
case SuspendFlags::CLIENT_ILLEGAL:
break;
break;
}
}
}
}
void SyncContext::printSignals()
{
if (s_flags.message) {
SE_LOG_INFO(NULL, NULL, "%s", s_flags.message);
s_flags.message = NULL;
}
}
SyncContext::SyncContext()
{
init();
@ -1533,6 +1479,30 @@ string SyncContext::getUsedSyncURL() {
return "";
}
static void CancelTransport(TransportAgent *agent, SuspendFlags &flags)
{
if (flags.getState() == SuspendFlags::ABORT) {
SE_LOG_DEBUG(NULL, NULL, "CancelTransport: cancelling because of SuspendFlags::ABORT");
agent->cancel();
}
}
/**
* common initialization for all kinds of transports, to be called
* before using them
*/
static void InitializeTransport(const boost::shared_ptr<TransportAgent> &agent,
int timeout)
{
agent->setTimeout(timeout);
// Automatically call cancel() when we an abort request
// is detected. Relies of automatic connection management
// to disconnect when agent is deconstructed.
SuspendFlags &flags(SuspendFlags::getSuspendFlags());
flags.m_stateChanged.connect(SuspendFlags::StateChanged_t::slot_type(CancelTransport, agent.get(), _1).track(agent));
}
boost::shared_ptr<TransportAgent> SyncContext::createTransportAgent(void *gmainloop)
{
string url = getUsedSyncURL();
@ -1543,22 +1513,21 @@ boost::shared_ptr<TransportAgent> SyncContext::createTransportAgent(void *gmainl
if (m_localSync) {
string peer = url.substr(strlen("local://"));
boost::shared_ptr<LocalTransportAgent> agent(new LocalTransportAgent(this, peer, gmainloop));
agent->setTimeout(timeout);
InitializeTransport(agent, timeout);
agent->start();
return agent;
} else if (boost::starts_with(url, "http://") ||
boost::starts_with(url, "https://")) {
#ifdef ENABLE_LIBSOUP
boost::shared_ptr<SoupTransportAgent> agent(new SoupTransportAgent(static_cast<GMainLoop *>(gmainloop)));
agent->setConfig(*this);
agent->setTimeout(timeout);
InitializeTransport(agent, timeout);
return agent;
#elif defined(ENABLE_LIBCURL)
if (!gmainloop) {
boost::shared_ptr<CurlTransportAgent> agent(new CurlTransportAgent());
agent->setConfig(*this);
agent->setTimeout(timeout);
InitializeTransport(agent, timeout);
return agent;
}
#endif
@ -1568,7 +1537,8 @@ boost::shared_ptr<TransportAgent> SyncContext::createTransportAgent(void *gmainl
boost::shared_ptr<ObexTransportAgent> agent(new ObexTransportAgent(ObexTransportAgent::OBEX_BLUETOOTH,
static_cast<GMainLoop *>(gmainloop)));
agent->setURL (btUrl);
agent->setTimeout(timeout);
InitializeTransport(agent, timeout);
// this will block already
agent->connect();
return agent;
#endif
@ -1890,6 +1860,20 @@ void SyncContext::displaySourceProgress(sysync::TProgressEventEnum type,
}
}
bool SyncContext::checkForAbort()
{
SuspendFlags &flags(SuspendFlags::getSuspendFlags());
flags.printSignals();
return flags.getState() == SuspendFlags::ABORT;
}
bool SyncContext::checkForSuspend()
{
SuspendFlags &flags(SuspendFlags::getSuspendFlags());
flags.printSignals();
return flags.getState() == SuspendFlags::SUSPEND;
}
void SyncContext::throwError(const string &error)
{
throwError(SyncMLStatus(STATUS_FATAL + sysync::LOCAL_STATUS_CODE), error);
@ -3233,24 +3217,13 @@ static string Step2String(sysync::uInt16 stepcmd)
SyncMLStatus SyncContext::doSync()
{
// install signal handlers only if default behavior
// is currently active, restore when we return
struct sigaction new_action, old_action;
memset(&new_action, 0, sizeof(new_action));
new_action.sa_handler = handleSignal;
sigemptyset(&new_action.sa_mask);
sigaction(SIGINT, NULL, &old_action);
boost::shared_ptr<SuspendFlags::Guard> signalGuard;
// install signal handlers unless this was explicitly disabled
bool catchSignals = getenv("SYNCEVOLUTION_NO_SYNC_SIGNALS") == NULL;
if (catchSignals && old_action.sa_handler == SIG_DFL) {
sigaction(SIGINT, &new_action, NULL);
if (catchSignals) {
signalGuard = SuspendFlags::getSuspendFlags().activate();
}
struct sigaction old_term_action;
sigaction(SIGTERM, NULL, &old_term_action);
if (catchSignals && old_term_action.sa_handler == SIG_DFL) {
sigaction(SIGTERM, &new_action, NULL);
}
SyncMLStatus status = STATUS_OK;
std::string s;
@ -3820,10 +3793,6 @@ SyncMLStatus SyncContext::doSync()
}
}
if (catchSignals) {
sigaction (SIGINT, &old_action, NULL);
sigaction (SIGTERM, &old_term_action, NULL);
}
return status;
}

View File

@ -40,32 +40,6 @@ class TransportAgent;
class SourceList;
class SyncSource;
struct SuspendFlags
{
/** SIGINT twice within this amount of seconds aborts the sync */
static const time_t ABORT_INTERVAL = 2;
enum CLIENT_STATE
{
CLIENT_NORMAL,
CLIENT_SUSPEND,
CLIENT_ABORT,
CLIENT_ILLEGAL
}state;
time_t last_suspend;
/**
* Simple string to print in SyncContext::printSignals().
* Set by SyncContext::handleSignal() when updating the
* global state. There's a slight race condition: if
* messages are set more quickly than they are printed,
* only the last message is printed.
*/
const char *message;
SuspendFlags():state(CLIENT_NORMAL),last_suspend(0),message(NULL)
{}
};
/**
* This is the main class inside SyncEvolution which
* looks at the configuration, activates all enabled
@ -102,10 +76,6 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
FullProps m_configFilters;
boost::shared_ptr<TransportAgent> m_agent;
/**
* flags for suspend and abort
*/
static SuspendFlags s_flags;
/**
* a pointer to the active SourceList instance for this context if one exists;
@ -265,9 +235,6 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
void setSyncDeviceID(const std::string &deviceID) { m_syncDeviceID = deviceID; }
std::string getSyncDeviceID() const { return m_syncDeviceID; }
/** read-only access to suspend and abort state */
static const SuspendFlags &getSuspendFlags() { return s_flags; }
/*
* Use sendSAN as the first step is sync() if this is a server alerted sync.
* Prepare the san package and send the SAN request to the peer.
@ -485,33 +452,6 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
*/
SharedSession getSession() { return m_session; }
/**
* sync() installs signal handlers for SIGINT and SIGTERM if no
* handler was installed already. SIGINT will try to suspend.
* Sending the signal again quickly (typically done by pressing
* CTRL-C twice) will abort. SIGTERM will abort the running sync
* immediately.
*
* If a handler was installed already, the caller is responsible
* for calling this function if this kind of SIGINT/SIGTERM
* handling is desired.
*/
static void handleSignal(int signal);
/**
* Once a signal was received, all future calls to sync() will
* react to it unless this function is called first.
*/
static void resetSignals() { s_flags = SuspendFlags(); }
/**
* handleSignals() is called in a signal handler,
* which can only call reentrant functions. Our
* logging code is not reentrant and thus has
* to be called outside of the signal handler.
*/
static void printSignals();
bool getRemoteInitiated() {return m_remoteInitiated;}
void setRemoteInitiated(bool remote) {m_remoteInitiated = remote;}
@ -758,10 +698,7 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
*
* @return true if user wants to abort
*/
virtual bool checkForAbort() {
printSignals();
return (s_flags.state == SuspendFlags::CLIENT_ABORT);
}
virtual bool checkForAbort();
/**
* Called to find out whether user wants to suspend sync.
@ -769,10 +706,7 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
* Same as checkForAbort(), but the session is finished
* gracefully so that it can be resumed.
*/
virtual bool checkForSuspend() {
printSignals();
return (s_flags.state == SuspendFlags::CLIENT_SUSPEND);
}
virtual bool checkForSuspend();
private:
/** initialize members as part of constructors */

View File

@ -72,6 +72,9 @@ src_syncevo_sources = \
\
src/syncevo/SynthesisDBPlugin.cpp \
\
src/syncevo/SuspendFlags.h \
src/syncevo/SuspendFlags.cpp \
\
src/syncevo/SyncContext.h \
src/syncevo/SyncContext.cpp \
\
@ -142,6 +145,7 @@ src_syncevo_libsyncevolution_include_HEADERS = \
src/syncevo/SyncConfig.h \
src/syncevo/SyncSource.h \
src/syncevo/util.h \
src/syncevo/SuspendFlags.h \
src/syncevo/SyncContext.h \
src/syncevo/SynthesisEngine.h \
src/syncevo/Logging.h \

View File

@ -56,6 +56,7 @@ using namespace GDBusCXX;
#include <syncevo/Cmdline.h>
#include <syncevo/SyncContext.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/LogRedirect.h>
#include <syncevo/LocalTransportAgent.h>
#include "CmdlineSyncClient.h"
@ -295,16 +296,10 @@ public:
void getStatusAsync();
/**
* call 'Suspend' method of 'Session' in dbus server
* call 'Suspend' or 'Abort' method of 'Session' in dbus server
* without waiting for return
*/
void suspendAsync();
/**
* call 'Abort' method of 'Session' in dbus server
* without waiting for return
*/
void abortAsync();
void interruptAsync(const char *operation);
/**
* call 'GetConfig' method of 'Session' in dbus server
@ -723,27 +718,13 @@ void RemoteDBusServer::daemonGone()
exit(1);
}
/**
* Don't hang onto a shared_ptr here!
*
* RemoteSessions contain a reference to the
* RemoteDBusServer which created them. Once that
* server destructs, all sessions must have been
* deleted earlier, otherwise they'll call a destructed
* object.
*/
boost::weak_ptr<RemoteSession> RemoteDBusServer::g_session;
void RemoteDBusServer::handleSignal(int sig)
static void SuspendFlagsChanged(RemoteSession *session,
SuspendFlags &flags)
{
SyncContext::handleSignal(sig);
boost::shared_ptr<RemoteSession> session = g_session.lock();
if (session) {
const SuspendFlags &flags = SyncContext::getSuspendFlags();
if(flags.state == SuspendFlags::CLIENT_SUSPEND) {
session->suspendAsync();
} else if(flags.state == SuspendFlags::CLIENT_ABORT) {
session->abortAsync();
}
if (flags.getState() == SuspendFlags::SUSPEND) {
session->interruptAsync("Suspend");
} else if(flags.getState() == SuspendFlags::ABORT) {
session->interruptAsync("Abort");
}
}
@ -789,42 +770,18 @@ bool RemoteDBusServer::execute(const vector<string> &args, const string &peer, b
return m_result;
}
//g_session is used to pass 'abort' or 'suspend' commands
//make sure session is ready to run
g_session = m_session;
//set up signal handlers to send 'suspend' or 'abort' to dbus server
//only do this once session is executing and can suspend and abort
struct sigaction new_action, old_action;
struct sigaction old_term_action;
if(runSync) {
memset(&new_action, 0, sizeof(new_action));
new_action.sa_handler = handleSignal;
sigemptyset(&new_action.sa_mask);
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler == SIG_DFL) {
sigaction(SIGINT, &new_action, NULL);
}
sigaction(SIGTERM, NULL, &old_term_action);
if (old_term_action.sa_handler == SIG_DFL) {
sigaction(SIGTERM, &new_action, NULL);
}
}
// Acticate signal handling in all cases.
// We let SuspendFlags catch them and then
// react in the normal event loop.
SuspendFlags &flags(SuspendFlags::getSuspendFlags());
boost::shared_ptr<SuspendFlags::Guard> signalGuard = flags.activate();
flags.m_stateChanged.connect(SuspendFlags::StateChanged_t::slot_type(SuspendFlagsChanged, m_session.get(), _1).track(m_session));
//wait until status is 'done'
while(!m_session->statusDone()) {
g_main_loop_run(m_loop);
}
if(runSync) {
sigaction (SIGINT, &old_action, NULL);
sigaction (SIGTERM, &old_term_action, NULL);
}
//reset session
g_session.reset();
//restore logging level
// LoggerBase::instance().setLevel(level);
m_session->setRunSync(false);
@ -1122,34 +1079,18 @@ void RemoteSession::getConfigCb(const Config_t &config, const string &error)
}
}
void RemoteSession::suspendAsync()
static void interruptCb(const std::string &error)
{
DBusClientCall0 suspend(*this, "Suspend");
suspend(boost::bind(&RemoteSession::suspendCb, this, _1));
}
void RemoteSession::suspendCb(const string &error)
{
//avoid logging messages in handleSignal
SyncContext::printSignals();
if(!error.empty()) {
m_server.setResult(false);
if (!error.empty()) {
SE_LOG_DEBUG(NULL, NULL, "interruptAsync() error from remote: %s", error.c_str());
}
}
void RemoteSession::abortCb(const string &error)
void RemoteSession::interruptAsync(const char *operation)
{
//avoid logging messages in handleSignal
SyncContext::printSignals();
if(!error.empty()) {
m_server.setResult(false);
}
}
void RemoteSession::abortAsync()
{
DBusClientCall0 abort(*this, "Abort");
abort(boost::bind(&RemoteSession::abortCb, this, _1));
// call Suspend() without checking result
DBusClientCall0 suspend(*this, operation);
suspend(interruptCb);
}
void RemoteSession::logOutput(Logger::Level level, const string &log)