diff --git a/CHANGELOG.md b/CHANGELOG.md index f5541b4d5c..a1eb052731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ * Sanity check the allocation in the installation of `bugsnag-plugin-android-ndk` to avoid a possible crash when allocation fails [#2191](https://github.com/bugsnag/bugsnag-android/pull/2191) +* Added deterministic sorting for `discardOldestFileIfNeeded` method to avoid potential crashes when files are being written while sorting the queue + [#2181](https://github.com/bugsnag/bugsnag-android/pull/2189) + ## 6.13.0 (2025-04-15) ### Enhancements diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt index f7ede678cb..4c76b54dad 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt @@ -114,11 +114,21 @@ internal abstract class FileStore( if (isStorageDirValid(storageDir)) { val listFiles = storageDir.listFiles() ?: return if (listFiles.size < maxStoreCount) return - val sortedListFiles = listFiles.sortedBy { it.lastModified() } + + // Store lastModified to ensure it doesn't change during sort + val timestampedFiles = listFiles.mapTo(ArrayList(listFiles.size)) { file -> + FileWithTimestamp(file, file.lastModified()) + } + + // Sort by cached lastModified timesstamps + timestampedFiles.sort() + // Number of files to discard takes into account that a new file may need to be written val numberToDiscard = listFiles.size - maxStoreCount + 1 var discardedCount = 0 - for (file in sortedListFiles) { + + for (fileMeta in timestampedFiles) { + val file = fileMeta.file if (discardedCount == numberToDiscard) { return } else if (!queuedFiles.contains(file)) { @@ -188,3 +198,15 @@ internal abstract class FileStore( } } } + +/** + * A data holder for associating a {@link File} with its last modified timestamp. + * + * @param file The file to associate with a timestamp. + * @param timestamp The last modified time of the file, cached to ensure consistent ordering. + */ +private data class FileWithTimestamp(val file: File, val timestamp: Long) : Comparable { + override fun compareTo(other: FileWithTimestamp): Int { + return timestamp.compareTo(other.timestamp) + } +}