Skip to content

Commit c6e9e91

Browse files
fix(storage): Fix multiple instances of TransferDB leading to SQLiteDatabaseLockedException (#2786)
Co-authored-by: Tyler Roach <[email protected]>
1 parent 916a3bb commit c6e9e91

File tree

6 files changed

+55
-30
lines changed

6 files changed

+55
-30
lines changed

aws-storage-s3/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
testImplementation(libs.test.mockk)
4343
testImplementation(libs.test.androidx.workmanager)
4444
testImplementation(libs.test.kotlin.coroutines)
45+
testImplementation(libs.test.kotest.assertions)
4546
testImplementation(project(":aws-storage-s3"))
4647

4748
androidTestImplementation(project(":testutils"))

aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/transfer/TransferDB.kt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515

1616
package com.amplifyframework.storage.s3.transfer
1717

18-
import android.annotation.SuppressLint
1918
import android.content.ContentValues
2019
import android.content.Context
2120
import android.database.Cursor
2221
import android.net.Uri
22+
import androidx.annotation.VisibleForTesting
2323
import aws.sdk.kotlin.services.s3.model.CompletedPart
2424
import aws.sdk.kotlin.services.s3.model.ObjectCannedAcl
2525
import com.amplifyframework.core.Amplify
@@ -33,33 +33,33 @@ import java.io.File
3333
/**
3434
* SQlite database to store transfer records
3535
*/
36-
@SuppressLint("VisibleForTests")
3736
internal class TransferDB private constructor(context: Context) {
3837

39-
private var transferDBHelper: TransferDBHelper = synchronized(this) {
40-
TransferDBHelper(context)
41-
}
38+
private val transferDBHelper = TransferDBHelper(context)
4239

43-
private val logger =
44-
Amplify.Logging.logger(
45-
CategoryType.STORAGE,
46-
AWSS3StoragePlugin.AWS_S3_STORAGE_LOG_NAMESPACE.format(this::class.java.simpleName)
47-
)
40+
private val logger = Amplify.Logging.logger(
41+
CategoryType.STORAGE,
42+
AWSS3StoragePlugin.AWS_S3_STORAGE_LOG_NAMESPACE.format(this::class.java.simpleName)
43+
)
4844

4945
companion object {
5046
private const val QUERY_PLACE_HOLDER_STRING = ",?"
51-
private val instance: TransferDB? = null
5247

53-
@JvmStatic
48+
@Volatile
49+
@VisibleForTesting
50+
var instance: TransferDB? = null
51+
5452
fun getInstance(context: Context): TransferDB {
55-
return instance ?: TransferDB(context)
53+
// Use double check locking for thread safety. It is important that there is never more than one instance
54+
// of this class since it holds an SQLiteOpenHelper.
55+
return instance ?: synchronized(this) {
56+
instance ?: TransferDB(context).also { instance = it }
57+
}
5658
}
5759
}
5860

5961
fun closeDB() {
60-
synchronized(this) {
61-
transferDBHelper.close()
62-
}
62+
transferDBHelper.close()
6363
}
6464

6565
/**

aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/transfer/TransferDBHelper.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ import android.database.sqlite.SQLiteOpenHelper
2424
import android.database.sqlite.SQLiteQueryBuilder
2525
import android.net.Uri
2626
import android.text.TextUtils
27-
import androidx.annotation.VisibleForTesting
2827
import com.amplifyframework.core.Amplify
2928
import com.amplifyframework.core.category.CategoryType
3029
import com.amplifyframework.storage.s3.AWSS3StoragePlugin
3130

32-
@VisibleForTesting
33-
internal class TransferDBHelper(private val context: Context) :
34-
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
31+
internal class TransferDBHelper(private val context: Context) : SQLiteOpenHelper(
32+
context,
33+
DATABASE_NAME,
34+
null,
35+
DATABASE_VERSION
36+
) {
3537

3638
internal val contentUri: Uri
3739
private val uriMatcher: UriMatcher

aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/transfer/TransferManager.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,16 @@ import kotlin.math.min
4343
* download files. It inserts upload and download records into the database and
4444
* enqueue a WorkRequest for WorkManager to service the transfer
4545
*/
46-
internal class TransferManager @JvmOverloads constructor(
46+
internal class TransferManager(
4747
context: Context,
4848
s3: S3Client,
4949
private val pluginKey: String,
5050
private val workManager: WorkManager = WorkManager.getInstance(context)
5151
) {
5252

5353
private val transferDB: TransferDB = TransferDB.getInstance(context)
54-
val transferStatusUpdater: TransferStatusUpdater = TransferStatusUpdater.getInstance(context)
54+
val transferStatusUpdater: TransferStatusUpdater = TransferStatusUpdater(transferDB)
55+
5556
private val logger =
5657
Amplify.Logging.logger(
5758
CategoryType.STORAGE,

aws-storage-s3/src/main/java/com/amplifyframework/storage/s3/transfer/TransferStatusUpdater.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
package com.amplifyframework.storage.s3.transfer
1717

18-
import android.content.Context
1918
import android.os.Handler
2019
import android.os.Looper
2120
import com.amplifyframework.core.Amplify
@@ -28,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap
2827
/**
2928
* Updates transfer status to observers and to local DB
3029
**/
31-
internal class TransferStatusUpdater private constructor(
30+
internal class TransferStatusUpdater(
3231
private val transferDB: TransferDB
3332
) {
3433
private val logger =
@@ -64,13 +63,7 @@ internal class TransferStatusUpdater private constructor(
6463
}
6564

6665
companion object {
67-
6866
internal const val TEMP_FILE_PREFIX = "aws-s3-d861b25a-1edf-11eb-adc1-0242ac120002"
69-
70-
@JvmStatic
71-
fun getInstance(context: Context): TransferStatusUpdater {
72-
return TransferStatusUpdater(TransferDB.getInstance(context))
73-
}
7467
}
7568

7669
@Synchronized
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.amplifyframework.storage.s3.transfer
2+
3+
import io.kotest.matchers.types.shouldBeSameInstanceAs
4+
import org.junit.After
5+
import org.junit.Test
6+
import org.junit.runner.RunWith
7+
import org.robolectric.RobolectricTestRunner
8+
import org.robolectric.RuntimeEnvironment
9+
10+
@RunWith(RobolectricTestRunner::class)
11+
class TransferDBTest {
12+
13+
@After
14+
fun teardown() {
15+
// Clear instance
16+
TransferDB.instance = null
17+
}
18+
19+
@Test
20+
fun `getInstance returns the same object`() {
21+
val context = RuntimeEnvironment.getApplication()
22+
23+
val db1 = TransferDB.getInstance(context)
24+
val db2 = TransferDB.getInstance(context)
25+
26+
db1 shouldBeSameInstanceAs db2
27+
}
28+
}

0 commit comments

Comments
 (0)