Prevent FCM bottlenecking.

This commit is contained in:
Greyson Parrelli
2020-05-26 11:58:34 -04:00
parent 4cda267f3b
commit fe25d941bb
6 changed files with 167 additions and 66 deletions

View File

@@ -0,0 +1,64 @@
package org.thoughtcrime.securesms.util.concurrent;
import androidx.annotation.NonNull;
import java.util.concurrent.Executor;
/**
* Wraps another executor to make a new executor that only keeps around two tasks:
* - The actively running task
* - A single enqueued task
*
* If multiple tasks are enqueued while one is running, only the latest task is kept. The rest are
* dropped.
*
* This is useful when you want to enqueue a bunch of tasks at unknown intervals, but only the most
* recent one is relevant. For instance, running a query in response to changing user input.
*
* Based on SerialExecutor
* https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html
*/
public final class SerialMonoLifoExecutor implements Executor {
private final Executor executor;
private Runnable next;
private Runnable active;
public SerialMonoLifoExecutor(@NonNull Executor executor) {
this.executor = executor;
}
@Override
public synchronized void execute(@NonNull Runnable command) {
enqueue(command);
}
/**
* @return True if a pending task was replaced by this one, otherwise false.
*/
public synchronized boolean enqueue(@NonNull Runnable command) {
boolean performedReplace = next != null;
next = () -> {
try {
command.run();
} finally {
scheduleNext();
}
};
if (active == null) {
scheduleNext();
}
return performedReplace;
}
private synchronized void scheduleNext() {
active = next;
next = null;
if (active != null) {
executor.execute(active);
}
}
}

View File

@@ -7,6 +7,7 @@ import androidx.lifecycle.MutableLiveData;
import com.annimon.stream.function.Predicate;
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.whispersystems.libsignal.util.guava.Function;
@@ -57,7 +58,7 @@ public final class LiveDataUtil {
*/
public static <A, B> LiveData<B> mapAsync(@NonNull Executor executor, @NonNull LiveData<A> source, @NonNull Function<A, B> backgroundFunction) {
MediatorLiveData<B> outputLiveData = new MediatorLiveData<>();
Executor liveDataExecutor = new SerialLiveDataExecutor(executor);
Executor liveDataExecutor = new SerialMonoLifoExecutor(executor);
outputLiveData.addSource(source, currentValue -> liveDataExecutor.execute(() -> outputLiveData.postValue(backgroundFunction.apply(currentValue))));
@@ -119,42 +120,4 @@ public final class LiveDataUtil {
}
}
}
/**
* Executor decorator that runs serially but enqueues just the latest task, dropping any pending task.
* <p>
* Based on SerialExecutor https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html
* but modified to represent a queue of size one which is replaced by the latest call to {@link #execute(Runnable)}.
*/
private static final class SerialLiveDataExecutor implements Executor {
private final Executor executor;
private Runnable next;
private Runnable active;
SerialLiveDataExecutor(@NonNull Executor executor) {
this.executor = executor;
}
public synchronized void execute(@NonNull Runnable command) {
next = () -> {
try {
command.run();
} finally {
scheduleNext();
}
};
if (active == null) {
scheduleNext();
}
}
private synchronized void scheduleNext() {
active = next;
next = null;
if (active != null) {
executor.execute(active);
}
}
}
}