From 904b5a39206c56f1e1d3018a1177b7a891708d2e Mon Sep 17 00:00:00 2001 From: M66B Date: Fri, 7 Feb 2020 22:07:16 +0100 Subject: [PATCH] Retry failed operations --- app/src/main/java/eu/faircode/email/Core.java | 80 +++++++++++-------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index e30021948c..df91a26aa1 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -126,33 +126,33 @@ class Core { private static final int DOWNLOAD_BATCH_SIZE = 20; private static final long YIELD_DURATION = 200L; // milliseconds private static final long FUTURE_RECEIVED = 30 * 24 * 3600 * 1000L; // milliseconds + private static final int OPERATION_RETRY_MAX = 10; + private static final long OPERATION_RETRY_DELAY = 15 * 1000L; // milliseconds static void processOperations( Context context, EntityAccount account, EntityFolder folder, List ops, Store istore, Folder ifolder, State state) - throws MessagingException, JSONException, IOException { + throws JSONException { try { Log.i(folder.name + " start process"); DB db = DB.getInstance(context); - List processed = new ArrayList<>(); + int retry = 0; + boolean group = true; Log.i(folder.name + " executing operations=" + ops.size()); - for (int i = 0; i < ops.size() && state.isRunning() && state.isRecoverable(); i++) { - EntityOperation op = ops.get(i); - if (processed.contains(op.id)) { - Log.i(folder.name + " already processed op=" + op.id + "/" + op.name); - continue; - } + while (retry < OPERATION_RETRY_MAX && ops.size() > 0 && state.isRunning() && state.isRecoverable()) { + TupleOperationEx op = ops.get(0); try { Log.i(folder.name + " start op=" + op.id + "/" + op.name + " folder=" + op.folder + " msg=" + op.message + - " args=" + op.args); + " args=" + op.args + + " retry=" + retry); // Fetch most recent copy of message EntityMessage message = null; @@ -160,7 +160,7 @@ class Core { message = db.message().getMessage(op.message); JSONArray jargs = new JSONArray(op.args); - Map similar = new HashMap<>(); + Map similar = new HashMap<>(); try { // Operations should use database transaction when needed @@ -173,8 +173,8 @@ class Core { // Process similar operations boolean skip = false; - for (int j = i + 1; j < ops.size(); j++) { - EntityOperation next = ops.get(j); + for (int j = 1; j < ops.size(); j++) { + TupleOperationEx next = ops.get(j); switch (op.name) { case EntityOperation.ADD: @@ -195,16 +195,15 @@ class Core { break; case EntityOperation.MOVE: - if (message.uid != null && + if (group && + message.uid != null && EntityOperation.MOVE.equals(next.name)) { JSONArray jnext = new JSONArray(next.args); // Same target if (jargs.getLong(0) == jnext.getLong(0)) { EntityMessage m = db.message().getMessage(next.message); - if (m != null && m.uid != null) { - processed.add(next.id); + if (m != null && m.uid != null) similar.put(next, m); - } } } break; @@ -216,11 +215,12 @@ class Core { " skipping op=" + op.id + "/" + op.name + " msg=" + op.message + " args=" + op.args); db.operation().deleteOperation(op.id); + ops.remove(op); continue; } List sids = new ArrayList<>(); - for (EntityOperation s : similar.keySet()) + for (TupleOperationEx s : similar.keySet()) sids.add(s.id); if (similar.size() > 0) @@ -242,7 +242,7 @@ class Core { db.beginTransaction(); db.operation().setOperationError(op.id, null); - for (EntityOperation s : similar.keySet()) + for (TupleOperationEx s : similar.keySet()) db.operation().setOperationError(s.id, null); if (message != null) { @@ -253,7 +253,7 @@ class Core { if (!EntityOperation.SYNC.equals(op.name)) { db.operation().setOperationState(op.id, "executing"); - for (EntityOperation s : similar.keySet()) + for (TupleOperationEx s : similar.keySet()) db.operation().setOperationState(s.id, "executing"); } @@ -374,13 +374,17 @@ class Core { db.beginTransaction(); db.operation().deleteOperation(op.id); - for (EntityOperation s : similar.keySet()) + for (TupleOperationEx s : similar.keySet()) db.operation().deleteOperation(s.id); db.setTransactionSuccessful(); } finally { db.endTransaction(); } + + ops.remove(op); + for (TupleOperationEx s : similar.keySet()) + ops.remove(s); } catch (Throwable ex) { Log.e(folder.name, ex); EntityLog.log(context, folder.name + " " + Log.formatThrowable(ex, false)); @@ -389,7 +393,7 @@ class Core { db.beginTransaction(); db.operation().setOperationError(op.id, Log.formatThrowable(ex)); - for (EntityOperation s : similar.keySet()) + for (TupleOperationEx s : similar.keySet()) db.operation().setOperationError(s.id, Log.formatThrowable(ex)); if (message != null && !(ex instanceof IllegalArgumentException)) { @@ -406,6 +410,12 @@ class Core { db.endTransaction(); } + if (similar.size() > 0) { + // Retry individually + group = false; + continue; + } + if (ex instanceof OutOfMemoryError || ex instanceof MessageRemovedException || ex instanceof MessageRemovedIOException || @@ -433,8 +443,6 @@ class Core { // There is no use in repeating db.operation().deleteOperation(op.id); - for (EntityOperation s : similar.keySet()) - db.operation().deleteOperation(s.id); // Cleanup folder if (EntityOperation.SYNC.equals(op.name)) @@ -445,34 +453,32 @@ class Core { (ex instanceof MessageRemovedException || ex instanceof MessageRemovedIOException || ex.getCause() instanceof MessageRemovedException || - ex.getCause() instanceof MessageRemovedIOException)) { - // Failsafe: retry batch - if (similar.size() > 0) - throw ex; - + ex.getCause() instanceof MessageRemovedIOException)) db.message().deleteMessage(message.id); - } // Cleanup operations op.cleanup(context); - for (EntityOperation s : similar.keySet()) - s.cleanup(context); db.setTransactionSuccessful(); } finally { db.endTransaction(); } - continue; + ops.remove(op); + } else { + retry++; + try { + Thread.sleep(OPERATION_RETRY_DELAY); + } catch (InterruptedException ex1) { + Log.w(ex1); + } } - - throw ex; } finally { try { db.beginTransaction(); db.operation().setOperationState(op.id, null); - for (EntityOperation s : similar.keySet()) + for (TupleOperationEx s : similar.keySet()) db.operation().setOperationState(s.id, null); db.setTransactionSuccessful(); @@ -484,6 +490,10 @@ class Core { Log.i(folder.name + " end op=" + op.id + "/" + op.name); } } + + if (ops.size() > 0) + Log.w("Operations failed=" + ops.size()); + } finally { Log.i(folder.name + " end process state=" + state); }