Skip to content

Commit 558ba7f

Browse files
authored
Fix leaked connections and cached statements (#68)
1 parent 2253b78 commit 558ba7f

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

library/src/commonMain/kotlin/com/eygraber/sqldelight/androidx/driver/AndroidxSqliteDriver.kt

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,25 @@ public class AndroidxSqliteDriver(
9292

9393
private val transactions = TransactionsThreadLocal()
9494

95-
private val statementsCache =
96-
object : LruCache<Int, AndroidxStatement>(configuration.cacheSize) {
97-
override fun entryRemoved(
98-
evicted: Boolean,
99-
key: Int,
100-
oldValue: AndroidxStatement,
101-
newValue: AndroidxStatement?,
102-
) {
103-
if(evicted) oldValue.close()
104-
}
95+
private val statementsCache = HashMap<SQLiteConnection, LruCache<Int, AndroidxStatement>>()
96+
97+
private fun getStatementCache(connection: SQLiteConnection) =
98+
when {
99+
configuration.cacheSize > 0 ->
100+
statementsCache.getOrPut(connection) {
101+
object : LruCache<Int, AndroidxStatement>(configuration.cacheSize) {
102+
override fun entryRemoved(
103+
evicted: Boolean,
104+
key: Int,
105+
oldValue: AndroidxStatement,
106+
newValue: AndroidxStatement?,
107+
) {
108+
if(evicted) oldValue.close()
109+
}
110+
}
111+
}
112+
113+
else -> null
105114
}
106115

107116
private var skipStatementsCache = true
@@ -228,7 +237,7 @@ public class AndroidxSqliteDriver(
228237
binders: (SqlPreparedStatement.() -> Unit)?,
229238
result: AndroidxStatement.() -> T,
230239
): QueryResult.Value<T> {
231-
val statementsCache = if(!skipStatementsCache) statementsCache else null
240+
val statementsCache = if(!skipStatementsCache) getStatementCache(connection) else null
232241
var statement: AndroidxStatement? = null
233242
if(identifier != null && statementsCache != null) {
234243
// remove temporarily from the cache if present
@@ -271,7 +280,12 @@ public class AndroidxSqliteDriver(
271280
return execute(
272281
identifier = identifier,
273282
connection = writerConnection,
274-
createStatement = { AndroidxPreparedStatement(it.prepare(sql)) },
283+
createStatement = { c ->
284+
AndroidxPreparedStatement(
285+
sql = sql,
286+
statement = c.prepare(sql),
287+
)
288+
},
275289
binders = binders,
276290
result = { execute() },
277291
)
@@ -283,7 +297,12 @@ public class AndroidxSqliteDriver(
283297
return execute(
284298
identifier = identifier,
285299
connection = connection,
286-
createStatement = { AndroidxPreparedStatement(it.prepare(sql)) },
300+
createStatement = { c ->
301+
AndroidxPreparedStatement(
302+
sql = sql,
303+
statement = c.prepare(sql),
304+
)
305+
},
287306
binders = binders,
288307
result = { execute() },
289308
)
@@ -306,7 +325,13 @@ public class AndroidxSqliteDriver(
306325
return execute(
307326
identifier = identifier,
308327
connection = connection,
309-
createStatement = { AndroidxQuery(sql, it, parameters) },
328+
createStatement = { c ->
329+
AndroidxQuery(
330+
sql = sql,
331+
statement = c.prepare(sql),
332+
argCount = parameters,
333+
)
334+
},
310335
binders = binders,
311336
result = { executeQuery(mapper) },
312337
)
@@ -318,7 +343,13 @@ public class AndroidxSqliteDriver(
318343
return execute(
319344
identifier = identifier,
320345
connection = connection,
321-
createStatement = { AndroidxQuery(sql, it, parameters) },
346+
createStatement = { c ->
347+
AndroidxQuery(
348+
sql = sql,
349+
statement = c.prepare(sql),
350+
argCount = parameters,
351+
)
352+
},
322353
binders = binders,
323354
result = { executeQuery(mapper) },
324355
)
@@ -330,7 +361,8 @@ public class AndroidxSqliteDriver(
330361
* are using any of the connections starting from when close is invoked
331362
*/
332363
override fun close() {
333-
statementsCache.evictAll()
364+
statementsCache.values.forEach { it.evictAll() }
365+
statementsCache.clear()
334366
connectionPool.close()
335367
}
336368

@@ -365,15 +397,15 @@ public class AndroidxSqliteDriver(
365397
0L -> schema.create(driver).value
366398
else -> schema.migrate(driver, currentVersion, schema.version, *migrationCallbacks).value
367399
}
368-
skipStatementsCache = false
400+
skipStatementsCache = configuration.cacheSize == 0
369401
when(currentVersion) {
370402
0L -> onCreate()
371403
else -> onUpdate(currentVersion, schema.version)
372404
}
373405
writerConnection.prepare("PRAGMA user_version = ${schema.version}").use { it.step() }
374406
}
375407
} else {
376-
skipStatementsCache = false
408+
skipStatementsCache = configuration.cacheSize == 0
377409
}
378410

379411
onOpen()
@@ -393,6 +425,7 @@ internal interface AndroidxStatement : SqlPreparedStatement {
393425
}
394426

395427
private class AndroidxPreparedStatement(
428+
private val sql: String,
396429
private val statement: SQLiteStatement,
397430
) : AndroidxStatement {
398431
override fun bindBytes(index: Int, bytes: ByteArray?) {
@@ -430,6 +463,8 @@ private class AndroidxPreparedStatement(
430463
return statement.getColumnCount().toLong()
431464
}
432465

466+
override fun toString() = sql
467+
433468
override fun reset() {
434469
statement.reset()
435470
}
@@ -441,7 +476,7 @@ private class AndroidxPreparedStatement(
441476

442477
private class AndroidxQuery(
443478
private val sql: String,
444-
private val connection: SQLiteConnection,
479+
private val statement: SQLiteStatement,
445480
argCount: Int,
446481
) : AndroidxStatement {
447482
private val binds = MutableList<((SQLiteStatement) -> Unit)?>(argCount) { null }
@@ -477,23 +512,22 @@ private class AndroidxQuery(
477512
override fun execute() = throw UnsupportedOperationException()
478513

479514
override fun <R> executeQuery(mapper: (SqlCursor) -> QueryResult<R>): R {
480-
val statement = connection.prepare(sql)
481515
for(action in binds) {
482516
requireNotNull(action).invoke(statement)
483517
}
484518

485-
return try {
486-
mapper(AndroidxCursor(statement)).value
487-
} finally {
488-
statement.close()
489-
}
519+
return mapper(AndroidxCursor(statement)).value
490520
}
491521

492522
override fun toString() = sql
493523

494-
override fun reset() {}
524+
override fun reset() {
525+
statement.reset()
526+
}
495527

496-
override fun close() {}
528+
override fun close() {
529+
statement.close()
530+
}
497531
}
498532

499533
private class AndroidxCursor(

library/src/commonTest/kotlin/com/eygraber/sqldelight/androidx/driver/AndroidxSqliteCallbackTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,15 @@ abstract class AndroidxSqliteCallbackTest {
121121
@BeforeTest
122122
fun clearNamedDb() {
123123
deleteFile(dbName)
124+
deleteFile("$dbName-shm")
125+
deleteFile("$dbName-wal")
124126
}
125127

126128
@AfterTest
127129
fun clearNamedDbPostTests() {
128130
deleteFile(dbName)
131+
deleteFile("$dbName-shm")
132+
deleteFile("$dbName-wal")
129133
}
130134

131135
@Test

library/src/commonTest/kotlin/com/eygraber/sqldelight/androidx/driver/AndroidxSqliteEphemeralTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,15 @@ abstract class AndroidxSqliteEphemeralTest {
6767
@BeforeTest
6868
fun clearNamedDb() {
6969
deleteFile(dbName)
70+
deleteFile("$dbName-shm")
71+
deleteFile("$dbName-wal")
7072
}
7173

7274
@AfterTest
7375
fun clearNamedDbPostTests() {
7476
deleteFile(dbName)
77+
deleteFile("$dbName-shm")
78+
deleteFile("$dbName-wal")
7579
}
7680

7781
@Test

0 commit comments

Comments
 (0)