syncevolution/src/dbus/server/auto-sync-manager.h
Patrick Ohly a009f28520 D-Bus server: fork/exec for sync, command line and restore operations
This commit moves the blocking syncing, database restore and command
line execution into a separate, short-lived process executing the
syncevo-dbus-helper. The advantage is that the main
syncevo-dbus-server remains responsive under all circumstances (fully
asynchronous now) and suffers less from memory leaks and/or crashes
during a sync.

The core idea behind the new architecture is that Session remains the
D-Bus facing side of a session. It continues to run inside
syncevo-dbus-server and uses the syncevo-dbus-helper transparently via
a custom D-Bus interface between the two processes. State changes of
the helper are mirrored in the server.

Later the helper might also be used multiple times in a Session. For
example, anything related to loading backends should be moved into the
helper (currently the "is config usable" check still runs in the
syncevo-dbus-server and needs to load/initialize backends). The
startup code of the helper already handles that (see boolean result of
operation callback), but it is not used yet in practice.

At the moment, only the helper provides a D-Bus API. It sends out
signals when it needs information from the server. The server watches
those and replies when ready. The helper monitors the connection to
the parent and detects that it won't get an answer if that connection
goes down.

The problem of "helper died unexpectedly" is also handled, by not
returning a D-Bus method reply until the requested operation is
completed (different from the way how the public D-Bus API is
defined!).

The Connection class continues to use such a Session, as before. It's
now fully asynchronous and exchanges messages with the helper via the
Session class.

Inside syncevo-dbus-server, boost::signals2 and the dbus-callbacks
infrastructure for asynchronous methods execution are used heavily
now. The glib event loop is entered exactly once and only left to shut
down.

Inside syncevo-dbus-helper, the event loop is entered only as
needed. Password requests sent from syncevo-local-sync to
syncevo-dbus-helper are handled asynchronously inside the event loop
driven by the local transport.

syncevo-dbus-helper and syncevo-local-sync are conceptually very
similar. Should investigate whether a single executable can serve both
functions.

The AutoSyncManager was completely rewritten. The data structure is a
lot simpler now (basically just a cache of transient information about
a sync config and the relevant config properties that define auto
syncing). The main work happens inside the schedule() call, which
verifies whether a session can run and, if not possible for some
reasons, ensures that it gets invoked again when that blocker is
gone (timeout over, server idle, etc.). The new code also uses
signals/slots instead of explicit coupling between the different
classes.

All code still lives inside the src/dbus/server directory. This
simplifies checking differences in partly modified files like
dbus-sync.cpp. A future commit will move the helper files.

The syslog logger code is referenced by the server, but never used.
This functionality needs further thought:
- Make usage depend on command line option? Beware that test-dbus.py
  looks for the "ready to run" output and thus startup breaks when
  all output goes to syslog instead of stdout.
- Redirect glib messages into syslog (done by LogRedirect, disabled when
  using LoggerSyslog)?

The syncevo-dbus-server now sends the final "Session.StatusChanged
done" signal immediately. The old implementation accidentally delayed
sending that for 100 seconds. The revised test-dbus.py checks for
more "session done" quit events to cover this fix.

Only user-visible messages should have the INFO level in any of the
helpers. Messages about starting and stopping processes are related to
implementation details and thus should only have DEBUG level.

The user also doesn't care about where the operation eventually
runs. All messages related to it should be in INFO/DEBUG/ERROR
messages without a process name. Therefore now syncevo-dbus-server
logs with a process name (also makes some explicit argv[0] logging
redundant; requires changes in test-dbus.py) and syncevo-dbus-helper
doesn't.

syncevo-local-sync is different from syncevo-dbus-helper: it produces
user-relevant output (the other half of the local sync). It's output
is carefully chosen so that the process name is something the user
understands (target context) and output can be clearly related to one
side or the other (for example, context names are included in the sync
table).

Output handling is based on the same idea as output handling in the
syncevo-dbus-server:
- Session registers itself as the top-most logger and sends
  SyncEvolution logging via D-Bus to the parent, which re-sends
  it with the right D-Bus object path as output of the session.
- Output redirection catches all other output and feeds it back
  to the Session log handler, from where it goes via D-Bus
  to the parent.

The advantage of this approach is that level information is made
available directly to the parent and that message boundaries are
preserved properly.

stderr and stdout are redirected into the parent and logged there as
error. Normally the child should not print anything. While it runs,
LogRedirect inside it will capture output and log it
internally. Anything reaching the parent thus must be from early
process startup or shutdown.

Almost all communication from syncevo-dbus-helper to
syncevo-dbus-server is purely information for the syncevo-dbus-server;
syncevo-dbus-helper doesn't care whether the signal can be
delivered. The only exception is the information request, which must
succeed.

Instead of catching exceptions everywhere, the optional signals are
declared as such in the EmitSignal template parameterization and
no longer throw exceptions when something goes wrong. They also don't
log anything, because that could lead to quite a lof of output.
2012-05-10 22:08:49 +02:00

226 lines
7.2 KiB
C++

