Skip to content

Commit 82d0cbe

Browse files
committed
Don't need to block on disk fetch for removeAll.
This insight comes from the fact that we don't evict any key-value pairs from in-memory structure. Thus, once removeAll issued, we know for sure all values thereafter won't be fetched from disk any more (because everything is removed). If later someone added a new value, they must be available in the in-memory structure first, thus, no need to do any disk fetch after removeAll call for key accesses. During removeAll, we don't need to fetch from disk as well, because we can simply mark a flag and avoid disk trip for the key accesses then on.
1 parent 67e558d commit 82d0cbe

File tree

5 files changed

+90
-45
lines changed

5 files changed

+90
-45
lines changed

src/Publisher.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import Combine
33

44
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
5-
open class AtomPublisher<Element: Atom>: Publisher where Element: Equatable {
5+
open class AtomPublisher<Element: Atom & Equatable>: Publisher {
66
public typealias Output = SubscribedObject<Element>
77
public typealias Failure = Never
88
public init() {}
@@ -12,7 +12,7 @@
1212
}
1313

1414
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
15-
open class FetchedResultPublisher<Element: Atom>: Publisher where Element: Equatable {
15+
open class FetchedResultPublisher<Element: Atom & Equatable>: Publisher {
1616
public typealias Output = FetchedResult<Element>
1717
public typealias Failure = Never
1818
public init() {}
@@ -22,7 +22,7 @@
2222
}
2323

2424
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
25-
open class QueryPublisher<Element: Atom>: Publisher where Element: Equatable {
25+
open class QueryPublisher<Element: Atom & Equatable>: Publisher {
2626
public typealias Output = FetchedResult<Element>
2727
public typealias Failure = Never
2828
public init() {}
@@ -32,7 +32,7 @@
3232
}
3333

3434
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
35-
open class QueryPublisherBuilder<Element: Atom> where Element: Equatable {
35+
open class QueryPublisherBuilder<Element: Atom & Equatable> {
3636
public init() {}
3737
/**
3838
* Subscribe to a query against the Workspace. This is coupled with `publisher(for: Element.self)` method

src/Workspace.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ public protocol Workspace: Queryable {
134134
* - Returns: A subscription object that you can cancel the subscription. If no one hold the subscription
135135
* object, it will cancel automatically.
136136
*/
137-
func subscribe<Element: Atom>(
137+
func subscribe<Element: Atom & Equatable>(
138138
fetchedResult: FetchedResult<Element>,
139139
changeHandler: @escaping (_: FetchedResult<Element>) -> Void
140-
) -> Subscription where Element: Equatable
140+
) -> Subscription
141141
/**
142142
* Subscribe to changes of an object. If anything in the object changed or
143143
* the object itself is deleted. Deletion is a terminal event for subscription.
@@ -154,28 +154,27 @@ public protocol Workspace: Queryable {
154154
* - Returns: A subscription object that you can cancel on. If no one hold the subscription, it will cancel
155155
* automatically.
156156
*/
157-
func subscribe<Element: Atom>(
157+
func subscribe<Element: Atom & Equatable>(
158158
object: Element, changeHandler: @escaping (_: SubscribedObject<Element>) -> Void
159-
) -> Subscription where Element: Equatable
159+
) -> Subscription
160160
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
161161
// MARK - Combine-compliant
162162
/**
163163
* Return a publisher for object subscription in Combine.
164164
*/
165165
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
166-
func publisher<Element: Atom>(for: Element) -> AtomPublisher<Element> where Element: Equatable
166+
func publisher<Element: Atom & Equatable>(for: Element) -> AtomPublisher<Element>
167167
/**
168168
* Return a publisher for fetched result subscription in Combine.
169169
*/
170170
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
171-
func publisher<Element: Atom>(for: FetchedResult<Element>) -> FetchedResultPublisher<Element>
172-
where Element: Equatable
171+
func publisher<Element: Atom & Equatable>(for: FetchedResult<Element>)
172+
-> FetchedResultPublisher<Element>
173173
/**
174174
* Return a publisher builder for query subscription in Combine.
175175
*/
176176
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
177-
func publisher<Element: Atom>(for: Element.Type) -> QueryPublisherBuilder<Element>
178-
where Element: Equatable
177+
func publisher<Element: Atom & Equatable>(for: Element.Type) -> QueryPublisherBuilder<Element>
179178
#endif
180179
}
181180

src/sqlite/SQLitePublisher.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Dispatch
55
import Combine
66

77
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
8-
final class SQLiteAtomPublisher<Element: Atom>: AtomPublisher<Element> where Element: Equatable {
8+
final class SQLiteAtomPublisher<Element: Atom & Equatable>: AtomPublisher<Element> {
99
final class AtomSubscription<S: Subscriber>: Subscription
1010
where Failure == S.Failure, Output == S.Input {
1111
private let subscriber: S
@@ -46,8 +46,10 @@ import Dispatch
4646
}
4747

4848
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
49-
final class SQLiteFetchedResultPublisher<Element: Atom>: FetchedResultPublisher<Element>
50-
where Element: Equatable {
49+
final class SQLiteFetchedResultPublisher<Element: Atom & Equatable>: FetchedResultPublisher<
50+
Element
51+
>
52+
{
5153
final class FetchedResultSubscription<S: Subscriber>: Subscription
5254
where Failure == S.Failure, Output == S.Input {
5355
private let subscriber: S
@@ -86,8 +88,7 @@ import Dispatch
8688
}
8789

8890
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
89-
final class SQLiteQueryPublisher<Element: Atom>: QueryPublisher<Element>
90-
where Element: Equatable {
91+
final class SQLiteQueryPublisher<Element: Atom & Equatable>: QueryPublisher<Element> {
9192
final class QuerySubscription<S: Subscriber>: Subscription
9293
where Failure == S.Failure, Output == S.Input {
9394
private let subscriber: S
@@ -147,8 +148,8 @@ import Dispatch
147148
}
148149

149150
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
150-
final class SQLiteQueryPublisherBuilder<Element: Atom>: QueryPublisherBuilder<Element>
151-
where Element: Equatable {
151+
final class SQLiteQueryPublisherBuilder<Element: Atom & Equatable>: QueryPublisherBuilder<Element>
152+
{
152153
private let workspace: SQLiteWorkspace
153154
init(workspace: SQLiteWorkspace) {
154155
self.workspace = workspace

src/sqlite/SQLiteWorkspace.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,10 @@ public final class SQLiteWorkspace: Workspace {
366366

367367
// MARK - Observation
368368

369-
public func subscribe<Element: Atom>(
369+
public func subscribe<Element: Atom & Equatable>(
370370
fetchedResult: FetchedResult<Element>,
371371
changeHandler: @escaping (_: FetchedResult<Element>) -> Void
372-
) -> Workspace.Subscription where Element: Equatable {
372+
) -> Workspace.Subscription {
373373
let fetchedResult = fetchedResult as! SQLiteFetchedResult<Element>
374374
let identifier = ObjectIdentifier(fetchedResult.query)
375375
let subscription = SQLiteSubscription(
@@ -430,9 +430,9 @@ public final class SQLiteWorkspace: Workspace {
430430
return subscription
431431
}
432432

433-
public func subscribe<Element: Atom>(
433+
public func subscribe<Element: Atom & Equatable>(
434434
object: Element, changeHandler: @escaping (_: SubscribedObject<Element>) -> Void
435-
) -> Workspace.Subscription where Element: Equatable {
435+
) -> Workspace.Subscription {
436436
let subscription = SQLiteSubscription(
437437
ofType: .object(Element.self, object._rowid), workspace: self)
438438
guard
@@ -520,21 +520,22 @@ public final class SQLiteWorkspace: Workspace {
520520
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
521521

522522
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
523-
public func publisher<Element: Atom>(for object: Element) -> AtomPublisher<Element>
524-
where Element: Equatable {
523+
public func publisher<Element: Atom & Equatable>(for object: Element) -> AtomPublisher<Element>
524+
{
525525
return SQLiteAtomPublisher<Element>(workspace: self, object: object)
526526
}
527527

528528
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
529-
public func publisher<Element: Atom>(for fetchedResult: FetchedResult<Element>)
530-
-> FetchedResultPublisher<Element> where Element: Equatable
529+
public func publisher<Element: Atom & Equatable>(for fetchedResult: FetchedResult<Element>)
530+
-> FetchedResultPublisher<Element>
531531
{
532532
return SQLiteFetchedResultPublisher<Element>(workspace: self, fetchedResult: fetchedResult)
533533
}
534534

535535
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
536-
public func publisher<Element: Atom>(for: Element.Type) -> QueryPublisherBuilder<Element>
537-
where Element: Equatable {
536+
public func publisher<Element: Atom & Equatable>(for: Element.Type) -> QueryPublisherBuilder<
537+
Element
538+
> {
538539
return SQLiteQueryPublisherBuilder<Element>(workspace: self)
539540
}
540541

src/sqlite/SQLiteWorkspaceDictionary.swift

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Atomics
12
import Dflat
23
import Dispatch
34
import FlatBuffers
@@ -12,6 +13,7 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
1213
let namespace: String
1314
var locks: UnsafeMutablePointer<os_unfair_lock_s>
1415
var dictionaries: [[String: Any]]
16+
var disableDiskFetch = UnsafeAtomic<Bool>.Storage(false)
1517
init(namespace: String) {
1618
self.namespace = namespace
1719
locks = UnsafeMutablePointer.allocate(capacity: Self.size)
@@ -99,6 +101,12 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
99101
return value is None ? nil : (value as! T)
100102
} // Otherwise, try to load from disk.
101103
storage.unlock(tuple.1)
104+
// Don't need to fetch from disk if it is disabled.
105+
guard
106+
!(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
107+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
108+
})
109+
else { return nil }
102110
if let value = workspace.fetch(for: DictItem.self).where(
103111
DictItem.key == key && DictItem.namespace == storage.namespace
104112
).first {
@@ -164,6 +172,12 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
164172
return value is None ? nil : (value as! Bool)
165173
} // Otherwise, try to load from disk.
166174
storage.unlock(tuple.1)
175+
// Don't need to fetch from disk if it is disabled.
176+
guard
177+
!(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
178+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
179+
})
180+
else { return nil }
167181
if let value = workspace.fetch(for: DictItem.self).where(
168182
DictItem.key == key && DictItem.namespace == storage.namespace
169183
).first {
@@ -213,6 +227,12 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
213227
return value is None ? nil : (value as! Int)
214228
} // Otherwise, try to load from disk.
215229
storage.unlock(tuple.1)
230+
// Don't need to fetch from disk if it is disabled.
231+
guard
232+
!(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
233+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
234+
})
235+
else { return nil }
216236
if let value = workspace.fetch(for: DictItem.self).where(
217237
DictItem.key == key && DictItem.namespace == storage.namespace
218238
).first {
@@ -262,6 +282,12 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
262282
return value is None ? nil : (value as! UInt)
263283
} // Otherwise, try to load from disk.
264284
storage.unlock(tuple.1)
285+
// Don't need to fetch from disk if it is disabled.
286+
guard
287+
!(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
288+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
289+
})
290+
else { return nil }
265291
if let value = workspace.fetch(for: DictItem.self).where(
266292
DictItem.key == key && DictItem.namespace == storage.namespace
267293
).first {
@@ -312,6 +338,12 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
312338
return value is None ? nil : (value as! Float)
313339
} // Otherwise, try to load from disk.
314340
storage.unlock(tuple.1)
341+
// Don't need to fetch from disk if it is disabled.
342+
guard
343+
!(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
344+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
345+
})
346+
else { return nil }
315347
if let value = workspace.fetch(for: DictItem.self).where(
316348
DictItem.key == key && DictItem.namespace == storage.namespace
317349
).first {
@@ -361,6 +393,12 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
361393
return value is None ? nil : (value as! Double)
362394
} // Otherwise, try to load from disk.
363395
storage.unlock(tuple.1)
396+
// Don't need to fetch from disk if it is disabled.
397+
guard
398+
!(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
399+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
400+
})
401+
else { return nil }
364402
if let value = workspace.fetch(for: DictItem.self).where(
365403
DictItem.key == key && DictItem.namespace == storage.namespace
366404
).first {
@@ -410,6 +448,12 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
410448
return value is None ? nil : (value as! String)
411449
} // Otherwise, try to load from disk.
412450
storage.unlock(tuple.1)
451+
// Don't need to fetch from disk if it is disabled.
452+
guard
453+
!(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
454+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
455+
})
456+
else { return nil }
413457
if let value = workspace.fetch(for: DictItem.self).where(
414458
DictItem.key == key && DictItem.namespace == storage.namespace
415459
).first {
@@ -464,8 +508,14 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
464508
}
465509

466510
var keys: [String] {
467-
let items = workspace.fetch(for: DictItem.self).where(DictItem.namespace == storage.namespace)
468-
var keys = Set(items.map { $0.key })
511+
var keys = Set<String>()
512+
// Only need to fetch from disk if it is not disabled.
513+
if !(withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
514+
UnsafeAtomic(at: $0).load(ordering: .acquiring)
515+
}) {
516+
let items = workspace.fetch(for: DictItem.self).where(DictItem.namespace == storage.namespace)
517+
keys = Set(items.map { $0.key })
518+
}
469519
for i in 0..<Storage.size {
470520
storage.lock(i)
471521
defer { storage.unlock(i) }
@@ -482,28 +532,22 @@ struct SQLiteWorkspaceDictionary: WorkspaceDictionary {
482532
}
483533

484534
func removeAll() {
485-
let namespace = storage.namespace
486-
let items = workspace.fetch(for: DictItem.self).where(DictItem.namespace == namespace)
487-
var keys = [[String]](repeating: [], count: Storage.size)
488-
for key in items.lazy.map(\.key) {
489-
var hasher = Hasher()
490-
key.hash(into: &hasher)
491-
let hashValue = Int(UInt(bitPattern: hasher.finalize()) % UInt(Storage.size))
492-
keys[hashValue].append(key)
493-
}
494535
for i in 0..<Storage.size {
495536
storage.lock(i)
496537
defer { storage.unlock(i) }
497538
// Set existing ones in the dictionaries to be None.
498539
for key in storage.dictionaries[i].keys {
499540
storage.dictionaries[i][key] = None.none
500541
}
501-
// Set the ones fetched from disk to be None.
502-
for key in keys[i] {
503-
storage.dictionaries[i][key] = None.none
504-
}
542+
}
543+
// Since removed everything from disk. We no longer need to fetch from disk in case
544+
// of a miss any more (because the only thing accessible is write to the in-memory
545+
// data structure first).
546+
withUnsafeMutablePointer(to: &storage.disableDiskFetch) {
547+
UnsafeAtomic(at: $0).store(true, ordering: .releasing)
505548
}
506549
let workspace = self.workspace
550+
let namespace = storage.namespace
507551
workspace.performChanges(
508552
[DictItem.self],
509553
changesHandler: { txnContext in

0 commit comments

Comments
 (0)