mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Various JobManager performance improvements.
This commit is contained in:
@@ -77,6 +77,11 @@ class JobController {
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
synchronized void flush() {
|
||||
jobStorage.flush();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
synchronized void submitNewJobChain(@NonNull List<List<Job>> chain) {
|
||||
chain = Stream.of(chain).filterNot(List::isEmpty).toList();
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.jobmanager;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
@@ -14,6 +16,8 @@ import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.NonMainThreadExecutor;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -26,9 +30,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -41,18 +43,21 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
|
||||
public static final int CURRENT_VERSION = 7;
|
||||
|
||||
private final Application application;
|
||||
private final Configuration configuration;
|
||||
private final ExecutorService executor;
|
||||
private final JobController jobController;
|
||||
private final JobTracker jobTracker;
|
||||
private final Application application;
|
||||
private final Configuration configuration;
|
||||
private final Executor executor;
|
||||
private final JobController jobController;
|
||||
private final JobTracker jobTracker;
|
||||
|
||||
@GuardedBy("emptyQueueListeners")
|
||||
private final Set<EmptyQueueListener> emptyQueueListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
private volatile boolean initialized;
|
||||
|
||||
public JobManager(@NonNull Application application, @NonNull Configuration configuration) {
|
||||
this.application = application;
|
||||
this.configuration = configuration;
|
||||
this.executor = configuration.getExecutorFactory().newSingleThreadExecutor("signal-JobManager");
|
||||
this.executor = new NonMainThreadExecutor(configuration.getExecutorFactory().newSingleThreadExecutor("signal-JobManager"));
|
||||
this.jobTracker = configuration.getJobTracker();
|
||||
this.jobController = new JobController(application,
|
||||
configuration.getJobStorage(),
|
||||
@@ -66,25 +71,30 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
this::onEmptyQueue);
|
||||
|
||||
executor.execute(() -> {
|
||||
if (WorkManagerMigrator.needsMigration(application)) {
|
||||
Log.i(TAG, "Detected an old WorkManager database. Migrating.");
|
||||
WorkManagerMigrator.migrate(application, configuration.getJobStorage(), configuration.getDataSerializer());
|
||||
}
|
||||
synchronized (this) {
|
||||
if (WorkManagerMigrator.needsMigration(application)) {
|
||||
Log.i(TAG, "Detected an old WorkManager database. Migrating.");
|
||||
WorkManagerMigrator.migrate(application, configuration.getJobStorage(), configuration.getDataSerializer());
|
||||
}
|
||||
|
||||
JobStorage jobStorage = configuration.getJobStorage();
|
||||
jobStorage.init();
|
||||
JobStorage jobStorage = configuration.getJobStorage();
|
||||
jobStorage.init();
|
||||
|
||||
int latestVersion = configuration.getJobMigrator().migrate(jobStorage, configuration.getDataSerializer());
|
||||
TextSecurePreferences.setJobManagerVersion(application, latestVersion);
|
||||
int latestVersion = configuration.getJobMigrator().migrate(jobStorage, configuration.getDataSerializer());
|
||||
TextSecurePreferences.setJobManagerVersion(application, latestVersion);
|
||||
|
||||
jobController.init();
|
||||
jobController.init();
|
||||
|
||||
for (ConstraintObserver constraintObserver : configuration.getConstraintObservers()) {
|
||||
constraintObserver.register(this);
|
||||
}
|
||||
for (ConstraintObserver constraintObserver : configuration.getConstraintObservers()) {
|
||||
constraintObserver.register(this);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
application.startService(new Intent(application, KeepAliveService.class));
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
application.startService(new Intent(application, KeepAliveService.class));
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
notifyAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -93,11 +103,11 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
* Begins the execution of jobs.
|
||||
*/
|
||||
public void beginJobLoop() {
|
||||
executor.execute(() -> {
|
||||
runOnExecutor(()-> {
|
||||
for (int i = 0; i < configuration.getJobThreadCount(); i++) {
|
||||
new JobRunner(application, i + 1, jobController).start();
|
||||
}
|
||||
wakeUp();
|
||||
jobController.wakeUp();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -138,9 +148,9 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
public void add(@NonNull Job job, @NonNull Collection<String> dependsOn) {
|
||||
jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
|
||||
|
||||
executor.execute(() -> {
|
||||
runOnExecutor(() -> {
|
||||
jobController.submitJobWithExistingDependencies(job, dependsOn, null);
|
||||
wakeUp();
|
||||
jobController.wakeUp();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -151,9 +161,9 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
public void add(@NonNull Job job, @Nullable String dependsOnQueue) {
|
||||
jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
|
||||
|
||||
executor.execute(() -> {
|
||||
runOnExecutor(() -> {
|
||||
jobController.submitJobWithExistingDependencies(job, Collections.emptyList(), dependsOnQueue);
|
||||
wakeUp();
|
||||
jobController.wakeUp();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -164,9 +174,9 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
public void add(@NonNull Job job, @NonNull Collection<String> dependsOn, @Nullable String dependsOnQueue) {
|
||||
jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
|
||||
|
||||
executor.execute(() -> {
|
||||
runOnExecutor(() -> {
|
||||
jobController.submitJobWithExistingDependencies(job, dependsOn, dependsOnQueue);
|
||||
wakeUp();
|
||||
jobController.wakeUp();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,14 +205,14 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
* moment. Just like a normal failure, all later jobs in the same chain will also be failed.
|
||||
*/
|
||||
public void cancel(@NonNull String id) {
|
||||
executor.execute(() -> jobController.cancelJob(id));
|
||||
runOnExecutor(() -> jobController.cancelJob(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all jobs in the specified queue. See {@link #cancel(String)} for details.
|
||||
*/
|
||||
public void cancelAllInQueue(@NonNull String queue) {
|
||||
executor.execute(() -> jobController.cancelAllInQueue(queue));
|
||||
runOnExecutor(() -> jobController.cancelAllInQueue(queue));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,10 +257,22 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
*/
|
||||
@WorkerThread
|
||||
public @NonNull String getDebugInfo() {
|
||||
Future<String> result = executor.submit(jobController::getDebugInfo);
|
||||
AtomicReference<String> result = new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
runOnExecutor(() -> {
|
||||
result.set(jobController.getDebugInfo());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
try {
|
||||
return result.get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
boolean finished = latch.await(10, TimeUnit.SECONDS);
|
||||
if (finished) {
|
||||
return result.get();
|
||||
} else {
|
||||
return "Timed out waiting for Job info.";
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Failed to retrieve Job info.", e);
|
||||
return "Failed to retrieve Job info.";
|
||||
}
|
||||
@@ -260,8 +282,10 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
* Adds a listener that will be notified when the job queue has been drained.
|
||||
*/
|
||||
void addOnEmptyQueueListener(@NonNull EmptyQueueListener listener) {
|
||||
executor.execute(() -> {
|
||||
emptyQueueListeners.add(listener);
|
||||
runOnExecutor(() -> {
|
||||
synchronized (emptyQueueListeners) {
|
||||
emptyQueueListeners.add(listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -269,8 +293,10 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
* Removes a listener that was added via {@link #addOnEmptyQueueListener(EmptyQueueListener)}.
|
||||
*/
|
||||
void removeOnEmptyQueueListener(@NonNull EmptyQueueListener listener) {
|
||||
executor.execute(() -> {
|
||||
emptyQueueListeners.remove(listener);
|
||||
runOnExecutor(() -> {
|
||||
synchronized (emptyQueueListeners) {
|
||||
emptyQueueListeners.remove(listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -280,11 +306,31 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
wakeUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until all pending operations are finished.
|
||||
*/
|
||||
@WorkerThread
|
||||
public void flush() {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
runOnExecutor(() -> {
|
||||
jobController.flush();
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
Log.i(TAG, "Successfully flushed.");
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Failed to finish flushing.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pokes the system to take another pass at the job queue.
|
||||
*/
|
||||
void wakeUp() {
|
||||
executor.execute(jobController::wakeUp);
|
||||
runOnExecutor(jobController::wakeUp);
|
||||
}
|
||||
|
||||
private void enqueueChain(@NonNull Chain chain) {
|
||||
@@ -294,20 +340,46 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
}
|
||||
}
|
||||
|
||||
executor.execute(() -> {
|
||||
runOnExecutor(() -> {
|
||||
jobController.submitNewJobChain(chain.getJobListChain());
|
||||
wakeUp();
|
||||
jobController.wakeUp();
|
||||
});
|
||||
}
|
||||
|
||||
private void onEmptyQueue() {
|
||||
executor.execute(() -> {
|
||||
for (EmptyQueueListener listener : emptyQueueListeners) {
|
||||
listener.onQueueEmpty();
|
||||
runOnExecutor(() -> {
|
||||
synchronized (emptyQueueListeners) {
|
||||
for (EmptyQueueListener listener : emptyQueueListeners) {
|
||||
listener.onQueueEmpty();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Anything that you want to ensure happens off of the main thread and after initialization, run
|
||||
* it through here.
|
||||
*/
|
||||
private void runOnExecutor(@NonNull Runnable runnable) {
|
||||
executor.execute(() -> {
|
||||
waitUntilInitialized();
|
||||
runnable.run();
|
||||
});
|
||||
}
|
||||
|
||||
private void waitUntilInitialized() {
|
||||
if (!initialized) {
|
||||
Log.i(TAG, "Waiting for initialization...");
|
||||
synchronized (this) {
|
||||
while (!initialized) {
|
||||
Util.wait(this, 0);
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Initialization complete.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface EmptyQueueListener {
|
||||
void onQueueEmpty();
|
||||
}
|
||||
|
||||
@@ -13,10 +13,18 @@ public class WebsocketDrainedConstraintObserver implements ConstraintObserver {
|
||||
|
||||
private static final String REASON = WebsocketDrainedConstraintObserver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void register(@NonNull Notifier notifier) {
|
||||
private volatile Notifier notifier;
|
||||
|
||||
public WebsocketDrainedConstraintObserver() {
|
||||
ApplicationDependencies.getInitialMessageRetriever().addListener(() -> {
|
||||
notifier.onConstraintMet(REASON);
|
||||
if (notifier != null) {
|
||||
notifier.onConstraintMet(REASON);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(@NonNull Notifier notifier) {
|
||||
this.notifier = notifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ public interface JobStorage {
|
||||
@WorkerThread
|
||||
void init();
|
||||
|
||||
@WorkerThread
|
||||
void flush();
|
||||
|
||||
@WorkerThread
|
||||
void insertJobs(@NonNull List<FullSpec> fullSpecs);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user