Skip to content

Commit 198a0be

Browse files
author
Clément Le Provost
committed
[offline] Fix race condition in instantiation of local index
It turns out that [lazy variables are not thread-safe](http://stackoverflow.com/a/29761777). We therefore have to synchronize initialization: - For `OfflineIndex`, we can instantiate the local index during init. - For `MirroredIndex`, we still need lazy instantiation, so we synchronize access explicitly.
1 parent 12247c5 commit 198a0be

File tree

3 files changed

+31
-2
lines changed

3 files changed

+31
-2
lines changed

Source/Helpers.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,23 @@ extension Date {
120120
}
121121
}
122122

123+
// MARK: - Thread synchronization
124+
125+
extension NSObject {
126+
/// Equivalent of Objective-C's `@synchronized` statement.
127+
/// Actually leverages the same Objective-C runtime feature (this is why it's only available on `NSObject`).
128+
///
129+
/// - paremeter block: Block to be executed serially on the object.
130+
///
131+
func synchronized(_ block: () -> ()) {
132+
objc_sync_enter(self)
133+
defer {
134+
objc_sync_exit(self)
135+
}
136+
block()
137+
}
138+
}
139+
123140
// MARK: - Miscellaneous
124141

125142
/// The operating system's name.

Source/Offline/MirroredIndex.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ import Foundation
135135
}
136136

137137
/// The local index mirroring this remote index (lazy instantiated, only if mirroring is activated).
138-
lazy var localIndex: LocalIndex = LocalIndex(dataDir: self.offlineClient.rootDataDir, appID: self.client.appID, indexName: self.name)
138+
var localIndex: LocalIndex!
139139

140140
/// The mirrored index settings.
141141
let mirrorSettings = MirrorSettings()
@@ -146,10 +146,21 @@ import Foundation
146146
if (mirrored) {
147147
do {
148148
try FileManager.default.createDirectory(atPath: self.indexDataDir, withIntermediateDirectories: true, attributes: nil)
149+
// Lazy instantiate the local index.
150+
self.synchronized {
151+
if (self.localIndex == nil) {
152+
self.localIndex = LocalIndex(dataDir: self.offlineClient.rootDataDir, appID: self.client.appID, indexName: self.name)
153+
}
154+
}
149155
} catch _ {
150156
// Ignore
151157
}
152158
mirrorSettings.load(self.mirrorSettingsFilePath)
159+
} else {
160+
// Release the local index.
161+
self.synchronized {
162+
self.localIndex = nil
163+
}
153164
}
154165
}
155166
}

Source/Offline/OfflineIndex.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public struct IOError: CustomNSError {
132132
@objc public let client: OfflineClient
133133

134134
/// The local index (lazy instantiated).
135-
lazy var localIndex: LocalIndex = LocalIndex(dataDir: self.client.rootDataDir, appID: self.client.appID, indexName: self.name)
135+
var localIndex: LocalIndex
136136

137137
/// Queue used to run transaction bodies (but not the build).
138138
private let transactionQueue: OperationQueue
@@ -155,6 +155,7 @@ public struct IOError: CustomNSError {
155155
self.name = name
156156
self.transactionQueue = OperationQueue()
157157
self.transactionQueue.maxConcurrentOperationCount = 1
158+
self.localIndex = LocalIndex(dataDir: self.client.rootDataDir, appID: self.client.appID, indexName: self.name)
158159
}
159160

160161
override public var description: String {

0 commit comments

Comments
 (0)