local sync: avoid updating meta data when nothing changed

The sync meta data (sync anchors, client change log) get updated after
a sync even if nothing changed and the existing meta data could be
used again. This can be skipped for local sync, because then
SyncEvolution can ensure that both sides skip updating the meta
data. With a remote SyncML server that is not possible and thus
SyncEvolution has to update its data.

This optimization is only used for local syncs with one source.  It is
based on the observation that when the server side calls
SaveAdminData, the client has sent its last message and the sync is
complete. At that point, SyncEvolution can check whether anything has
changed and if not, skip saving the server's admin data and stop the
sync without sending the real reply to the client.

Instead the client gets an empty message with "quitsync" as content
type. Then it takes shortcuts to close down without finalizing the
sync engine, because that would trigger writing of meta data
changes. The server continues its shutdown normally.

This optimization is limited to syncs with a single source, because
the assumption about when aborting is possible is harder to verify
when multiple sources are involved.
This commit is contained in:
Patrick Ohly 2014-08-29 11:27:07 +02:00
parent b985da7011
commit 8dca19a6cf
4 changed files with 90 additions and 2 deletions

View File

@ -150,6 +150,7 @@ void SyncContext::init()
m_serverAlerted = false;
m_configNeeded = true;
m_firstSourceAccess = true;
m_quitSync = false;
m_remoteInitiated = false;
m_sourceListPtr = NULL;
m_syncFreeze = SYNC_FREEZE_NONE;
@ -1961,6 +1962,7 @@ bool SyncContext::displaySourceProgress(SyncSource &source,
SE_LOG_INFO(NULL, "%s: sent %d",
source.getDisplayName().c_str(), event.m_extra1);
}
source.recordTotalNumItemsSent(event.m_extra1);
break;
// Not reached, see above.
case sysync::PEV_ITEMPROCESSED:
@ -3725,6 +3727,22 @@ bool SyncContext::setFreeze(bool freeze)
}
}
SyncMLStatus SyncContext::preSaveAdminData(SyncSource &source)
{
if (!source.getTotalNumItemsReceived() &&
!source.getTotalNumItemsSent() &&
source.getFinalSyncMode() == SYNC_TWO_WAY &&
!source.isFirstSync()) {
SE_LOG_DEBUG(NULL, "requesting end of two-way sync with one source early because nothing changed");
m_quitSync = true;
return STATUS_SYNC_END_SHORTCUT;
} else {
return STATUS_OK;
}
}
SharedSession *keepSession;
SyncMLStatus SyncContext::doSync()
{
boost::shared_ptr<SuspendFlags::Guard> signalGuard;
@ -3948,6 +3966,22 @@ SyncMLStatus SyncContext::doSync()
// (not needed for OBEX)
}
// Special case local sync when nothing changed: we can be sure
// that we can do another sync from exactly the same state (nonce,
// source change tracking meta data, etc.) and be successful
// again. In such a case we can avoid unnecessary updates of the
// .ini and .bfi files.
//
// To detect this, the server side hooks into the SaveAdminData
// operation and replaces it with just returning an "aborted by
// user" error.
if (m_serverMode &&
m_localSync &&
m_sourceListPtr->size() == 1) {
SyncSource *source = *(*m_sourceListPtr).begin();
source->getOperations().m_saveAdminData.getPreSignal().connect(boost::bind(&SyncContext::preSaveAdminData, this, _1));
}
// Sync main loop: runs until SessionStep() signals end or error.
// Exceptions are caught and lead to a call of SessionStep() with
// parameter STEPCMD_ABORT -> abort session as soon as possible.
@ -3957,8 +3991,19 @@ SyncMLStatus SyncContext::doSync()
int requestNum = 0;
sysync::uInt16 previousStepCmd = stepCmd;
std::vector<int> numItemsReceived; // source->getTotalNumItemsReceived() for each source, see STEPCMD_SENDDATA
m_quitSync = false;
do {
try {
if (m_quitSync &&
!m_serverMode) {
SE_LOG_DEBUG(NULL, "ending sync early as requested");
// Intentionally prevent destructing the Synthesis
// engine and session destruction by keeping a
// reference to it around forever, because destroying
// the session would cause undesired disk writes.
keepSession = new SharedSession(session);
break;
}
// check for suspend, if so, modify step command for next step
// Since the suspend will actually be committed until it is
// sending out a message, we can safely delay the suspend to
@ -4199,7 +4244,15 @@ SyncMLStatus SyncContext::doSync()
// sent or have it copied into caller's buffer using
// ReadSyncMLBuffer(), then send it to the server
sendBuffer = m_engine.GetSyncMLBuffer(session, true);
m_agent->send(sendBuffer.get(), sendBuffer.size());
if (m_serverMode && m_quitSync) {
// When aborting prematurely, skip the server's
// last reply message and instead tell the client
// to quit.
m_agent->setContentType("quitsync");
m_agent->send(NULL, 0);
} else {
m_agent->send(sendBuffer.get(), sendBuffer.size());
}
stepCmd = sysync::STEPCMD_SENTDATA; // we have sent the data
break;
}
@ -4270,6 +4323,18 @@ SyncMLStatus SyncContext::doSync()
}
stepCmd = sysync::STEPCMD_GOTDATA; // we have received response data
break;
} else if (contentType == "quitsync") {
SE_LOG_DEBUG(NULL, "server is asking us to quit the sync session");
// Fake "done" events for each active source.
BOOST_FOREACH(SyncSource *source, *m_sourceListPtr) {
if (source->getFinalSyncMode() != SYNC_NONE) {
displaySourceProgress(*source,
SyncSourceEvent(sysync::PEV_SYNCEND, 0, 0, 0),
true);
}
}
m_quitSync = true;
break;
} else {
SE_LOG_DEBUG(NULL, "unexpected content type '%s' in reply, %d bytes:\n%.*s",
contentType.c_str(), (int)replylen, (int)replylen, reply);

View File

@ -121,6 +121,9 @@ class SyncContext : public SyncConfig {
*/
SharedSession m_session;
/** Set to true in displaySourceProgress() when doSync() is expected to stop the sync early. */
bool m_quitSync;
/**
* installs session in SyncContext and removes it again
* when going out of scope
@ -664,6 +667,11 @@ class SyncContext : public SyncConfig {
*/
void initLocalSync(const string &config);
/**
* called via pre-signal of m_saveAdminData
*/
SyncMLStatus preSaveAdminData(SyncSource &source);
/**
* called via pre-signal of m_startDataRead
*/

View File

@ -365,6 +365,10 @@ std::string Status2String(SyncMLStatus status)
error = "proceeding would make backward incompatible changes, aborted";
break;
case STATUS_SYNC_END_SHORTCUT:
error = "intentionally skip sync session shutdown";
break;
case sysync::LOCERR_BADPROTO:
error = "bad or unknown protocol";
break;

View File

@ -296,6 +296,12 @@ enum SyncMLStatus {
*/
STATUS_MIGRATION_NEEDED = 22005,
/**
* Skip writing of admin data after a sync did not change
* anything.
*/
STATUS_SYNC_END_SHORTCUT = 22006,
STATUS_MAX = 0x7FFFFFF
};
@ -335,6 +341,7 @@ class SyncSourceReport {
SyncSourceReport() {
memset(m_stat, 0, sizeof(m_stat));
m_received = 0;
m_sent = 0;
m_first =
m_resume = false;
m_mode = SYNC_NONE;
@ -426,6 +433,10 @@ class SyncSourceReport {
void recordTotalNumItemsReceived(int received) { m_received = received; }
int getTotalNumItemsReceived() const { return m_received; }
/** counts each sent add/update/delete */
void recordTotalNumItemsSent(int sent) { m_sent = sent; }
int getTotalNumItemsSent() const { return m_sent; }
/**
* if not empty, then this was the virtual source which cause the
* current one to be included in the sync
@ -439,7 +450,7 @@ class SyncSourceReport {
private:
/** storage for getItemStat(): allow access with _MAX as index */
int m_stat[ITEM_LOCATION_MAX + 1][ITEM_STATE_MAX + 1][ITEM_RESULT_MAX + 1];
int m_received;
int m_received, m_sent;
SyncMode m_mode;
int m_restarts;