engine: prevent timeouts in HTTP server mode

HTTP SyncML clients give up after a certain timeout (SyncEvolution
after RetryDuration = 5 minutes by default, Nokia e51 after 15
minutes) when the server fails to respond.

This can happen with SyncEvolution as server when it uses a slow
storage with many items, for example via WebDAV. In the case of slow
session startup, multithreading is now used to run the storage
initializing in parallel to sending regular "keep-alive" SyncML
replies to the client.

By default, these replies are sent every 2 minutes. This can be
configured with another extensions of the SyncMLVersion property:
  SyncMLVersion = REQUESTMAXTIME=5m

Other modes do not use multithreading by default, but it can be
enabled by setting REQUESTMAXTIME explicitly. It can be disabled
by setting the time to zero.

The new feature depends on a libsynthesis with multithreading enabled
and glib >= 2.32.0, which is necessary to make SyncEvolution itself
thread-safe. With an older glib, multithreading is disabled, but can
be enabled as a stop-gap measure by setting REQUESTMAXTIME explicitly.
This commit is contained in:
Patrick Ohly 2013-04-24 12:00:45 +02:00
parent 2032d17098
commit 9a4c770d8e
4 changed files with 87 additions and 5 deletions

View file

@ -25,7 +25,7 @@ SE_CHECK_FOR_STABLE_RELEASE
# Minimum version of libsynthesis as defined in its
# configure script and thus .pc files:
define([SYNTHESIS_MIN_VERSION], [3.4.0.16.8])
define([SYNTHESIS_MIN_VERSION], [3.4.0.16.9])
# Line above is patched by gen-autotools.sh. Handle
# both "yes" and "no".

View file

