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
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index e5b9919..694f6b9 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -158,6 +158,10 @@
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 @@
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 @@
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 1e0aff5..6473000 100644
--- a/src/com/android/exchange/adapter/AbstractSyncAdapter.java
+++ b/src/com/android/exchange/adapter/AbstractSyncAdapter.java
@@ -57,6 +57,10 @@
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 97dfb50..af17b79 100644
--- a/src/com/android/exchange/adapter/AbstractSyncParser.java
+++ b/src/com/android/exchange/adapter/AbstractSyncParser.java
@@ -45,6 +45,8 @@
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 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 @@
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 @@
// 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 dc4f204..4e478b5 100644
--- a/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -81,6 +81,9 @@
ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
+ // Holds the parser's value for isLooping()
+ boolean mIsLooping = false;
+
public EmailSyncAdapter(Mailbox mailbox, EasSyncService service) {
super(mailbox, service);
}
@@ -88,7 +91,18 @@
@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