SyncML server: timeouts for unresponsive clients (MB #7710)

A SyncML server cannot resend a message. With HTTP, a reply simply
cannot be sent, for other transports the D-Bus API doesn't support it.
Therefore RetryInterval is ignored and only RetryDuration is used as
the final timeout after which the session is aborted.

The same transport callback is used for client and server. It was
changed to log the timeout duration (sent in via its parameter). The
rest of the logic is in the caller of TransportAgent::wait().

While touching the timeout code in initSAN(), the resending of the SAN
message was removed. This makes the code consistent with the HTTP
SyncML client case, where the initial message is also not resent.

The new test-dbus.py TestConnection.testTimeoutSync covers thus timeout
handling. It depends on setting RetryDuration to 10 in the server-side
config of the sc-api-nat device.
This commit is contained in:
Patrick Ohly 2010-01-14 12:09:33 +01:00
parent 650e730b91
commit 2e62210f5c
3 changed files with 78 additions and 50 deletions

View File

@ -1464,21 +1464,20 @@ void SyncContext::initSources(SourceList &sourceList)
bool SyncContext::transport_cb (void *udata)
{
return static_cast <SyncContext *> (udata) -> processTransportCb();
unsigned int interval = reinterpret_cast<uintptr_t>(udata);
SE_LOG_INFO(NULL, NULL, "Transport timeout after %u:%02umin",
interval / 60,
interval % 60);
// never cancel the transport, the higher levels will deal
// with the timeout
return true;
}
bool SyncContext::processTransportCb()
void SyncContext::setTransportCallback(int seconds)
{
// TODO: distinguish between client and server. In the server
// we have to implement a much higher time out and then disconnect
// an unresponsive client.
//Always return true to continue, we will detect the retry count at
//the higher level together with transport error scenarios.
SE_LOG_INFO(NULL, NULL, "Transport timeout after %d:%02dmin",
m_retryInterval / 60,
m_retryInterval % 60);
return true;
m_agent->setCallback(transport_cb,
reinterpret_cast<void *>(static_cast<uintptr_t>(seconds)),
seconds);
}
// XML configuration converted to C string constant
@ -2093,7 +2092,7 @@ SyncMLStatus SyncContext::sync(SyncReport *report)
return status;
}
bool SyncContext::initSAN(int retries)
bool SyncContext::initSAN()
{
sysync::SanPackage san;
/* Should be nonce sent by the server in the preceeding sync session */
@ -2178,38 +2177,41 @@ bool SyncContext::initSAN(int retries)
return false;
}
/* Create the transport agent */
try {
m_agent = createTransportAgent();
//register transport callback
if (m_retryInterval) {
m_agent->setCallback (transport_cb, this, m_retryInterval);
// Time out after the complete retry duration. This is the
// initial message of a sync, so we don't resend it (just as
// in a HTTP SyncML client trying to contact server).
if (m_retryDuration) {
setTransportCallback(m_retryDuration);
}
int retry = 0;
while (retry++ < retries)
{
SE_LOG_INFO (NULL, NULL, "Server sending SAN attempts #%d", retry);
m_agent->setContentType (TransportAgent::m_contentTypeServerAlertedNotificationDS);
m_agent->send(reinterpret_cast <char *> (buffer), sanSize);
//change content type
m_agent->setContentType (getWBXML() ? TransportAgent::m_contentTypeSyncWBXML :
TransportAgent::m_contentTypeSyncML);
if (m_agent->wait() == TransportAgent::GOT_REPLY){
const char *reply;
size_t replyLen;
string contentType;
m_agent->getReply (reply, replyLen, contentType);
//sanity check for the reply
if (contentType.empty() ||
contentType.find(TransportAgent::m_contentTypeSyncML) != contentType.npos ||
contentType.find(TransportAgent::m_contentTypeSyncWBXML) != contentType.npos) {
SharedBuffer request (reply, replyLen);
//TODO should generate more reasonable sessionId here
string sessionId ="";
initServer (sessionId, request, contentType);
return true;
}
SE_LOG_INFO (NULL, NULL, "Server sending SAN");
m_agent->setContentType(TransportAgent::m_contentTypeServerAlertedNotificationDS);
m_agent->send(reinterpret_cast <char *> (buffer), sanSize);
//change content type
m_agent->setContentType(getWBXML() ? TransportAgent::m_contentTypeSyncWBXML :
TransportAgent::m_contentTypeSyncML);
TransportAgent::Status status;
do {
status = m_agent->wait();
} while(status == TransportAgent::ACTIVE);
if (status == TransportAgent::GOT_REPLY) {
const char *reply;
size_t replyLen;
string contentType;
m_agent->getReply (reply, replyLen, contentType);
//sanity check for the reply
if (contentType.empty() ||
contentType.find(TransportAgent::m_contentTypeSyncML) != contentType.npos ||
contentType.find(TransportAgent::m_contentTypeSyncWBXML) != contentType.npos) {
SharedBuffer request (reply, replyLen);
//TODO should generate more reasonable sessionId here
string sessionId ="";
initServer (sessionId, request, contentType);
return true;
}
}
} catch (TransportException e) {
@ -2524,9 +2526,9 @@ SyncMLStatus SyncContext::doSync()
sessionKey.reset();
sendStart = resendStart = time (NULL);
//register transport callback
if (m_retryInterval) {
m_agent->setCallback (transport_cb, this, m_retryInterval);
int timeout = m_serverMode ? m_retryDuration : m_retryInterval;
if (timeout) {
setTransportCallback(timeout);
}
requestNum ++;
// use GetSyncMLBuffer()/RetSyncMLBuffer() to access the data to be
@ -2555,7 +2557,12 @@ SyncMLStatus SyncContext::doSync()
case TransportAgent::TIME_OUT: {
time_t duration = time(NULL) - sendStart;
if(duration > m_retryDuration){
// HTTP SyncML servers cannot resend a HTTP POST
// reply. Other server transports could in theory
// resend, but don't have the necessary D-Bus APIs
// (MB #6370).
if (m_serverMode ||
duration > m_retryDuration){
SE_LOG_INFO(NULL, NULL,
"Transport giving up after %d retries and %ld:%02ldmin",
m_retries,
@ -2606,7 +2613,8 @@ SyncMLStatus SyncContext::doSync()
case TransportAgent::FAILED: {
time_t curTime = time(NULL);
time_t duration = curTime - sendStart;
if (!m_retryInterval || duration > m_retryDuration || requestNum == 1) {
if (m_serverMode ||
!m_retryInterval || duration > m_retryDuration || requestNum == 1) {
SE_LOG_INFO(NULL, NULL,
"Transport giving up after %d retries and %ld:%02ldmin",
m_retries,

View File

@ -177,13 +177,13 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
/*
* Use initSAN 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 in retry
* times. Returns false if failed to get a valid client sync request
* Prepare the san package and send the SAN request to the peer.
* Returns false if failed to get a valid client sync request
* otherwise put the client sync request into m_initialMessage which will
* be used to initalze the server via initServer(), then continue sync() to
* start the real sync serssion.
*/
bool initSAN (int retry = 3);
bool initSAN();
/**
* Initializes the session so that it runs as SyncML server once
@ -666,7 +666,7 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
public:
static bool transport_cb (void *data);
bool processTransportCb();
void setTransportCallback(int seconds);
};
SE_END_CXX

View File

@ -1547,6 +1547,26 @@ class TestConnection(unittest.TestCase, DBusUtil):
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " aborted",
"session done"])
@timeout(20)
def testTimeoutSync(self):
"""start a sync, then wait for server to detect that we stopped replying
The server-side configuration for sc-api-nat must contain a retryDuration=10
because this test itself will time out with a failure after 20 seconds."""
conpath, connection = self.getConnection()
connection.Process(TestConnection.message1, 'application/vnd.syncml+xml')
loop.run()
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " got reply"])
DBusUtil.quit_events = []
# TODO: check events
self.failIfEqual(DBusUtil.reply, None)
self.failUnlessEqual(DBusUtil.reply[1], 'application/vnd.syncml+xml')
# wait for connection reset and "session done" due to timeout
loop.run()
loop.run()
self.failUnlessEqual(DBusUtil.quit_events, ["connection " + conpath + " aborted",
"session done"])
class TestMultipleConfigs(unittest.TestCase, DBusUtil):
""" sharing of properties between configs