@ -32,6 +32,7 @@
#include <syncevo/IniConfigNode.h>
#include <syncevo/Cmdline.h>
#include <syncevo/lcs.h>
#include <syncevo/ThreadSupport.h>
#include <test.h>
#include <synthesis/timeutil.h>
@ -1363,10 +1364,28 @@ static ConfigProperty syncPropSyncMLVersion("SyncMLVersion",
"Instead or in adddition to the version, several keywords can\n"
"be set in this property (separated by spaces or commas):\n"
"\n"
"- NOCTCAP = avoid sending CtCap meta information\n"
"- NORESTART = disable the sync mode extension that SyncEvolution\n"
"- NOCTCAP - avoid sending CtCap meta information\n"
"- NORESTART - disable the sync mode extension that SyncEvolution\n"
" client and server use to negotiate whether both sides support\n"
" running multiple sync iterations in the same session\n"
"- REQUESTMAXTIME=<time> - override the rate at which the\n"
" SyncML server sends preliminary replies while preparing\n"
" local storages in the background. This helps to avoid timeouts\n"
" in the SyncML client. Depends on multithreading.\n"
#ifdef HAVE_THREAD_SUPPORT
// test-dbus.py checks for 'is thread-safe'!
" This SyncEvolution binary is thread-safe and thus this feature\n"
" is enabled by default for HTTP servers, with a delay of 2 minutes\n"
" between messages. Other servers (Bluetooth, local sync) should not\n"
" need preliminary replies and the feature is disabled, although\n"
" it can be enabled by setting the time explicitly.\n"
#else
" This SyncEvolution binary is not thread-safe and thus this feature\n"
" is disabled by default, although it can be enabled if absolutely\n"
" needed by setting the time explicitly.\n"
#endif
" <time> can be specified like other durations in the config,\n"
" for example as REQUESTMAXTIME=2m.\n"
"\n"
"Setting these flags should only be necessary as workaround for\n"
"broken peers.\n"
@ -1968,6 +1987,27 @@ InitStateString SyncConfig::getSyncMLVersion() const {
}
return InitStateString("", flags.wasSet());
}
InitState<unsigned int> SyncConfig::getRequestMaxTime() const {
InitState<unsigned int> requestmaxtime;
InitState< std::set<std::string> > flags = getSyncMLFlags();
BOOST_FOREACH(const std::string &flag, flags) {
size_t offset = flag.find('=');
if (offset != flag.npos) {
std::string key = flag.substr(0, offset);
if (boost::iequals(key, "RequestMaxTime")) {
std::string value = flag.substr(offset + 1);
unsigned int seconds;
std::string error;
if (!SecondsConfigProperty::parseDuration(value, error, seconds)) {
SE_THROW("invalid RequestMaxTime value in SyncMLVersion property: " + error);
}
requestmaxtime = seconds;
break;
}
}
}
return requestmaxtime;
}
InitState< std::set<std::string> > SyncConfig::getSyncMLFlags() const {
InitStateString value = syncPropSyncMLVersion.getProperty(*getNode(syncPropSyncMLVersion));
std::list<std::string> keywords;

View file

@ -1514,6 +1514,8 @@ class SyncConfig {
/** all flags that are set in the SyncMLVersion property, including the 1.0/1.1/1.2 versions */
virtual InitState< std::set<std::string> > getSyncMLFlags() const;
virtual InitState<unsigned int> getRequestMaxTime() const;
/**
* An arbitrary name assigned to the peer configuration,
* not necessarily unique. Can be used by a GUI instead

View file

@ -27,6 +27,7 @@
#include <syncevo/SyncSource.h>
#include <syncevo/util.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/ThreadSupport.h>
#include <syncevo/SafeConfigNode.h>
#include <syncevo/IniConfigNode.h>
@ -2429,7 +2430,45 @@ void SyncContext::getConfigXML(string &xml, string &configname)
" <server type='plugin'>\n"
" <plugin_module>SyncEvolution</plugin_module>\n"
" <plugin_sessionauth>yes</plugin_sessionauth>\n"
" <plugin_deviceadmin>yes</plugin_deviceadmin>\n"
" <plugin_deviceadmin>yes</plugin_deviceadmin>\n";
InitState<unsigned int> configrequestmaxtime = getRequestMaxTime();
unsigned int requestmaxtime;
if (configrequestmaxtime.wasSet()) {
// Explicitly set, use it regardless of the kind of sync.
// We allow this even if thread support was not available,
// because if a user enables it explicitly, it's probably
// for a good reason (= failing client), in which case
// risking multithreading issues is preferable.
requestmaxtime = configrequestmaxtime.get();
} else if (m_remoteInitiated || m_localSync) {
// We initiated the sync (local sync, Bluetooth). The client
// should not time out, so there is no need for intermediate
// message sending.
//
// To avoid potential problems and get a single log file,
// avoid it and multithreading by default.
requestmaxtime = 0;
} else {
// We were contacted by an HTTP client. Reply to client
// not later than 120 seconds while storage initializes
// in a background thread.
#ifdef HAVE_THREAD_SUPPORT
requestmaxtime = 120; // default in seconds
#else
requestmaxtime = 0;
#endif
}
if (requestmaxtime) {
clientorserver <<
" <multithread>yes</multithread>\n"
" <requestmaxtime>" << requestmaxtime << "</requestmaxtime>\n";
} else {
clientorserver <<
" <multithread>no</multithread>\n";
}
clientorserver <<
"\n" <<
sessioninitscript <<
" <sessiontimeout>300</sessiontimeout>\n"
@ -2454,6 +2493,7 @@ void SyncContext::getConfigXML(string &xml, string &configname)
clientorserver <<
" <client type='plugin'>\n"
" <binfilespath>$(binfilepath)</binfilespath>\n"
" <multithread>no</multithread>\n"
" <defaultauth/>\n";
if (getRefreshSync()) {
clientorserver <<
@ -2511,7 +2551,7 @@ void SyncContext::getConfigXML(string &xml, string &configname)
" <timestamp>yes</timestamp>\n"
" <timestampall>yes</timestampall>\n"
" <timedsessionlognames>no</timedsessionlognames>\n"
" <subthreadmode>suppress</subthreadmode>\n"
" <subthreadmode>separate</subthreadmode>\n"
" <logsessionstoglobal>yes</logsessionstoglobal>\n"
" <singlegloballog>yes</singlegloballog>\n";
if (logging) {