/*
* Copyright (C) 2011 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 AUTO_SYNC_MANAGER_H
#define AUTO_SYNC_MANAGER_H
#include <boost/algorithm/string/predicate.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/signals2.hpp>
#include <syncevo/SyncML.h>
#include <syncevo/SmartPtr.h>
#include <syncevo/util.h>
#include "notification-manager-factory.h"
#include "timeout.h"
SE_BEGIN_CXX
class Server;
class Session;
/**
* Manager to manage automatic sync.
*
* Once a configuration is enabled with automatic sync, possibly http
* or obex-bt or both, the manager tracks whether they are ready to
* run. For that it watches which transports are available (and how
* long), which syncs run, etc.
*
* Automatic syncs only run when the server is idle. Then a new
* Session is created and thus runs immediately. Because multiple
* parallel sessions are not currently supported by SyncEvolution,
* scheduling the next session waits until the server is idle again.
*
* Currently only time-based automatic syncs are supported.
* Syncs triggered by local or remote changes will be added
* later.
*/
class AutoSyncManager
{
Server &m_server;
boost::weak_ptr<AutoSyncManager> m_me;
/** true if we currently hold a ref for AutoTerm */
bool m_autoTermLocked;
/** currently running auto sync session */
boost::shared_ptr<Session> m_session;
/** connects m_server.m_idleSignal with schedule() */
boost::signals2::connection m_idleConnection;
/** time when Bluetooth and HTTP transports became available, zero if not available */
Timespec m_btStartTime, m_httpStartTime;
/** initialize m_idleConnection */
void connectIdle();
public:
/**
* A single task for automatic sync.
*
* Caches information about the corresponding configuration.
* Some of that information is directly from the config, other
* is collected from sessions (time of last sync).
*
* Each task maintains information for all sync URLs.
*/
class AutoSyncTask
{
public:
/**
* unique, normalized config name, set when task is created the first time;
* by definition it cannot be changed later
*/
const std::string m_configName;
/**
* user-configurable peer name, with config name as fallback
*/
std::string m_peerName;
/** copy of config's remoteDeviceId sync property */
std::string m_remoteDeviceId;
/** last auto sync attempt succeeded (needed for notification logic) */
bool m_syncSuccessStart;
/** last auto sync attempt showed permanent failure (don't retry) */
bool m_permanentFailure;
/** autoSyncDelay = the time that the peer must at least have been around (seconds) */
unsigned int m_delay;
/**
* autoSyncInterval = the minimum time in seconds between syncs.
*
* Documentation is vague about whether this is measured as the time from
* start to start or between end to start. Traditionally, the implementation
* was between starts (= fixed rate). This assumed that syncs are short compared
* to the interval. In the extreme case (seen in testing), a sync takes longer
* than the interval and thus the next sync is started immediately - probably
* not what is expected. Keeping the behavior for now.
*/
unsigned int m_interval;
/**
* currently the start time of the last sync, measured with
* the monotonicly increasing OS time
*/
Timespec m_lastSyncTime;
/** maps syncURL to a specific transport mechanism */
enum Transport {
NEEDS_HTTP,
NEEDS_BT,
NEEDS_OTHER
};
/** list of sync URLs for which autosyncing over their transport mechanism
, in same order as in syncURL */
typedef std::list< std::pair<Transport, std::string> > URLInfo_t;
URLInfo_t m_urls;
AutoSyncTask(const std::string &configName) :
m_configName(configName),
m_syncSuccessStart(false),
m_permanentFailure(false),
m_delay(0),
m_interval(0)
{
}
/* /\** compare whether two tasks are the same, based on unique config name *\/ */
/* bool operator==(const AutoSyncTask &right) const */
/* { */
/* return m_peer == right.m_peer; */
/* } */
Timeout m_intervalTimeout;
Timeout m_btTimeout;
Timeout m_httpTimeout;
};
/* /\** remove tasks from m_peerMap and m_workQueue created from the config *\/ */
/* void remove(const std::string &configName); */
/**
* A map with information about *all* configs ever seen while auto
* sync manager was active, including configs without auto sync
* enabled (to track when and if they ran) and deleted configs
* (because they might get recreated).
*/
typedef std::map<std::string, boost::shared_ptr<AutoSyncTask> > PeerMap;
PeerMap m_peerMap;
/** used to send notifications */
boost::shared_ptr<NotificationManagerBase> m_notificationManager;
/**
* It reads all peers which are enabled to do auto sync and store them in
* the m_peerMap and then add timeout sources in the main loop to schedule
* auto sync tasks.
*/
void init();
/**
* check m_peerMap: runs syncs that are ready, sets/updates timers for the rest
*
* @param reason a short explanation why the method gets called (for debugging)
*/
void schedule(const std::string &reason);
/**
* Watch further progress (if auto sync session),
* record start time (in all cases).
*/
void sessionStarted(const boost::shared_ptr<Session> &session);
/** Show "sync started" notification. */
void autoSyncSuccessStart(AutoSyncTask *task);
/** Show completion notification. */
void autoSyncDone(AutoSyncTask *task, SyncMLStatus status);
/** Record result. */
void anySyncDone(AutoSyncTask *task, SyncMLStatus status);
AutoSyncManager(Server &server);
public:
static boost::shared_ptr<AutoSyncManager> createAutoSyncManager(Server &server);
/**
* prevent dbus server automatic termination when it has
* any auto sync task enabled in the configs.
* If returning true, prevent automatic termination.
*/
bool preventTerm();
/** init a config and set up auto sync task for it */
void initConfig(const std::string &configName);
};
SE_END_CXX
#endif // AUTO_SYNC_MANAGER_H