@@ -168,47 +168,66 @@ extension DatabasePool {
168168
169169 // MARK: - Memory management
170170
171- /// Frees as much memory as possible, by disposing non-essential memory from
172- /// the writer connection, and closing all reader connections.
171+ /// Frees as much memory as possible, by disposing non-essential memory.
173172 ///
174173 /// This method is synchronous, and blocks the current thread until all
175174 /// database accesses are completed.
176175 ///
176+ /// This method closes all read-only connections, unless the
177+ /// ``Configuration/persistentReadOnlyConnections`` configuration flag
178+ /// is set.
179+ ///
177180 /// - warning: This method can prevent concurrent reads from executing,
178181 /// until it returns. Prefer ``releaseMemoryEventually()`` if you intend
179182 /// to keep on using the database while releasing memory.
180183 public func releaseMemory( ) {
181184 // Release writer memory
182185 writer. sync { $0. releaseMemory ( ) }
183186
184- // Release readers memory by closing all connections.
185- //
186- // We must use a barrier in order to guarantee that memory has been
187- // freed (reader connections closed) when the method exits, as
188- // documented.
189- //
190- // Without the barrier, connections would only close _eventually_ (after
191- // their eventual concurrent jobs have completed).
192- readerPool? . barrier {
193- readerPool? . removeAll ( )
187+ if configuration. persistentReadOnlyConnections {
188+ // Keep existing readers
189+ readerPool? . forEach { reader in
190+ reader. sync { $0. releaseMemory ( ) }
191+ }
192+ } else {
193+ // Release readers memory by closing all connections.
194+ //
195+ // We must use a barrier in order to guarantee that memory has been
196+ // freed (reader connections closed) when the method exits, as
197+ // documented.
198+ //
199+ // Without the barrier, connections would only close _eventually_ (after
200+ // their eventual concurrent jobs have completed).
201+ readerPool? . barrier {
202+ readerPool? . removeAll ( )
203+ }
194204 }
195205 }
196206
197- /// Eventually frees as much memory as possible, by disposing non-essential
198- /// memory from the writer connection, and closing all reader connections.
207+ /// Eventually frees as much memory as possible, by disposing
208+ /// non-essential memory.
209+ ///
210+ /// This method eventually closes all read-only connections, unless the
211+ /// ``Configuration/persistentReadOnlyConnections`` configuration flag
212+ /// is set.
199213 ///
200214 /// Unlike ``releaseMemory()``, this method does not prevent concurrent
201215 /// database accesses when it is executing. But it does not notify when
202216 /// non-essential memory has been freed.
203217 public func releaseMemoryEventually( ) {
204- // Release readers memory by eventually closing all reader connections
205- // (they will close after their current jobs have completed).
206- readerPool? . removeAll ( )
218+ if configuration. persistentReadOnlyConnections {
219+ // Keep existing readers
220+ readerPool? . forEach { reader in
221+ reader. async { $0. releaseMemory ( ) }
222+ }
223+ } else {
224+ // Release readers memory by eventually closing all reader connections
225+ // (they will close after their current jobs have completed).
226+ readerPool? . removeAll ( )
227+ }
207228
208229 // Release writer memory eventually.
209- writer. async { db in
210- db. releaseMemory ( )
211- }
230+ writer. async { $0. releaseMemory ( ) }
212231 }
213232
214233 #if os(iOS)
@@ -608,8 +627,12 @@ extension DatabasePool: DatabaseReader {
608627 /// After this method is called, read-only database access methods will use
609628 /// new SQLite connections.
610629 ///
611- /// Eventual concurrent read-only accesses are not invalidated: they will
630+ /// Eventual concurrent read-only accesses are not interrupted, and
612631 /// proceed until completion.
632+ ///
633+ /// - This method closes all read-only connections, even if the
634+ /// ``Configuration/persistentReadOnlyConnections`` configuration flag
635+ /// is set.
613636 public func invalidateReadOnlyConnections( ) {
614637 readerPool? . removeAll ( )
615638 }
@@ -640,6 +663,50 @@ extension DatabasePool: DatabaseReader {
640663 return readers. first { $0. onValidQueue }
641664 }
642665
666+ // MARK: - WAL Snapshot Transactions
667+
668+ // swiftlint:disable:next line_length
669+ #if SQLITE_ENABLE_SNAPSHOT || (!GRDBCUSTOMSQLITE && !GRDBCIPHER && (compiler(>=5.7.1) || !(os(macOS) || targetEnvironment(macCatalyst))))
670+ /// Returns a long-lived WAL snapshot transaction on a reader connection.
671+ func walSnapshotTransaction( ) throws -> WALSnapshotTransaction {
672+ guard let readerPool else {
673+ throw DatabaseError . connectionIsClosed ( )
674+ }
675+
676+ let ( reader, releaseReader) = try readerPool. get ( )
677+ return try WALSnapshotTransaction ( onReader: reader, release: { isInsideTransaction in
678+ // Discard the connection if the transaction could not be
679+ // properly ended. If we'd reuse it, the next read would
680+ // fail because we'd fail starting a read transaction.
681+ releaseReader ( isInsideTransaction ? . discard : . reuse)
682+ } )
683+ }
684+
685+ /// Returns a long-lived WAL snapshot transaction on a reader connection.
686+ ///
687+ /// - important: The `completion` argument is executed in a serial
688+ /// dispatch queue, so make sure you use the transaction asynchronously.
689+ func asyncWALSnapshotTransaction( _ completion: @escaping ( Result < WALSnapshotTransaction , Error > ) -> Void ) {
690+ guard let readerPool else {
691+ completion ( . failure( DatabaseError . connectionIsClosed ( ) ) )
692+ return
693+ }
694+
695+ readerPool. asyncGet { result in
696+ completion ( result. flatMap { reader, releaseReader in
697+ Result {
698+ try WALSnapshotTransaction ( onReader: reader, release: { isInsideTransaction in
699+ // Discard the connection if the transaction could not be
700+ // properly ended. If we'd reuse it, the next read would
701+ // fail because we'd fail starting a read transaction.
702+ releaseReader ( isInsideTransaction ? . discard : . reuse)
703+ } )
704+ }
705+ } )
706+ }
707+ }
708+ #endif
709+
643710 // MARK: - Database Observation
644711
645712 public func _add< Reducer: ValueReducer > (
0 commit comments