Skip to content

Commit 86cf820

Browse files
greyson-signalnicholas-signal
authored andcommitted
Remove cases where all jobs were expected to be in memory.
1 parent 973dc72 commit 86cf820

10 files changed

Lines changed: 418 additions & 235 deletions

File tree

app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.kt

Lines changed: 106 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import androidx.core.content.contentValuesOf
88
import net.zetetic.database.sqlcipher.SQLiteDatabase
99
import net.zetetic.database.sqlcipher.SQLiteOpenHelper
1010
import org.signal.core.util.CursorUtil
11+
import org.signal.core.util.SqlUtil
1112
import org.signal.core.util.concurrent.SignalExecutors
1213
import org.signal.core.util.delete
14+
import org.signal.core.util.forEach
1315
import org.signal.core.util.insertInto
1416
import org.signal.core.util.logging.Log
1517
import org.signal.core.util.readToList
@@ -31,6 +33,7 @@ import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec
3133
import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec
3234
import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec
3335
import org.thoughtcrime.securesms.jobs.MinimalJobSpec
36+
import java.util.function.Predicate
3437

3538
class JobDatabase(
3639
application: Application,
@@ -184,28 +187,32 @@ class JobDatabase(
184187
}
185188

186189
@Synchronized
187-
fun getAllJobSpecs(): List<JobSpec> {
190+
fun getJobSpecs(limit: Int): List<JobSpec> {
188191
return readableDatabase
189192
.select()
190193
.from(Jobs.TABLE_NAME)
191194
.orderBy("${Jobs.CREATE_TIME}, ${Jobs.ID} ASC")
195+
.limit(limit)
192196
.run()
193-
.readToList { cursor ->
194-
jobSpecFromCursor(cursor)
195-
}
197+
.readToList { it.toJobSpec() }
196198
}
197199

198200
@Synchronized
199-
fun getOldestJobSpecs(limit: Int): List<JobSpec> {
200-
return readableDatabase
201+
fun getAllMatchingFilter(predicate: Predicate<JobSpec>): List<JobSpec> {
202+
val output: MutableList<JobSpec> = mutableListOf()
203+
204+
readableDatabase
201205
.select()
202206
.from(Jobs.TABLE_NAME)
203-
.orderBy("${Jobs.CREATE_TIME}, ${Jobs.ID} ASC")
204-
.limit(limit)
205207
.run()
206208
.readToList { cursor ->
207-
jobSpecFromCursor(cursor)
209+
val jobSpec = cursor.toJobSpec()
210+
if (predicate.test(jobSpec)) {
211+
output += jobSpec
212+
}
208213
}
214+
215+
return output
209216
}
210217

211218
@Synchronized
@@ -215,9 +222,7 @@ class JobDatabase(
215222
.from(Jobs.TABLE_NAME)
216223
.where("${Jobs.JOB_SPEC_ID} = ?", id)
217224
.run()
218-
.readToSingleObject {
219-
jobSpecFromCursor(it)
220-
}
225+
.readToSingleObject { it.toJobSpec() }
221226
}
222227

223228
@Synchronized
@@ -296,26 +301,41 @@ class JobDatabase(
296301
.filterNot { it.isMemoryOnly }
297302
.forEach { job ->
298303
db.update(Jobs.TABLE_NAME)
299-
.values(
300-
Jobs.JOB_SPEC_ID to job.id,
301-
Jobs.FACTORY_KEY to job.factoryKey,
302-
Jobs.QUEUE_KEY to job.queueKey,
303-
Jobs.CREATE_TIME to job.createTime,
304-
Jobs.LAST_RUN_ATTEMPT_TIME to job.lastRunAttemptTime,
305-
Jobs.NEXT_BACKOFF_INTERVAL to job.nextBackoffInterval,
306-
Jobs.RUN_ATTEMPT to job.runAttempt,
307-
Jobs.MAX_ATTEMPTS to job.maxAttempts,
308-
Jobs.LIFESPAN to job.lifespan,
309-
Jobs.SERIALIZED_DATA to job.serializedData,
310-
Jobs.SERIALIZED_INPUT_DATA to job.serializedInputData,
311-
Jobs.IS_RUNNING to if (job.isRunning) 1 else 0
312-
)
304+
.values(job.toContentValues())
313305
.where("${Jobs.JOB_SPEC_ID} = ?", job.id)
314306
.run()
315307
}
316308
}
317309
}
318310

311+
@Synchronized
312+
fun transformJobs(transformer: (JobSpec) -> JobSpec): List<JobSpec> {
313+
val transformed: MutableList<JobSpec> = mutableListOf()
314+
315+
writableDatabase.withinTransaction { db ->
316+
readableDatabase
317+
.select()
318+
.from(Jobs.TABLE_NAME)
319+
.run()
320+
.forEach { cursor ->
321+
val jobSpec = cursor.toJobSpec()
322+
val updated = transformer(jobSpec)
323+
if (updated != jobSpec) {
324+
transformed += updated
325+
}
326+
}
327+
328+
for (job in transformed) {
329+
db.update(Jobs.TABLE_NAME)
330+
.values(job.toContentValues())
331+
.where("${Jobs.JOB_SPEC_ID} = ?", job.id)
332+
.run()
333+
}
334+
}
335+
336+
return transformed
337+
}
338+
319339
@Synchronized
320340
fun deleteJobs(jobIds: List<String>) {
321341
writableDatabase.withinTransaction { db ->
@@ -340,14 +360,30 @@ class JobDatabase(
340360
}
341361

342362
@Synchronized
343-
fun getAllConstraintSpecs(): List<ConstraintSpec> {
363+
fun getConstraintSpecs(limit: Int): List<ConstraintSpec> {
344364
return readableDatabase
345365
.select()
346366
.from(Constraints.TABLE_NAME)
367+
.limit(limit)
347368
.run()
348-
.readToList { cursor ->
349-
constraintSpecFromCursor(cursor)
350-
}
369+
.readToList { it.toConstraintSpec() }
370+
}
371+
372+
fun getConstraintSpecsForJobs(jobIds: Collection<String>): List<ConstraintSpec> {
373+
val output: MutableList<ConstraintSpec> = mutableListOf()
374+
375+
for (query in SqlUtil.buildCollectionQuery(Constraints.JOB_SPEC_ID, jobIds)) {
376+
readableDatabase
377+
.select()
378+
.from(Constraints.TABLE_NAME)
379+
.where(query.where, query.whereArgs)
380+
.run()
381+
.forEach {
382+
output += it.toConstraintSpec()
383+
}
384+
}
385+
386+
return output
351387
}
352388

353389
@Synchronized
@@ -356,9 +392,7 @@ class JobDatabase(
356392
.select()
357393
.from(Dependencies.TABLE_NAME)
358394
.run()
359-
.readToList { cursor ->
360-
dependencySpecFromCursor(cursor)
361-
}
395+
.readToList { it.toDependencySpec() }
362396
}
363397

364398
private fun insertJobSpec(db: SQLiteDatabase, job: JobSpec) {
@@ -369,21 +403,7 @@ class JobDatabase(
369403
check(db.inTransaction())
370404

371405
db.insertInto(Jobs.TABLE_NAME)
372-
.values(
373-
Jobs.JOB_SPEC_ID to job.id,
374-
Jobs.FACTORY_KEY to job.factoryKey,
375-
Jobs.QUEUE_KEY to job.queueKey,
376-
Jobs.CREATE_TIME to job.createTime,
377-
Jobs.LAST_RUN_ATTEMPT_TIME to job.lastRunAttemptTime,
378-
Jobs.NEXT_BACKOFF_INTERVAL to job.nextBackoffInterval,
379-
Jobs.RUN_ATTEMPT to job.runAttempt,
380-
Jobs.MAX_ATTEMPTS to job.maxAttempts,
381-
Jobs.LIFESPAN to job.lifespan,
382-
Jobs.SERIALIZED_DATA to job.serializedData,
383-
Jobs.SERIALIZED_INPUT_DATA to job.serializedInputData,
384-
Jobs.IS_RUNNING to if (job.isRunning) 1 else 0,
385-
Jobs.PRIORITY to job.priority
386-
)
406+
.values(job.toContentValues())
387407
.run(SQLiteDatabase.CONFLICT_IGNORE)
388408
}
389409

@@ -417,37 +437,37 @@ class JobDatabase(
417437
}
418438
}
419439

420-
private fun jobSpecFromCursor(cursor: Cursor): JobSpec {
440+
private fun Cursor.toJobSpec(): JobSpec {
421441
return JobSpec(
422-
id = cursor.requireNonNullString(Jobs.JOB_SPEC_ID),
423-
factoryKey = cursor.requireNonNullString(Jobs.FACTORY_KEY),
424-
queueKey = cursor.requireString(Jobs.QUEUE_KEY),
425-
createTime = cursor.requireLong(Jobs.CREATE_TIME),
426-
lastRunAttemptTime = cursor.requireLong(Jobs.LAST_RUN_ATTEMPT_TIME),
427-
nextBackoffInterval = cursor.requireLong(Jobs.NEXT_BACKOFF_INTERVAL),
428-
runAttempt = cursor.requireInt(Jobs.RUN_ATTEMPT),
429-
maxAttempts = cursor.requireInt(Jobs.MAX_ATTEMPTS),
430-
lifespan = cursor.requireLong(Jobs.LIFESPAN),
431-
serializedData = cursor.requireBlob(Jobs.SERIALIZED_DATA),
432-
serializedInputData = cursor.requireBlob(Jobs.SERIALIZED_INPUT_DATA),
433-
isRunning = cursor.requireBoolean(Jobs.IS_RUNNING),
442+
id = this.requireNonNullString(Jobs.JOB_SPEC_ID),
443+
factoryKey = this.requireNonNullString(Jobs.FACTORY_KEY),
444+
queueKey = this.requireString(Jobs.QUEUE_KEY),
445+
createTime = this.requireLong(Jobs.CREATE_TIME),
446+
lastRunAttemptTime = this.requireLong(Jobs.LAST_RUN_ATTEMPT_TIME),
447+
nextBackoffInterval = this.requireLong(Jobs.NEXT_BACKOFF_INTERVAL),
448+
runAttempt = this.requireInt(Jobs.RUN_ATTEMPT),
449+
maxAttempts = this.requireInt(Jobs.MAX_ATTEMPTS),
450+
lifespan = this.requireLong(Jobs.LIFESPAN),
451+
serializedData = this.requireBlob(Jobs.SERIALIZED_DATA),
452+
serializedInputData = this.requireBlob(Jobs.SERIALIZED_INPUT_DATA),
453+
isRunning = this.requireBoolean(Jobs.IS_RUNNING),
434454
isMemoryOnly = false,
435-
priority = cursor.requireInt(Jobs.PRIORITY)
455+
priority = this.requireInt(Jobs.PRIORITY)
436456
)
437457
}
438458

439-
private fun constraintSpecFromCursor(cursor: Cursor): ConstraintSpec {
459+
private fun Cursor.toConstraintSpec(): ConstraintSpec {
440460
return ConstraintSpec(
441-
jobSpecId = cursor.requireNonNullString(Constraints.JOB_SPEC_ID),
442-
factoryKey = cursor.requireNonNullString(Constraints.FACTORY_KEY),
461+
jobSpecId = this.requireNonNullString(Constraints.JOB_SPEC_ID),
462+
factoryKey = this.requireNonNullString(Constraints.FACTORY_KEY),
443463
isMemoryOnly = false
444464
)
445465
}
446466

447-
private fun dependencySpecFromCursor(cursor: Cursor): DependencySpec {
467+
private fun Cursor.toDependencySpec(): DependencySpec {
448468
return DependencySpec(
449-
jobId = cursor.requireNonNullString(Dependencies.JOB_SPEC_ID),
450-
dependsOnJobId = cursor.requireNonNullString(Dependencies.DEPENDS_ON_JOB_SPEC_ID),
469+
jobId = this.requireNonNullString(Dependencies.JOB_SPEC_ID),
470+
dependsOnJobId = this.requireNonNullString(Dependencies.DEPENDS_ON_JOB_SPEC_ID),
451471
isMemoryOnly = false
452472
)
453473
}
@@ -468,6 +488,24 @@ class JobDatabase(
468488
writableDatabase.update(Jobs.TABLE_NAME, contentValuesOf(Jobs.NEXT_BACKOFF_INTERVAL to 0), null, null)
469489
}
470490

491+
private fun JobSpec.toContentValues(): ContentValues {
492+
return contentValuesOf(
493+
Jobs.JOB_SPEC_ID to this.id,
494+
Jobs.FACTORY_KEY to this.factoryKey,
495+
Jobs.QUEUE_KEY to this.queueKey,
496+
Jobs.CREATE_TIME to this.createTime,
497+
Jobs.LAST_RUN_ATTEMPT_TIME to this.lastRunAttemptTime,
498+
Jobs.NEXT_BACKOFF_INTERVAL to this.nextBackoffInterval,
499+
Jobs.RUN_ATTEMPT to this.runAttempt,
500+
Jobs.MAX_ATTEMPTS to this.maxAttempts,
501+
Jobs.LIFESPAN to this.lifespan,
502+
Jobs.SERIALIZED_DATA to this.serializedData,
503+
Jobs.SERIALIZED_INPUT_DATA to this.serializedInputData,
504+
Jobs.IS_RUNNING to if (this.isRunning) 1 else 0,
505+
Jobs.PRIORITY to this.priority
506+
)
507+
}
508+
471509
companion object {
472510
private val TAG = Log.tag(JobDatabase::class.java)
473511
private const val DATABASE_VERSION = 3

app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -231,26 +231,13 @@ synchronized void cancelAllInQueue(@NonNull String queue) {
231231

232232
@WorkerThread
233233
synchronized void update(@NonNull JobUpdater updater) {
234-
List<JobSpec> allJobs = jobStorage.getAllJobSpecs();
235-
List<JobSpec> updatedJobs = new LinkedList<>();
236-
237-
for (JobSpec job : allJobs) {
238-
JobSpec updated = updater.update(job);
239-
if (updated != job) {
240-
updatedJobs.add(updated);
241-
}
242-
}
243-
244-
jobStorage.updateJobs(updatedJobs);
245-
234+
jobStorage.transformJobs(updater::update);
246235
notifyAll();
247236
}
248237

249238
@WorkerThread
250239
synchronized List<JobSpec> findJobs(@NonNull Predicate<JobSpec> predicate) {
251-
return Stream.of(jobStorage.getAllJobSpecs())
252-
.filter(predicate::test)
253-
.toList();
240+
return jobStorage.getAllMatchingFilter(predicate);
254241
}
255242

256243
@WorkerThread
@@ -360,9 +347,9 @@ synchronized void onSuccess(@NonNull Job job, @Nullable byte[] outputData) {
360347
*/
361348
@WorkerThread
362349
synchronized @NonNull String getDebugInfo() {
363-
List<JobSpec> jobs = jobStorage.getAllJobSpecs();
364-
List<ConstraintSpec> constraints = jobStorage.getAllConstraintSpecs();
365-
List<DependencySpec> dependencies = jobStorage.getAllDependencySpecs();
350+
List<JobSpec> jobs = jobStorage.debugGetJobSpecs(1000);
351+
List<ConstraintSpec> constraints = jobStorage.debugGetConstraintSpecs(1000);
352+
List<DependencySpec> dependencies = jobStorage.debugGetAllDependencySpecs();
366353

367354
StringBuilder info = new StringBuilder();
368355

app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobMigration.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,33 @@ abstract class JobMigration protected constructor(val endVersion: Int) {
3333
return copy(data = newData)
3434
}
3535

36+
override fun equals(other: Any?): Boolean {
37+
if (this === other) return true
38+
if (javaClass != other?.javaClass) return false
39+
40+
other as JobData
41+
42+
if (factoryKey != other.factoryKey) return false
43+
if (queueKey != other.queueKey) return false
44+
if (maxAttempts != other.maxAttempts) return false
45+
if (lifespan != other.lifespan) return false
46+
if (data != null) {
47+
if (other.data == null) return false
48+
if (!data.contentEquals(other.data)) return false
49+
} else if (other.data != null) return false
50+
51+
return true
52+
}
53+
54+
override fun hashCode(): Int {
55+
var result = factoryKey.hashCode()
56+
result = 31 * result + (queueKey?.hashCode() ?: 0)
57+
result = 31 * result + maxAttempts
58+
result = 31 * result + lifespan.hashCode()
59+
result = 31 * result + (data?.contentHashCode() ?: 0)
60+
return result
61+
}
62+
3663
companion object {
3764
@JvmField
3865
val FAILING_JOB_DATA = JobData("FailingJob", null, -1, -1, null)

0 commit comments

Comments
 (0)