From 274492db09d464879903debf6645443b9be9a957 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Wed, 19 May 2010 09:50:09 -0700 Subject: [PATCH] Allow limited looping requests in sync * Microsoft has documented cases in which the server can continue to send MoreAvailable=true even when no new data is received. This can cause looping behavior, which we stop when we recognize it. * This workaround, however, can prevent the situation from resolving itself, and lead to delayed sync (up to a few hours has been noticed) * In this limited CL, we allow the sync to loop up to a maximum number of times before stopping it forcibly. Bug: 2685984 Change-Id: I2913b7e3438f6180c3c440508fab892176a06540 --- src/com/android/exchange/EasSyncService.java | 17 +++++++++++++++++ .../exchange/adapter/AbstractSyncAdapter.java | 4 ++++ .../exchange/adapter/AbstractSyncParser.java | 11 ++++++++--- .../exchange/adapter/EmailSyncAdapter.java | 16 +++++++++++++++- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index e5b9919c0..694f6b938 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -158,6 +158,10 @@ public class EasSyncService extends AbstractSyncService { static private final int PING_HEARTBEAT_INCREMENT = 3*PING_MINUTES; static private final int PING_FORCE_HEARTBEAT = 2*PING_MINUTES; + // Maximum number of times we'll allow a sync to "loop" with MoreAvailable true before + // forcing it to stop. This number has been determined empirically. + static private final int MAX_LOOPING_COUNT = 100; + static private final int PROTOCOL_PING_STATUS_COMPLETED = 1; // The amount of time we allow for a thread to release its post lock after receiving an alert @@ -1926,6 +1930,7 @@ public class EasSyncService extends AbstractSyncService { Mailbox mailbox = target.mMailbox; boolean moreAvailable = true; + int loopingCount = 0; while (!mStop && moreAvailable) { // If we have no connectivity, just exit cleanly. SyncManager will start us up again // when connectivity has returned @@ -2025,6 +2030,18 @@ public class EasSyncService extends AbstractSyncService { InputStream is = resp.getEntity().getContent(); if (is != null) { moreAvailable = target.parse(is); + if (target.isLooping()) { + loopingCount++; + userLog("** Looping: " + loopingCount); + // After the maximum number of loops, we'll set moreAvailable to false and + // allow the sync loop to terminate + if (moreAvailable && (loopingCount > MAX_LOOPING_COUNT)) { + userLog("** Looping force stopped"); + moreAvailable = false; + } + } else { + loopingCount = 0; + } target.cleanup(); } else { userLog("Empty input stream in sync command response"); diff --git a/src/com/android/exchange/adapter/AbstractSyncAdapter.java b/src/com/android/exchange/adapter/AbstractSyncAdapter.java index 1e0aff5eb..6473000cf 100644 --- a/src/com/android/exchange/adapter/AbstractSyncAdapter.java +++ b/src/com/android/exchange/adapter/AbstractSyncAdapter.java @@ -57,6 +57,10 @@ public abstract class AbstractSyncAdapter { public abstract void cleanup(); public abstract boolean isSyncable(); + public boolean isLooping() { + return false; + } + public AbstractSyncAdapter(Mailbox mailbox, EasSyncService service) { mMailbox = mailbox; mService = service; diff --git a/src/com/android/exchange/adapter/AbstractSyncParser.java b/src/com/android/exchange/adapter/AbstractSyncParser.java index 97dfb5011..af17b7987 100644 --- a/src/com/android/exchange/adapter/AbstractSyncParser.java +++ b/src/com/android/exchange/adapter/AbstractSyncParser.java @@ -45,6 +45,8 @@ public abstract class AbstractSyncParser extends Parser { protected ContentResolver mContentResolver; protected AbstractSyncAdapter mAdapter; + private boolean mLooping; + public AbstractSyncParser(InputStream in, AbstractSyncAdapter adapter) throws IOException { super(in); mAdapter = adapter; @@ -78,6 +80,10 @@ public abstract class AbstractSyncParser extends Parser { */ public abstract void wipe(); + public boolean isLooping() { + return mLooping; + } + /** * Loop through the top-level structure coming from the Exchange server * Sync keys and the more available flag are handled here, whereas specific data parsing @@ -89,7 +95,7 @@ public abstract class AbstractSyncParser extends Parser { boolean moreAvailable = false; boolean newSyncKey = false; int interval = mMailbox.mSyncInterval; - + mLooping = false; // If we're not at the top of the xml tree, throw an exception if (nextTag(START_DOCUMENT) != Tags.SYNC_SYNC) { throw new EasParserException(); @@ -154,8 +160,7 @@ public abstract class AbstractSyncParser extends Parser { // If we don't have a new sync key, ignore moreAvailable (or we'll loop) if (moreAvailable && !newSyncKey) { - userLog("!! SyncKey hasn't changed, setting moreAvailable = false"); - moreAvailable = false; + mLooping = true; } // Commit any changes diff --git a/src/com/android/exchange/adapter/EmailSyncAdapter.java b/src/com/android/exchange/adapter/EmailSyncAdapter.java index dc4f204f9..4e478b5ab 100644 --- a/src/com/android/exchange/adapter/EmailSyncAdapter.java +++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java @@ -81,6 +81,9 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { ArrayList mDeletedIdList = new ArrayList(); ArrayList mUpdatedIdList = new ArrayList(); + // Holds the parser's value for isLooping() + boolean mIsLooping = false; + public EmailSyncAdapter(Mailbox mailbox, EasSyncService service) { super(mailbox, service); } @@ -88,7 +91,18 @@ public class EmailSyncAdapter extends AbstractSyncAdapter { @Override public boolean parse(InputStream is) throws IOException { EasEmailSyncParser p = new EasEmailSyncParser(is, this); - return p.parse(); + boolean res = p.parse(); + // Hold on to the parser's value for isLooping() to pass back to the service + mIsLooping = p.isLooping(); + return res; + } + + /** + * Return the value of isLooping() as returned from the parser + */ + @Override + public boolean isLooping() { + return mIsLooping; } @Override