Skip to content

Commit a4a13d3

Browse files
committed
FIX possible deadlocks and added deadlocks unit tests
1 parent ce3987f commit a4a13d3

File tree

2 files changed

+118
-36
lines changed

2 files changed

+118
-36
lines changed

KakapoExample/KakapoTests/KakapoDBTests.swift

+102-14
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ class KakapoDBTests: QuickSpec {
104104

105105
it("should insert a large number of elements") {
106106
dispatch_apply(1000, dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT), { _ in
107-
sut.insert({ (id) -> UserFactory in
107+
sut.insert { (id) -> UserFactory in
108108
return UserFactory(firstName: "Name " + String(id), lastName: "Last Name " + String(id), age: id, id: id)
109-
})
109+
}
110110
})
111111

112112
let userObjects = sut.findAll(UserFactory.self)
@@ -142,9 +142,9 @@ class KakapoDBTests: QuickSpec {
142142
}
143143

144144
it("shoud return the expected object after inserting it") {
145-
sut.insert({ (id) -> UserFactory in
145+
sut.insert { (id) -> UserFactory in
146146
return UserFactory(firstName: "Hector", lastName: "Zarco", age:25, id: id)
147-
})
147+
}
148148

149149
let user = sut.find(UserFactory.self, id: 0)
150150
expect(user?.firstName).to(match("Hector"))
@@ -153,9 +153,9 @@ class KakapoDBTests: QuickSpec {
153153
}
154154

155155
it("should fail when inserting invalid id") {
156-
sut.insert({ (id) -> UserFactory in
156+
sut.insert { (id) -> UserFactory in
157157
return UserFactory(firstName: "Joan", lastName: "Romano", age:25, id: id)
158-
})
158+
}
159159

160160
// TODO: TEST THIS FATAL ERROR
161161
// expect{ sut.insert({ (id) -> UserFactory in
@@ -164,9 +164,9 @@ class KakapoDBTests: QuickSpec {
164164
}
165165

166166
it("should return the expected filtered element with valid id") {
167-
sut.insert({ (id) -> UserFactory in
167+
sut.insert { (id) -> UserFactory in
168168
UserFactory(firstName: "Hector", lastName: "Zarco", age:25, id: id)
169-
})
169+
}
170170

171171
let userArray = sut.filter(UserFactory.self, includeElement: { (item) -> Bool in
172172
return item.id == 0
@@ -180,9 +180,9 @@ class KakapoDBTests: QuickSpec {
180180

181181
it("should return no objects for some inexisting filtering") {
182182
sut.create(UserFactory.self, number: 20)
183-
sut.insert({ (id) -> UserFactory in
183+
sut.insert { (id) -> UserFactory in
184184
return UserFactory(firstName: "Hector", lastName: "Zarco", age:25, id: id)
185-
})
185+
}
186186

187187
let userArray = sut.filter(UserFactory.self, includeElement: { (item) -> Bool in
188188
return item.lastName == "Manzella"
@@ -191,6 +191,94 @@ class KakapoDBTests: QuickSpec {
191191
expect(userArray.count) == 0
192192
}
193193
}
194+
195+
// 💀💀💀💀💀💀💀💀
196+
describe("Database Operations Deadlock") {
197+
let sut = KakapoDB()
198+
let queue = dispatch_queue_create("com.kakapodb.testDeadlock", DISPATCH_QUEUE_SERIAL)
199+
200+
it("should not deadlock when writing into database during a writing operation") {
201+
let user = sut.insert { (id) -> UserFactory in
202+
sut.insert { (id) -> UserFactory in
203+
return UserFactory(id: id, db: sut)
204+
}
205+
206+
return UserFactory(id: id, db: sut)
207+
}
208+
209+
expect(user).toEventuallyNot(beNil())
210+
}
211+
212+
it("should not deadlock when synchronously writing from another queue into database during a writing operation") {
213+
let user = sut.insert { (id) -> UserFactory in
214+
dispatch_sync(queue) {
215+
sut.insert { (id) -> UserFactory in
216+
return UserFactory(id: id, db: sut)
217+
}
218+
}
219+
return UserFactory(id: id, db: sut)
220+
}
221+
expect(user).toEventuallyNot(beNil())
222+
}
223+
224+
it("should not deadlock when writing into database during a reading operation") {
225+
let result = sut.filter(UserFactory.self, includeElement: { (_) -> Bool in
226+
sut.create(UserFactory)
227+
return true
228+
})
229+
230+
expect(result).toEventuallyNot(beNil())
231+
}
232+
233+
it("should not deadlock when synchronously writing from another queue into database during a reading operation") {
234+
let result = sut.filter(UserFactory.self, includeElement: { (_) -> Bool in
235+
dispatch_sync(queue) {
236+
sut.create(UserFactory)
237+
}
238+
return true
239+
})
240+
241+
expect(result).toEventuallyNot(beNil())
242+
}
243+
244+
it("should not deadlock when reading the database during a read operation") {
245+
let result = sut.filter(UserFactory.self, includeElement: { (_) -> Bool in
246+
sut.findAll(UserFactory.self)
247+
return true
248+
})
249+
250+
expect(result).toEventuallyNot(beNil())
251+
}
252+
253+
it("should not deadlock when synchronously reading the database from another queue during a reading operation") {
254+
let result = sut.filter(UserFactory.self, includeElement: { (_) -> Bool in
255+
dispatch_sync(queue) {
256+
sut.findAll(UserFactory.self)
257+
}
258+
return true
259+
})
260+
261+
expect(result).toEventuallyNot(beNil())
262+
}
263+
264+
it("should not deadlock when reading the database during a write operation") {
265+
let user = sut.insert { (id) -> UserFactory in
266+
sut.findAll(UserFactory.self)
267+
return UserFactory(id: id, db: sut)
268+
}
269+
expect(user).toEventuallyNot(beNil())
270+
}
271+
272+
it("should not deadlock when synchronously reading the database from another queue during a write operation") {
273+
let user = sut.insert { (id) -> UserFactory in
274+
dispatch_sync(queue) {
275+
sut.findAll(UserFactory.self)
276+
}
277+
return UserFactory(id: id, db: sut)
278+
}
279+
expect(user).toEventuallyNot(beNil())
280+
}
281+
}
194282
}
195283
}
196284

@@ -200,19 +288,19 @@ class KakapoDBPerformaceTests: XCTestCase {
200288

201289
func testMultipleSingleCreationPerformance() {
202290
measureBlock {
203-
dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { _ in
291+
dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { _ in
204292
self.sut.create(UserFactory.self)
205-
})
293+
}
206294
}
207295
}
208296

209297
func testMultpleInsertions() {
210298
measureBlock {
211-
dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { _ in
299+
dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { _ in
212300
self.sut.insert { (id) -> UserFactory in
213301
return UserFactory(id: id, db: self.sut)
214302
}
215-
})
303+
}
216304
}
217305
}
218306
}

Source/KakapoDB.swift

+16-22
Original file line numberDiff line numberDiff line change
@@ -66,31 +66,33 @@ public class KakapoDB {
6666
}
6767

6868
public func create<T: Storable>(_: T.Type, number: Int = 1) -> [T] {
69-
var result = [T]()
69+
var ids = [Int]()
70+
synchronizeDBWrite {
71+
ids = (0..<number).map { _ in self.uuid()}
72+
}
7073

74+
let result = ids.map { id in T(id: id, db: self) }
7175
synchronizeDBWrite {
72-
result = (0..<number).map { _ in T(id: self.uuid(), db: self) }
7376
self.lookup(T).value.appendContentsOf(result.flatMap{ $0 as Storable })
7477
}
7578

7679
return result
7780
}
7881

7982
public func insert<T: Storable>(handler: (Int) -> T) -> T {
80-
var obj: T? = nil
83+
var id: Int = 0
8184
synchronizeDBWrite {
82-
let potentialId = self._uuid + 1
83-
let object = handler(potentialId)
84-
obj = object
85+
id = self.uuid()
86+
}
87+
88+
let object = handler(id)
8589

86-
if object.id < potentialId {
87-
fatalError("Tried to insert an invalid id")
88-
} else {
89-
self.lookup(T).value.append(object)
90-
self.uuid()
91-
}
90+
precondition(object.id == id, "Tried to insert an invalid id")
91+
synchronizeDBWrite {
92+
self.lookup(T).value.append(object)
9293
}
93-
return obj!
94+
95+
return object
9496
}
9597

9698
public func find<T: Storable>(_: T.Type, id: Int) -> T? {
@@ -118,15 +120,7 @@ public class KakapoDB {
118120
}
119121

120122
public func filter<T: Storable>(_: T.Type, includeElement: (T) -> Bool) -> [T] {
121-
var result: [T] = []
122-
123-
synchronizeDBRead { [weak self] in
124-
guard let weakSelf = self else { return }
125-
126-
result = weakSelf.lookup(T).value.flatMap{$0 as? T}.filter(includeElement)
127-
}
128-
129-
return result
123+
return findAll(T).filter(includeElement)
130124
}
131125

132126
private func uuid() -> Int {

0 commit comments

Comments
 (0)