Skip to content

Commit bbe94a9

Browse files
committed
Make memory management optional.
Memory management may not be desirable for all users of GRDB. `DatabasePool.releaseMemory` creates a barrier which may cause normally fast readers to block for a long time. If this is not a desirable tradeoff, the new `automaticMemoryManagement` flag in `Configuration` allows you to disable this feature of GRDB.
1 parent 84bee19 commit bbe94a9

File tree

4 files changed

+84
-3
lines changed

4 files changed

+84
-3
lines changed

GRDB/Core/Configuration.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,19 @@ public struct Configuration {
275275
///
276276
/// Default: nil
277277
public var writeTargetQueue: DispatchQueue? = nil
278-
278+
279+
/// Sets whether GRDB will release memory when entering the background or
280+
/// upon receiving a memory warning in iOS.
281+
///
282+
/// Enabling this setting may help keep iOS from terminating your app when
283+
/// memory pressure becomes high. However, it can also cause database
284+
/// readers to block longer than they normally would.
285+
///
286+
/// Default: true
287+
#if os(iOS)
288+
public var automaticMemoryManagement = true
289+
#endif
290+
279291
// MARK: - Factory Configuration
280292

281293
/// Creates a factory configuration

GRDB/Core/DatabasePool.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ public final class DatabasePool: DatabaseWriter {
103103
// Be a nice iOS citizen, and don't consume too much memory
104104
// See https://github.com/groue/GRDB.swift/#memory-management
105105
#if os(iOS)
106-
setupMemoryManagement()
106+
if configuration.automaticMemoryManagement {
107+
setupMemoryManagement()
108+
}
107109
#endif
108110
}
109111

@@ -161,6 +163,17 @@ public final class DatabasePool: DatabaseWriter {
161163
writer.sync(body)
162164
readerPool?.forEach { $0.sync(body) }
163165
}
166+
167+
var numberOfReaders: Int {
168+
guard let readerPool = readerPool else {
169+
return 0
170+
}
171+
var count = 0
172+
readerPool.forEach { _ in
173+
count += 1
174+
}
175+
return count
176+
}
164177
}
165178

166179
#if swift(>=5.6) && canImport(_Concurrency)

GRDB/Core/DatabaseQueue.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public final class DatabaseQueue: DatabaseWriter {
4343
// Be a nice iOS citizen, and don't consume too much memory
4444
// See https://github.com/groue/GRDB.swift/#memory-management
4545
#if os(iOS)
46-
setupMemoryManagement()
46+
if configuration.automaticMemoryManagement {
47+
setupMemoryManagement()
48+
}
4749
#endif
4850
}
4951

Tests/GRDBTests/DatabasePoolReleaseMemoryTests.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,60 @@ class DatabasePoolReleaseMemoryTests: GRDBTestCase {
4141
XCTAssertEqual(openConnectionCount, 0)
4242
}
4343

44+
#if os(iOS)
45+
func testDatabasePoolReleasesMemoryOnPressureEvent() throws {
46+
do {
47+
// Create a database pool.
48+
let dbPool = try makeDatabasePool()
49+
50+
// Write and read it to ensure readers exist.
51+
try dbPool.write { db in
52+
try db.execute(sql: "CREATE TABLE items (id INTEGER PRIMARY KEY)")
53+
}
54+
55+
// Precondition: there are readers.
56+
try dbPool.read { _ in }
57+
XCTAssertNotEqual(0, dbPool.numberOfReaders)
58+
59+
// Simulate memory warning.
60+
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "UIApplicationDidReceiveMemoryWarningNotification"),
61+
object: nil)
62+
// Block until memory is released, which happens on a global queue.
63+
dbPool.barrierWriteWithoutTransaction { _ in }
64+
65+
// Postcondition: readers removed.
66+
XCTAssertEqual(0, dbPool.numberOfReaders)
67+
}
68+
}
69+
70+
func testDatabasePoolDoesNotReleaseMemoryOnPressureEventIfDisabled() throws {
71+
do {
72+
// Create a database pool without automatic memory management.
73+
var configuration = dbConfiguration!
74+
configuration.automaticMemoryManagement = false
75+
let dbPool = try makeDatabasePool(configuration: configuration)
76+
77+
// Write and read it to ensure readers exist.
78+
try dbPool.write { db in
79+
try db.execute(sql: "CREATE TABLE items (id INTEGER PRIMARY KEY)")
80+
}
81+
82+
// Precondition: there are readers.
83+
try dbPool.read { _ in }
84+
XCTAssertNotEqual(0, dbPool.numberOfReaders)
85+
86+
// Simulate memory warning.
87+
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "UIApplicationDidReceiveMemoryWarningNotification"),
88+
object: nil)
89+
// Block until memory would be released, which would happen on a global queue.
90+
dbPool.barrierWriteWithoutTransaction { _ in }
91+
92+
// Postcondition: there are readers.
93+
XCTAssertNotEqual(0, dbPool.numberOfReaders)
94+
}
95+
}
96+
#endif
97+
4498
// TODO: fix flaky test
4599
// func testDatabasePoolReleaseMemoryClosesReaderConnections() throws {
46100
// let countQueue = DispatchQueue(label: "GRDB")

0 commit comments

Comments
 (0)