From dd2ee394ef0640948b9a4aab2bff55cbefe98e75 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 30 Jul 2026 09:43:18 -0400 Subject: [PATCH] Limit potential size of job info debug log section. --- .../securesms/jobmanager/JobController.java | 12 ++++++--- .../securesms/jobs/FastJobStorage.kt | 27 ++++++++++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) 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 338f1fdd2a..37e242b108 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java @@ -44,6 +44,9 @@ class JobController { private static final Predicate NO_PREDICATE = spec -> true; + /** Max number of items we'll print for any single collection in {@link #getDebugInfo()}, to avoid OOM'ing while building the string. */ + private static final int DEBUG_ITEM_LIMIT = 500; + private final Application application; private final JobStorage jobStorage; private final JobInstantiator jobInstantiator; @@ -409,8 +412,8 @@ class JobController { @WorkerThread synchronized @NonNull String getDebugInfo() { List running = runningJobs.keySet().stream().map(jobStorage::getJobSpec).collect(Collectors.toList()); - List jobs = jobStorage.debugGetJobSpecs(1000); - List constraints = jobStorage.debugGetConstraintSpecs(1000); + List jobs = jobStorage.debugGetJobSpecs(DEBUG_ITEM_LIMIT); + List constraints = jobStorage.debugGetConstraintSpecs(DEBUG_ITEM_LIMIT); List dependencies = jobStorage.debugGetAllDependencySpecs(); String additional = jobStorage.debugAdditionalDetails(); @@ -443,7 +446,10 @@ class JobController { info.append("\n-- Dependencies\n"); if (!dependencies.isEmpty()) { - dependencies.stream().forEach(d -> info.append(d.toString()).append('\n')); + dependencies.stream().limit(DEBUG_ITEM_LIMIT).forEach(d -> info.append(d.toString()).append('\n')); + if (dependencies.size() > DEBUG_ITEM_LIMIT) { + info.append("...TRUNCATED, showing first ").append(DEBUG_ITEM_LIMIT).append(" of ").append(dependencies.size()).append('\n'); + } } else { info.append("None\n"); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt index 2f0a87f964..887131d32b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.kt @@ -22,6 +22,9 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage { private val TAG = Log.tag(FastJobStorage::class) private const val JOB_CACHE_LIMIT = 1000 private const val DEBUG = false + + /** Max number of items we'll print for any single collection in [debugAdditionalDetails], to avoid OOM'ing while building the string. */ + private const val DEBUG_ITEM_LIMIT = 500 } /** We keep a trimmed down version of every job in memory. */ @@ -432,13 +435,23 @@ class FastJobStorage(private val jobDatabase: JobDatabase) : JobStorage { val nonEmptyDependencies = dependenciesByJobId.filterValues { it.isNotEmpty() } return buildString { - appendLine("minimalJobs: Size(${minimalJobs.size}), Items(${minimalJobs.joinToString(", ") { it.toLogString() }})") - appendLine("jobSpecCache: Size(${jobSpecCache.size}), Items(${jobSpecCache.keys.joinToString(", ") { it.toLogString() }})") - appendLine("eligibleJobs: Size(${eligibleJobs.size}), Items(${eligibleJobs.joinToString(", ") { it.toLogString() }})") - appendLine("migrationJobs: Size(${migrationJobs.size}), Items(${migrationJobs.joinToString(", ") { it.toLogString() }})") - appendLine("mostEligibleForQueue: Size(${mostEligibleJobForQueue.size}), Items(${mostEligibleJobForQueue.entries.joinToString(", ") { "[${it.key} => ${it.value.toLogString()}]" }})") - appendLine("constraintsByJobId: Size(${constraintsByJobId.size}), Items(${constraintsByJobId.entries.joinToString(", ") { "[${it.key.toLogString()} => ${it.value.joinToString(", ") { c -> c.toLogString() }}]" }})") - appendLine("dependenciesByJobId: Size(${nonEmptyDependencies.size}), Items(${nonEmptyDependencies.entries.joinToString(", ") { "[${it.key.toLogString()} => ${it.value.map { d -> d.toLogString() }}]" }})") + appendLine("minimalJobs: Size(${minimalJobs.size}), Items(${minimalJobs.toTruncatedLogString { it.toLogString() }})") + appendLine("jobSpecCache: Size(${jobSpecCache.size}), Items(${jobSpecCache.keys.toTruncatedLogString { it.toLogString() }})") + appendLine("eligibleJobs: Size(${eligibleJobs.size}), Items(${eligibleJobs.toTruncatedLogString { it.toLogString() }})") + appendLine("migrationJobs: Size(${migrationJobs.size}), Items(${migrationJobs.toTruncatedLogString { it.toLogString() }})") + appendLine("mostEligibleForQueue: Size(${mostEligibleJobForQueue.size}), Items(${mostEligibleJobForQueue.entries.toTruncatedLogString { "[${it.key} => ${it.value.toLogString()}]" }})") + appendLine("constraintsByJobId: Size(${constraintsByJobId.size}), Items(${constraintsByJobId.entries.toTruncatedLogString { "[${it.key.toLogString()} => ${it.value.joinToString(", ") { c -> c.toLogString() }}]" }})") + appendLine("dependenciesByJobId: Size(${nonEmptyDependencies.size}), Items(${nonEmptyDependencies.entries.toTruncatedLogString { "[${it.key.toLogString()} => ${it.value.map { d -> d.toLogString() }}]" }})") + } + } + + private fun Collection.toTruncatedLogString(transform: (T) -> CharSequence): String { + val rendered = this.asSequence().take(DEBUG_ITEM_LIMIT).joinToString(", ", transform = transform) + + return if (this.size > DEBUG_ITEM_LIMIT) { + "$rendered, ...TRUNCATED, showing first $DEBUG_ITEM_LIMIT of ${this.size}" + } else { + rendered } }