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:
parent
92993d1125
commit
da28994840
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 (¤t);
|
||||
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
|
|
@ -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
|
|
@ -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 (¤t);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue