From 87a59b6a9bf4ff90a5776d8b48446c854d5dd9c7 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 25 Jun 2020 05:32:36 -0700 Subject: [PATCH] Add support for memory-only jobs. --- .../securesms/database/JobDatabase.java | 91 ++++++---- .../securesms/jobmanager/Job.java | 31 +++- .../securesms/jobmanager/JobController.java | 17 +- .../securesms/jobmanager/JobMigrator.java | 3 +- .../persistence/ConstraintSpec.java | 21 ++- .../persistence/DependencySpec.java | 21 ++- .../jobmanager/persistence/FullSpec.java | 3 + .../jobmanager/persistence/JobSpec.java | 16 +- .../workmanager/WorkManagerDatabase.java | 3 +- .../securesms/jobs/FastJobStorage.java | 77 +++++++-- .../securesms/jobs/TypingSendJob.java | 5 + .../securesms/jobmanager/JobMigratorTest.java | 2 +- .../securesms/jobs/FastJobStorageTest.java | 157 ++++++++++++------ 13 files changed, 314 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java index 55f15345de..825536955e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -5,6 +5,8 @@ import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; +import com.annimon.stream.Stream; + import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -89,6 +91,10 @@ public class JobDatabase extends Database { } public synchronized void insertJobs(@NonNull List fullSpecs) { + if (Stream.of(fullSpecs).map(FullSpec::getJobSpec).allMatch(JobSpec::isMemoryOnly)) { + return; + } + SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.beginTransaction(); @@ -149,32 +155,38 @@ public class JobDatabase extends Database { } public synchronized void updateJobs(@NonNull List jobs) { + if (Stream.of(jobs).allMatch(JobSpec::isMemoryOnly)) { + return; + } + SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.beginTransaction(); try { - for (JobSpec job : jobs) { - ContentValues values = new ContentValues(); - values.put(Jobs.JOB_SPEC_ID, job.getId()); - values.put(Jobs.FACTORY_KEY, job.getFactoryKey()); - values.put(Jobs.QUEUE_KEY, job.getQueueKey()); - values.put(Jobs.CREATE_TIME, job.getCreateTime()); - values.put(Jobs.NEXT_RUN_ATTEMPT_TIME, job.getNextRunAttemptTime()); - values.put(Jobs.RUN_ATTEMPT, job.getRunAttempt()); - values.put(Jobs.MAX_ATTEMPTS, job.getMaxAttempts()); - values.put(Jobs.MAX_BACKOFF, job.getMaxBackoff()); - values.put(Jobs.MAX_INSTANCES, job.getMaxInstances()); - values.put(Jobs.LIFESPAN, job.getLifespan()); - values.put(Jobs.SERIALIZED_DATA, job.getSerializedData()); - values.put(Jobs.SERIALIZED_INPUT_DATA, job.getSerializedInputData()); - values.put(Jobs.IS_RUNNING, job.isRunning() ? 1 : 0); + Stream.of(jobs) + .filterNot(JobSpec::isMemoryOnly) + .forEach(job -> { + ContentValues values = new ContentValues(); + values.put(Jobs.JOB_SPEC_ID, job.getId()); + values.put(Jobs.FACTORY_KEY, job.getFactoryKey()); + values.put(Jobs.QUEUE_KEY, job.getQueueKey()); + values.put(Jobs.CREATE_TIME, job.getCreateTime()); + values.put(Jobs.NEXT_RUN_ATTEMPT_TIME, job.getNextRunAttemptTime()); + values.put(Jobs.RUN_ATTEMPT, job.getRunAttempt()); + values.put(Jobs.MAX_ATTEMPTS, job.getMaxAttempts()); + values.put(Jobs.MAX_BACKOFF, job.getMaxBackoff()); + values.put(Jobs.MAX_INSTANCES, job.getMaxInstances()); + values.put(Jobs.LIFESPAN, job.getLifespan()); + values.put(Jobs.SERIALIZED_DATA, job.getSerializedData()); + values.put(Jobs.SERIALIZED_INPUT_DATA, job.getSerializedInputData()); + values.put(Jobs.IS_RUNNING, job.isRunning() ? 1 : 0); - String query = Jobs.JOB_SPEC_ID + " = ?"; - String[] args = new String[]{ job.getId() }; + String query = Jobs.JOB_SPEC_ID + " = ?"; + String[] args = new String[]{ job.getId() }; - db.update(Jobs.TABLE_NAME, values, query, args); - } + db.update(Jobs.TABLE_NAME, values, query, args); + }); db.setTransactionSuccessful(); } finally { @@ -228,6 +240,10 @@ public class JobDatabase extends Database { } private void insertJobSpec(@NonNull SQLiteDatabase db, @NonNull JobSpec job) { + if (job.isMemoryOnly()) { + return; + } + ContentValues contentValues = new ContentValues(); contentValues.put(Jobs.JOB_SPEC_ID, job.getId()); contentValues.put(Jobs.FACTORY_KEY, job.getFactoryKey()); @@ -247,21 +263,25 @@ public class JobDatabase extends Database { } private void insertConstraintSpecs(@NonNull SQLiteDatabase db, @NonNull List constraints) { - for (ConstraintSpec constraintSpec : constraints) { - ContentValues contentValues = new ContentValues(); - contentValues.put(Constraints.JOB_SPEC_ID, constraintSpec.getJobSpecId()); - contentValues.put(Constraints.FACTORY_KEY, constraintSpec.getFactoryKey()); - db.insertWithOnConflict(Constraints.TABLE_NAME, null ,contentValues, SQLiteDatabase.CONFLICT_IGNORE); - } + Stream.of(constraints) + .filterNot(ConstraintSpec::isMemoryOnly) + .forEach(constraintSpec -> { + ContentValues contentValues = new ContentValues(); + contentValues.put(Constraints.JOB_SPEC_ID, constraintSpec.getJobSpecId()); + contentValues.put(Constraints.FACTORY_KEY, constraintSpec.getFactoryKey()); + db.insertWithOnConflict(Constraints.TABLE_NAME, null ,contentValues, SQLiteDatabase.CONFLICT_IGNORE); + }); } private void insertDependencySpecs(@NonNull SQLiteDatabase db, @NonNull List dependencies) { - for (DependencySpec dependencySpec : dependencies) { - ContentValues contentValues = new ContentValues(); - contentValues.put(Dependencies.JOB_SPEC_ID, dependencySpec.getJobId()); - contentValues.put(Dependencies.DEPENDS_ON_JOB_SPEC_ID, dependencySpec.getDependsOnJobId()); - db.insertWithOnConflict(Dependencies.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); - } + Stream.of(dependencies) + .filterNot(DependencySpec::isMemoryOnly) + .forEach(dependencySpec -> { + ContentValues contentValues = new ContentValues(); + contentValues.put(Dependencies.JOB_SPEC_ID, dependencySpec.getJobId()); + contentValues.put(Dependencies.DEPENDS_ON_JOB_SPEC_ID, dependencySpec.getDependsOnJobId()); + db.insertWithOnConflict(Dependencies.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); + }); } private @NonNull JobSpec jobSpecFromCursor(@NonNull Cursor cursor) { @@ -277,16 +297,19 @@ public class JobDatabase extends Database { cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_INSTANCES)), cursor.getString(cursor.getColumnIndexOrThrow(Jobs.SERIALIZED_DATA)), cursor.getString(cursor.getColumnIndexOrThrow(Jobs.SERIALIZED_INPUT_DATA)), - cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.IS_RUNNING)) == 1); + cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.IS_RUNNING)) == 1, + false); } private @NonNull ConstraintSpec constraintSpecFromCursor(@NonNull Cursor cursor) { return new ConstraintSpec(cursor.getString(cursor.getColumnIndexOrThrow(Constraints.JOB_SPEC_ID)), - cursor.getString(cursor.getColumnIndexOrThrow(Constraints.FACTORY_KEY))); + cursor.getString(cursor.getColumnIndexOrThrow(Constraints.FACTORY_KEY)), + false); } private @NonNull DependencySpec dependencySpecFromCursor(@NonNull Cursor cursor) { return new DependencySpec(cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.JOB_SPEC_ID)), - cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.DEPENDS_ON_JOB_SPEC_ID))); + cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.DEPENDS_ON_JOB_SPEC_ID)), + false); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java index 8d005023b7..0f89be395f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java @@ -245,6 +245,7 @@ public abstract class Job { private final String queue; private final List constraintKeys; private final Data inputData; + private final boolean memoryOnly; private Parameters(@NonNull String id, long createTime, @@ -254,7 +255,8 @@ public abstract class Job { int maxInstances, @Nullable String queue, @NonNull List constraintKeys, - @Nullable Data inputData) + @Nullable Data inputData, + boolean memoryOnly) { this.id = id; this.createTime = createTime; @@ -265,6 +267,7 @@ public abstract class Job { this.queue = queue; this.constraintKeys = constraintKeys; this.inputData = inputData; + this.memoryOnly = memoryOnly; } @NonNull String getId() { @@ -303,8 +306,12 @@ public abstract class Job { return inputData; } + boolean isMemoryOnly() { + return memoryOnly; + } + public Builder toBuilder() { - return new Builder(id, createTime, maxBackoff, lifespan, maxAttempts, maxInstances, queue, constraintKeys, inputData); + return new Builder(id, createTime, maxBackoff, lifespan, maxAttempts, maxInstances, queue, constraintKeys, inputData, memoryOnly); } @@ -319,13 +326,14 @@ public abstract class Job { private String queue; private List constraintKeys; private Data inputData; + private boolean memoryOnly; public Builder() { this(UUID.randomUUID().toString()); } Builder(@NonNull String id) { - this(id, System.currentTimeMillis(), TimeUnit.SECONDS.toMillis(30), IMMORTAL, 1, UNLIMITED, null, new LinkedList<>(), null); + this(id, System.currentTimeMillis(), TimeUnit.SECONDS.toMillis(30), IMMORTAL, 1, UNLIMITED, null, new LinkedList<>(), null, false); } private Builder(@NonNull String id, @@ -336,7 +344,8 @@ public abstract class Job { int maxInstances, @Nullable String queue, @NonNull List constraintKeys, - @Nullable Data inputData) + @Nullable Data inputData, + boolean memoryOnly) { this.id = id; this.createTime = createTime; @@ -347,6 +356,7 @@ public abstract class Job { this.queue = queue; this.constraintKeys = constraintKeys; this.inputData = inputData; + this.memoryOnly = memoryOnly; } /** Should only be invoked by {@link JobController} */ @@ -424,6 +434,17 @@ public abstract class Job { return this; } + /** + * Specify whether or not you want this job to only live in memory. If true, this job will + * *not* survive application death. This defaults to false, and should be used with care. + * + * Defaults to false. + */ + public @NonNull Builder setMemoryOnly(boolean memoryOnly) { + this.memoryOnly = memoryOnly; + return this; + } + /** * Sets the input data that will be made availabe to the job when it is run. * Should only be set by {@link JobController}. @@ -434,7 +455,7 @@ public abstract class Job { } public @NonNull Parameters build() { - return new Parameters(id, createTime, lifespan, maxAttempts, maxBackoff, maxInstances, queue, constraintKeys, inputData); + return new Parameters(id, createTime, lifespan, maxAttempts, maxBackoff, maxInstances, queue, constraintKeys, inputData, memoryOnly); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java index d26a917152..0c41743d72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java @@ -354,14 +354,20 @@ class JobController { job.getParameters().getMaxInstances(), dataSerializer.serialize(job.serialize()), null, - false); + false, + job.getParameters().isMemoryOnly()); List constraintSpecs = Stream.of(job.getParameters().getConstraintKeys()) - .map(key -> new ConstraintSpec(jobSpec.getId(), key)) + .map(key -> new ConstraintSpec(jobSpec.getId(), key, jobSpec.isMemoryOnly())) .toList(); List dependencySpecs = Stream.of(dependsOn) - .map(depends -> new DependencySpec(job.getId(), depends)) + .map(depends -> { + JobSpec dependsOnJobSpec = jobStorage.getJobSpec(depends); + boolean memoryOnly = job.getParameters().isMemoryOnly() || (dependsOnJobSpec != null && dependsOnJobSpec.isMemoryOnly()); + + return new DependencySpec(job.getId(), depends, memoryOnly); + }) .toList(); return new FullSpec(jobSpec, constraintSpecs, dependencySpecs); @@ -371,7 +377,7 @@ class JobController { private void scheduleJobs(@NonNull List jobs) { for (Job job : jobs) { List constraints = Stream.of(job.getParameters().getConstraintKeys()) - .map(key -> new ConstraintSpec(job.getId(), key)) + .map(key -> new ConstraintSpec(job.getId(), key, job.getParameters().isMemoryOnly())) .map(ConstraintSpec::getFactoryKey) .map(constraintInstantiator::instantiate) .toList(); @@ -460,7 +466,8 @@ class JobController { jobSpec.getMaxInstances(), jobSpec.getSerializedData(), dataSerializer.serialize(inputData), - jobSpec.isRunning()); + jobSpec.isRunning(), + jobSpec.isMemoryOnly()); } interface Callback { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobMigrator.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobMigrator.java index 6525c82679..7e2e85d07e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobMigrator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobMigrator.java @@ -76,7 +76,8 @@ public class JobMigrator { jobSpec.getMaxInstances(), dataSerializer.serialize(updatedJobData.getData()), jobSpec.getSerializedInputData(), - jobSpec.isRunning()); + jobSpec.isRunning(), + jobSpec.isMemoryOnly()); iter.set(updatedJobSpec); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java index 90d11cdc52..8cf773a1fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java @@ -6,12 +6,14 @@ import java.util.Objects; public final class ConstraintSpec { - private final String jobSpecId; - private final String factoryKey; + private final String jobSpecId; + private final String factoryKey; + private final boolean memoryOnly; - public ConstraintSpec(@NonNull String jobSpecId, @NonNull String factoryKey) { + public ConstraintSpec(@NonNull String jobSpecId, @NonNull String factoryKey, boolean memoryOnly) { this.jobSpecId = jobSpecId; this.factoryKey = factoryKey; + this.memoryOnly = memoryOnly; } public String getJobSpecId() { @@ -22,22 +24,27 @@ public final class ConstraintSpec { return factoryKey; } + public boolean isMemoryOnly() { + return memoryOnly; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ConstraintSpec that = (ConstraintSpec) o; - return Objects.equals(jobSpecId, that.jobSpecId) && - Objects.equals(factoryKey, that.factoryKey); + return Objects.equals(jobSpecId, that.jobSpecId) && + Objects.equals(factoryKey, that.factoryKey) && + memoryOnly == that.memoryOnly; } @Override public int hashCode() { - return Objects.hash(jobSpecId, factoryKey); + return Objects.hash(jobSpecId, factoryKey, memoryOnly); } @Override public @NonNull String toString() { - return String.format("jobSpecId: JOB::%s | factoryKey: %s", jobSpecId, factoryKey); + return String.format("jobSpecId: JOB::%s | factoryKey: %s | memoryOnly: %b", jobSpecId, factoryKey, memoryOnly); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java index 38d7e56d1d..8dfd9f13c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java @@ -6,12 +6,14 @@ import java.util.Objects; public final class DependencySpec { - private final String jobId; - private final String dependsOnJobId; + private final String jobId; + private final String dependsOnJobId; + private final boolean memoryOnly; - public DependencySpec(@NonNull String jobId, @NonNull String dependsOnJobId) { + public DependencySpec(@NonNull String jobId, @NonNull String dependsOnJobId, boolean memoryOnly) { this.jobId = jobId; this.dependsOnJobId = dependsOnJobId; + this.memoryOnly = memoryOnly; } public @NonNull String getJobId() { @@ -22,22 +24,27 @@ public final class DependencySpec { return dependsOnJobId; } + public boolean isMemoryOnly() { + return memoryOnly; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DependencySpec that = (DependencySpec) o; - return Objects.equals(jobId, that.jobId) && - Objects.equals(dependsOnJobId, that.dependsOnJobId); + return Objects.equals(jobId, that.jobId) && + Objects.equals(dependsOnJobId, that.dependsOnJobId) && + memoryOnly == that.memoryOnly; } @Override public int hashCode() { - return Objects.hash(jobId, dependsOnJobId); + return Objects.hash(jobId, dependsOnJobId, memoryOnly); } @Override public @NonNull String toString() { - return String.format("jobSpecId: JOB::%s | dependsOnJobSpecId: JOB::%s", jobId, dependsOnJobId); + return String.format("jobSpecId: JOB::%s | dependsOnJobSpecId: JOB::%s | memoryOnly: %b", jobId, dependsOnJobId, memoryOnly); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java index 563c36a80b..03169f141e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java @@ -32,6 +32,9 @@ public final class FullSpec { return dependencySpecs; } + public boolean isMemoryOnly() { + return jobSpec.isMemoryOnly(); + } @Override public boolean equals(Object o) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java index efbfe0801e..29a3b06a52 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java @@ -21,6 +21,7 @@ public final class JobSpec { private final String serializedData; private final String serializedInputData; private final boolean isRunning; + private final boolean memoryOnly; public JobSpec(@NonNull String id, @NonNull String factoryKey, @@ -34,7 +35,8 @@ public final class JobSpec { int maxInstances, @NonNull String serializedData, @Nullable String serializedInputData, - boolean isRunning) + boolean isRunning, + boolean memoryOnly) { this.id = id; this.factoryKey = factoryKey; @@ -49,6 +51,7 @@ public final class JobSpec { this.serializedData = serializedData; this.serializedInputData = serializedInputData; this.isRunning = isRunning; + this.memoryOnly = memoryOnly; } public @NonNull String getId() { @@ -103,6 +106,10 @@ public final class JobSpec { return isRunning; } + public boolean isMemoryOnly() { + return memoryOnly; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -116,6 +123,7 @@ public final class JobSpec { lifespan == jobSpec.lifespan && maxInstances == jobSpec.maxInstances && isRunning == jobSpec.isRunning && + memoryOnly == jobSpec.memoryOnly && Objects.equals(id, jobSpec.id) && Objects.equals(factoryKey, jobSpec.factoryKey) && Objects.equals(queueKey, jobSpec.queueKey) && @@ -125,13 +133,13 @@ public final class JobSpec { @Override public int hashCode() { - return Objects.hash(id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, lifespan, maxInstances, serializedData, serializedInputData, isRunning); + return Objects.hash(id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, lifespan, maxInstances, serializedData, serializedInputData, isRunning, memoryOnly); } @SuppressLint("DefaultLocale") @Override public @NonNull String toString() { - return String.format("id: JOB::%s | factoryKey: %s | queueKey: %s | createTime: %d | nextRunAttemptTime: %d | runAttempt: %d | maxAttempts: %d | maxBackoff: %d | maxInstances: %d | lifespan: %d | isRunning: %b", - id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, maxInstances, lifespan, isRunning); + return String.format("id: JOB::%s | factoryKey: %s | queueKey: %s | createTime: %d | nextRunAttemptTime: %d | runAttempt: %d | maxAttempts: %d | maxBackoff: %d | maxInstances: %d | lifespan: %d | isRunning: %b | memoryOnly: %b", + id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, maxInstances, lifespan, isRunning, memoryOnly); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/workmanager/WorkManagerDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/workmanager/WorkManagerDatabase.java index b760c531d5..21f9e4eac1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/workmanager/WorkManagerDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/workmanager/WorkManagerDatabase.java @@ -69,12 +69,13 @@ final class WorkManagerDatabase extends SQLiteOpenHelper { Job.Parameters.UNLIMITED, dataSerializer.serialize(DataMigrator.convert(data)), null, + false, false); if (cursor.getInt(cursor.getColumnIndexOrThrow("required_network_type")) != 0) { - constraints.add(new ConstraintSpec(id, NetworkConstraint.KEY)); + constraints.add(new ConstraintSpec(id, NetworkConstraint.KEY, false)); } fullSpecs.add(new FullSpec(jobSpec, constraints, Collections.emptyList())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java index 2c1210343b..1c4b22f97f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java @@ -85,9 +85,12 @@ public class FastJobStorage implements JobStorage { @Override public synchronized void insertJobs(@NonNull List fullSpecs) { - serialExecutor.execute(() -> { - jobDatabase.insertJobs(fullSpecs); - }); + List durable = Stream.of(fullSpecs).filterNot(FullSpec::isMemoryOnly).toList(); + if (durable.size() > 0) { + serialExecutor.execute(() -> { + jobDatabase.insertJobs(durable); + }); + } for (FullSpec fullSpec : fullSpecs) { jobs.add(fullSpec.getJobSpec()); @@ -168,9 +171,12 @@ public class FastJobStorage implements JobStorage { @Override public synchronized void updateJobRunningState(@NonNull String id, boolean isRunning) { - serialExecutor.execute(() -> { - jobDatabase.updateJobRunningState(id, isRunning); - }); + JobSpec job = getJobById(id); + if (job == null || !job.isMemoryOnly()) { + serialExecutor.execute(() -> { + jobDatabase.updateJobRunningState(id, isRunning); + }); + } ListIterator iter = jobs.listIterator(); @@ -189,7 +195,8 @@ public class FastJobStorage implements JobStorage { existing.getMaxInstances(), existing.getSerializedData(), existing.getSerializedInputData(), - isRunning); + isRunning, + existing.isMemoryOnly()); iter.set(updated); } } @@ -197,9 +204,12 @@ public class FastJobStorage implements JobStorage { @Override public synchronized void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime, @NonNull String serializedData) { - serialExecutor.execute(() -> { - jobDatabase.updateJobAfterRetry(id, isRunning, runAttempt, nextRunAttemptTime, serializedData); - }); + JobSpec job = getJobById(id); + if (job == null || !job.isMemoryOnly()) { + serialExecutor.execute(() -> { + jobDatabase.updateJobAfterRetry(id, isRunning, runAttempt, nextRunAttemptTime, serializedData); + }); + } ListIterator iter = jobs.listIterator(); @@ -218,7 +228,8 @@ public class FastJobStorage implements JobStorage { existing.getMaxInstances(), serializedData, existing.getSerializedInputData(), - isRunning); + isRunning, + existing.isMemoryOnly()); iter.set(updated); } } @@ -245,16 +256,27 @@ public class FastJobStorage implements JobStorage { existing.getMaxInstances(), existing.getSerializedData(), existing.getSerializedInputData(), - false); + false, + existing.isMemoryOnly()); iter.set(updated); } } @Override public void updateJobs(@NonNull List jobSpecs) { - serialExecutor.execute(() -> { - jobDatabase.updateJobs(jobSpecs); - }); + List durable = new ArrayList<>(jobSpecs.size()); + for (JobSpec update : jobSpecs) { + JobSpec found = getJobById(update.getId()); + if (found == null || !found.isMemoryOnly()) { + durable.add(update); + } + } + + if (durable.size() > 0) { + serialExecutor.execute(() -> { + jobDatabase.updateJobs(durable); + }); + } Map updates = Stream.of(jobSpecs).collect(Collectors.toMap(JobSpec::getId)); ListIterator iter = jobs.listIterator(); @@ -276,10 +298,19 @@ public class FastJobStorage implements JobStorage { @Override public synchronized void deleteJobs(@NonNull List jobIds) { - serialExecutor.execute(() -> { - jobDatabase.deleteJobs(jobIds); - }); + List durableIds = new ArrayList<>(jobIds.size()); + for (String id : jobIds) { + JobSpec job = getJobById(id); + if (job == null || !job.isMemoryOnly()) { + durableIds.add(id); + } + } + if (durableIds.size() > 0) { + serialExecutor.execute(() -> { + jobDatabase.deleteJobs(durableIds); + }); + } Set deleteIds = new HashSet<>(jobIds); @@ -355,4 +386,14 @@ public class FastJobStorage implements JobStorage { .flatMap(Stream::of) .toList(); } + + private JobSpec getJobById(@NonNull String id) { + for (JobSpec job : jobs) { + if (job.getId().equals(id)) { + return job; + } + } + Log.w(TAG, "Was looking for job with ID JOB::" + id + ", but it doesn't exist in memory!"); + return null; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java index 974ace882b..ba7d336859 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.jobs; +import android.net.Network; + import androidx.annotation.NonNull; import com.annimon.stream.Stream; @@ -10,6 +12,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientUtil; @@ -43,6 +46,8 @@ public class TypingSendJob extends BaseJob { .setQueue(getQueue(threadId)) .setMaxAttempts(1) .setLifespan(TimeUnit.SECONDS.toMillis(5)) + .addConstraint(NetworkConstraint.KEY) + .setMemoryOnly(true) .build(), threadId, typing); diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobmanager/JobMigratorTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobmanager/JobMigratorTest.java index 94f0d5b594..b82208c3ef 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/jobmanager/JobMigratorTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/jobmanager/JobMigratorTest.java @@ -88,7 +88,7 @@ public class JobMigratorTest { private static JobStorage simpleJobStorage() { JobStorage jobStorage = mock(JobStorage.class); - when(jobStorage.getAllJobSpecs()).thenReturn(new ArrayList<>(Collections.singletonList(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, 1, 1, "", null, false)))); + when(jobStorage.getAllJobSpecs()).thenReturn(new ArrayList<>(Collections.singletonList(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, 1, 1, "", null, false, false)))); return jobStorage; } diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java index 12d9d5e931..6292b22572 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; import org.thoughtcrime.securesms.testutil.DirectExecutor; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -24,6 +25,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -53,6 +55,16 @@ public class FastJobStorageTest { verify(database).insertJobs(DataSet1.FULL_SPECS); } + @Test + public void insertJobs_memoryOnlyJob_doesNotWriteToDatabase() { + JobDatabase database = noopDatabase(); + FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); + + subject.insertJobs(DataSetMemory.FULL_SPECS); + + verify(database, times(0)).insertJobs(DataSet1.FULL_SPECS); + } + @Test public void insertJobs_dataCanBeFound() { FastJobStorage subject = new FastJobStorage(noopDatabase(), new DirectExecutor()); @@ -86,10 +98,10 @@ public class FastJobStorageTest { @Test public void updateAllJobsToBePending_allArePending() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, true), + FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, true), + FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); @@ -104,32 +116,44 @@ public class FastJobStorageTest { @Test public void updateJobs_writesToDatabase() { - JobDatabase database = noopDatabase(); + JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); - List jobs = Collections.emptyList(); + List jobs = Collections.singletonList(new JobSpec("id1", "f1", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false, false)); + subject.init(); subject.updateJobs(jobs); verify(database).updateJobs(jobs); } @Test - public void updateJobs_updatesAllFields() { + public void updateJobs_memoryOnly_doesNotWriteToDatabase() { + JobDatabase database = fixedDataDatabase(DataSetMemory.FULL_SPECS); + FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); + List jobs = Collections.singletonList(new JobSpec("id1", "f1", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false, false)); - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false), + subject.init(); + subject.updateJobs(jobs); + + verify(database, times(0)).updateJobs(jobs); + } + + @Test + public void updateJobs_updatesAllFields() { + FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false), + FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec3 = new FullSpec(new JobSpec("3", "f3", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false), + FullSpec fullSpec3 = new FullSpec(new JobSpec("3", "f3", null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2, fullSpec3)), new DirectExecutor()); - JobSpec update1 = new JobSpec("1", "g1", "q1", 2, 2, 2, 2, 2, 2, 2, "abc", null, true); - JobSpec update2 = new JobSpec("2", "g2", "q2", 3, 3, 3, 3, 3, 3, 3, "def", "ghi", true); + JobSpec update1 = new JobSpec("1", "g1", "q1", 2, 2, 2, 2, 2, 2, 2, "abc", null, true, false); + JobSpec update2 = new JobSpec("2", "g2", "q2", 3, 3, 3, 3, 3, 3, 3, "def", "ghi", true, false); subject.init(); subject.updateJobs(Arrays.asList(update1, update2)); @@ -141,12 +165,13 @@ public class FastJobStorageTest { @Test public void updateJobRunningState_writesToDatabase() { - JobDatabase database = noopDatabase(); + JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); - subject.updateJobRunningState("1", true); + subject.init(); + subject.updateJobRunningState("id1", true); - verify(database).updateJobRunningState("1", true); + verify(database).updateJobRunningState("id1", true); } @Test @@ -163,17 +188,29 @@ public class FastJobStorageTest { @Test public void updateJobAfterRetry_writesToDatabase() { - JobDatabase database = noopDatabase(); + JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); - subject.updateJobAfterRetry("1", true, 1, 10, "a"); + subject.init(); + subject.updateJobAfterRetry("id1", true, 1, 10, "a"); - verify(database).updateJobAfterRetry("1", true, 1, 10, "a"); + verify(database).updateJobAfterRetry("id1", true, 1, 10, "a"); + } + + @Test + public void updateJobAfterRetry_memoryOnly_doesNotWriteToDatabase() { + JobDatabase database = fixedDataDatabase(DataSetMemory.FULL_SPECS); + FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); + + subject.init(); + subject.updateJobAfterRetry("id1", true, 1, 10, "a"); + + verify(database, times(0)).updateJobAfterRetry("id1", true, 1, 10, "a"); } @Test public void updateJobAfterRetry_stateUpdated() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 3, 30000, -1, -1, EMPTY_DATA, null, true), + FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 3, 30000, -1, -1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); @@ -193,10 +230,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenEarlierItemInQueueInRunning() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true), + FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -208,7 +245,7 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenAllJobsAreRunning() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true), + FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); @@ -220,7 +257,7 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenNextRunTimeIsAfterCurrentTime() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 10, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 10, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -232,12 +269,12 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenDependentOnAnotherJob() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true), + FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), - Collections.singletonList(new DependencySpec("2", "1"))); + Collections.singletonList(new DependencySpec("2", "1", false))); FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2)), new DirectExecutor()); @@ -248,7 +285,7 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJob() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -260,10 +297,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_multipleEligibleJobs() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -276,10 +313,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJobInMixedList() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true), + FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -295,10 +332,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_firstItemInQueue() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec1 = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec fullSpec2 = new FullSpec(new JobSpec("2", "f2", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -314,10 +351,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_migrationJobTakesPrecedence() { - FullSpec plainSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec plainSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec migrationSpec = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec migrationSpec = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -332,10 +369,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_runningMigrationBlocksNormalJobs() { - FullSpec plainSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec plainSpec = new FullSpec(new JobSpec("1", "f1", "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec migrationSpec = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true), + FullSpec migrationSpec = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); @@ -349,10 +386,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_runningMigrationBlocksLaterMigrationJobs() { - FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true), + FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, true, false), Collections.emptyList(), Collections.emptyList()); - FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -366,10 +403,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_onlyReturnFirstEligibleMigrationJob() { - FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -384,10 +421,10 @@ public class FastJobStorageTest { @Test public void getPendingJobsWithNoDependenciesInCreatedOrder_onlyMigrationJobWithAppropriateNextRunTime() { - FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 999, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec migrationSpec1 = new FullSpec(new JobSpec("1", "f1", Job.Parameters.MIGRATION_QUEUE_KEY, 0, 999, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); - FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false), + FullSpec migrationSpec2 = new FullSpec(new JobSpec("2", "f2", Job.Parameters.MIGRATION_QUEUE_KEY, 5, 0, 0, 0, 0, -1, -1, EMPTY_DATA, null, false, false), Collections.emptyList(), Collections.emptyList()); @@ -401,15 +438,28 @@ public class FastJobStorageTest { @Test public void deleteJobs_writesToDatabase() { - JobDatabase database = noopDatabase(); + JobDatabase database = fixedDataDatabase(DataSet1.FULL_SPECS); FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); - List ids = Arrays.asList("1", "2"); + List ids = Arrays.asList("id1", "id2"); + subject.init(); subject.deleteJobs(ids); verify(database).deleteJobs(ids); } + @Test + public void deleteJobs_memoryOnly_doesNotWriteToDatabase() { + JobDatabase database = fixedDataDatabase(DataSetMemory.FULL_SPECS); + FastJobStorage subject = new FastJobStorage(database, new DirectExecutor()); + List ids = Collections.singletonList("id1"); + + subject.init(); + subject.deleteJobs(ids); + + verify(database, times(0)).deleteJobs(ids); + } + @Test public void deleteJobs_deletesAllRelevantPieces() { FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS), new DirectExecutor()); @@ -509,13 +559,13 @@ public class FastJobStorageTest { } private static final class DataSet1 { - static final JobSpec JOB_1 = new JobSpec("id1", "f1", "q1", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, null, false); - static final JobSpec JOB_2 = new JobSpec("id2", "f2", "q2", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, null, false); - static final JobSpec JOB_3 = new JobSpec("id3", "f3", "q3", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, null, false); - static final ConstraintSpec CONSTRAINT_1 = new ConstraintSpec("id1", "f1"); - static final ConstraintSpec CONSTRAINT_2 = new ConstraintSpec("id2", "f2"); - static final DependencySpec DEPENDENCY_2 = new DependencySpec("id2", "id1"); - static final DependencySpec DEPENDENCY_3 = new DependencySpec("id3", "id2"); + static final JobSpec JOB_1 = new JobSpec("id1", "f1", "q1", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, null, false, false); + static final JobSpec JOB_2 = new JobSpec("id2", "f2", "q2", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, null, false, false); + static final JobSpec JOB_3 = new JobSpec("id3", "f3", "q3", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, null, false, false); + static final ConstraintSpec CONSTRAINT_1 = new ConstraintSpec("id1", "f1", false); + static final ConstraintSpec CONSTRAINT_2 = new ConstraintSpec("id2", "f2", false); + static final DependencySpec DEPENDENCY_2 = new DependencySpec("id2", "id1", false); + static final DependencySpec DEPENDENCY_3 = new DependencySpec("id3", "id2", false); static final FullSpec FULL_SPEC_1 = new FullSpec(JOB_1, Collections.singletonList(CONSTRAINT_1), Collections.emptyList()); static final FullSpec FULL_SPEC_2 = new FullSpec(JOB_2, Collections.singletonList(CONSTRAINT_2), Collections.singletonList(DEPENDENCY_2)); static final FullSpec FULL_SPEC_3 = new FullSpec(JOB_3, Collections.emptyList(), Collections.singletonList(DEPENDENCY_3)); @@ -540,4 +590,11 @@ public class FastJobStorageTest { assertTrue(dependencies.contains(DataSet1.DEPENDENCY_3)); } } + + private static final class DataSetMemory { + static final JobSpec JOB_1 = new JobSpec("id1", "f1", "q1", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, null, false, true); + static final ConstraintSpec CONSTRAINT_1 = new ConstraintSpec("id1", "f1", true); + static final FullSpec FULL_SPEC_1 = new FullSpec(JOB_1, Collections.singletonList(CONSTRAINT_1), Collections.emptyList()); + static final List FULL_SPECS = Collections.singletonList(FULL_SPEC_1); + } }