made i/o and memory intensive operations execute in serial order

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java   | 29 
src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java | 34 
2 files changed, 52 insertions(+), 11 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -77,6 +77,7 @@ import eu.siacs.conversations.utils.ExceptionHelper;
 import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
 import eu.siacs.conversations.utils.PRNGFixes;
 import eu.siacs.conversations.utils.PhoneHelper;
+import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
 import eu.siacs.conversations.utils.Xmlns;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.OnBindListener;
@@ -119,6 +120,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 			startService(intent);
 		}
 	};
+
+	private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor();
+	private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor();
+
 	private final IBinder mBinder = new XmppConnectionBinder();
 	private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
 	private final FileObserver fileObserver = new FileObserver(
@@ -373,7 +378,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 				callback.success(message);
 			}
 		} else {
-			new Thread(new Runnable() {
+			mFileAddingExecutor.execute(new Runnable() {
 				@Override
 				public void run() {
 					try {
@@ -388,8 +393,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 						callback.error(e.getResId(),message);
 					}
 				}
-			}).start();
-
+			});
 		}
 	}
 
@@ -405,7 +409,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 		message.setCounterpart(conversation.getNextCounterpart());
 		message.setType(Message.TYPE_IMAGE);
-		new Thread(new Runnable() {
+		mFileAddingExecutor.execute(new Runnable() {
 
 			@Override
 			public void run() {
@@ -420,7 +424,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 					callback.error(e.getResId(), message);
 				}
 			}
-		}).start();
+		});
 	}
 
 	public Conversation find(Bookmark bookmark) {
@@ -976,7 +980,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 				Account account = accountLookupTable.get(conversation.getAccountUuid());
 				conversation.setAccount(account);
 			}
-			new Thread(new Runnable() {
+			Runnable runnable =new Runnable() {
 				@Override
 				public void run() {
 					Log.d(Config.LOGTAG,"restoring roster");
@@ -997,7 +1001,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 					Log.d(Config.LOGTAG,"restored all messages");
 					updateConversationUi();
 				}
-			}).start();
+			};
+			mDatabaseExecutor.execute(runnable);
 		}
 	}
 
@@ -1066,7 +1071,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) {
 			return;
 		}
-		new Thread(new Runnable() {
+		Runnable runnable = new Runnable() {
 			@Override
 			public void run() {
 				final Account account = conversation.getAccount();
@@ -1085,7 +1090,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 					callback.informUser(R.string.fetching_history_from_server);
 				}
 			}
-		}).start();
+		};
+		mDatabaseExecutor.execute(runnable);
 	}
 
 	public List<Account> getAccounts() {
@@ -2344,13 +2350,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 	}
 
 	public void syncRosterToDisk(final Account account) {
-		new Thread(new Runnable() {
+		Runnable runnable = new Runnable() {
 
 			@Override
 			public void run() {
 				databaseBackend.writeRoster(account.getRoster());
 			}
-		}).start();
+		};
+		mDatabaseExecutor.execute(runnable);
 
 	}
 

src/main/java/eu/siacs/conversations/utils/SerialSingleThreadExecutor.java 🔗

@@ -0,0 +1,34 @@
+package eu.siacs.conversations.utils;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class SerialSingleThreadExecutor implements Executor {
+
+	final Executor executor = Executors.newSingleThreadExecutor();
+	final Queue<Runnable> tasks = new ArrayDeque();
+	Runnable active;
+
+	public synchronized void execute(final Runnable r) {
+		tasks.offer(new Runnable() {
+			public void run() {
+				try {
+					r.run();
+				} finally {
+					scheduleNext();
+				}
+			}
+		});
+		if (active == null) {
+			scheduleNext();
+		}
+	}
+
+	protected synchronized void scheduleNext() {
+		if ((active =  tasks.poll()) != null) {
+			executor.execute(active);
+		}
+	}
+}