Skip to content

Commit 4a6fa5d

Browse files
authored
chore: Use NSLock instead of DispatchQueue to protect Atomics (#868)
1 parent 582c7dd commit 4a6fa5d

8 files changed

+106
-45
lines changed

.gitallowed

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ case slave = "den"
22
amazon-textract-code-samples/master/src-csharp
33
aws-amplify/amplify-ci-support', branch: 'master'
44
branch: master
5+
build-support/codecov.sh

Amplify/Core/Support/AtomicDictionary.swift

+29-15
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99

1010
final class AtomicDictionary<Key: Hashable, Value> {
11-
let queue = DispatchQueue(label: "com.amazonaws.AtomicDictionary", target: DispatchQueue.global())
11+
let lock = NSLock()
1212

1313
private var value: [Key: Value]
1414

@@ -17,48 +17,62 @@ final class AtomicDictionary<Key: Hashable, Value> {
1717
}
1818

1919
var count: Int {
20-
queue.sync {
21-
value.count
20+
lock.lock()
21+
defer {
22+
lock.unlock()
2223
}
24+
return value.count
2325
}
2426

2527
var keys: [Key] {
26-
queue.sync {
27-
Array(value.keys)
28+
lock.lock()
29+
defer {
30+
lock.unlock()
2831
}
32+
return Array(value.keys)
2933
}
3034

3135
var values: [Value] {
32-
return queue.sync {
33-
Array(value.values)
36+
lock.lock()
37+
defer {
38+
lock.unlock()
3439
}
40+
return Array(value.values)
3541
}
3642

3743
// MARK: - Functions
3844

3945
func getValue(forKey key: Key) -> Value? {
40-
queue.sync {
41-
value[key]
46+
lock.lock()
47+
defer {
48+
lock.unlock()
4249
}
50+
return value[key]
4351
}
4452

4553
func removeAll() {
46-
queue.sync {
47-
value = [:]
54+
lock.lock()
55+
defer {
56+
lock.unlock()
4857
}
58+
value = [:]
4959
}
5060

5161
@discardableResult
5262
func removeValue(forKey key: Key) -> Value? {
53-
queue.sync {
54-
value.removeValue(forKey: key)
63+
lock.lock()
64+
defer {
65+
lock.unlock()
5566
}
67+
return value.removeValue(forKey: key)
5668
}
5769

5870
func set(value: Value, forKey key: Key) {
59-
queue.sync {
60-
self.value[key] = value
71+
lock.lock()
72+
defer {
73+
lock.unlock()
6174
}
75+
self.value[key] = value
6276
}
6377

6478
}

Amplify/Core/Support/AtomicValue+Bool.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ extension AtomicValue where Value == Bool {
1616
/// print(atomicBool.get()) // prints "false"
1717
/// ```
1818
public func getAndToggle() -> Value {
19-
queue.sync {
20-
let oldValue = value
21-
value.toggle()
22-
return oldValue
19+
lock.lock()
20+
defer {
21+
lock.unlock()
2322
}
23+
let oldValue = value
24+
value.toggle()
25+
return oldValue
2426
}
2527
}

Amplify/Core/Support/AtomicValue+Numeric.swift

+10-6
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@
88
extension AtomicValue where Value: Numeric {
99
/// Increments the current value by `amount` and returns the incremented value
1010
public func increment(by amount: Value = 1) -> Value {
11-
return queue.sync {
12-
value += amount
13-
return value
11+
lock.lock()
12+
defer {
13+
lock.unlock()
1414
}
15+
value += amount
16+
return value
1517
}
1618

1719
/// Decrements the current value by `amount` and returns the decremented value
1820
public func decrement(by amount: Value = 1) -> Value {
19-
return queue.sync {
20-
value -= amount
21-
return value
21+
lock.lock()
22+
defer {
23+
lock.unlock()
2224
}
25+
value -= amount
26+
return value
2327
}
2428
}

Amplify/Core/Support/AtomicValue+RangeReplaceableCollection.swift

+16-8
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,34 @@
77

88
extension AtomicValue where Value: RangeReplaceableCollection {
99
public func append(_ newElement: Value.Element) {
10-
queue.sync {
11-
value.append(newElement)
10+
lock.lock()
11+
defer {
12+
lock.unlock()
1213
}
14+
value.append(newElement)
1315
}
1416

1517
public func append<S>(contentsOf sequence: S) where S: Sequence, S.Element == Value.Element {
16-
queue.sync {
17-
value.append(contentsOf: sequence)
18+
lock.lock()
19+
defer {
20+
lock.unlock()
1821
}
22+
value.append(contentsOf: sequence)
1923
}
2024

2125
public func removeFirst() -> Value.Element {
22-
queue.sync {
23-
value.removeFirst()
26+
lock.lock()
27+
defer {
28+
lock.unlock()
2429
}
30+
return value.removeFirst()
2531
}
2632

2733
public subscript(_ key: Value.Index) -> Value.Element {
28-
queue.sync {
29-
value[key]
34+
lock.lock()
35+
defer {
36+
lock.unlock()
3037
}
38+
return value[key]
3139
}
3240
}

Amplify/Core/Support/AtomicValue.swift

+25-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99

1010
public final class AtomicValue<Value> {
11-
let queue = DispatchQueue(label: "com.amazonaws.AtomicValue", target: DispatchQueue.global())
11+
let lock = NSLock()
1212

1313
var value: Value
1414

@@ -17,35 +17,49 @@ public final class AtomicValue<Value> {
1717
}
1818

1919
public func get() -> Value {
20-
queue.sync { value }
20+
lock.lock()
21+
defer {
22+
lock.unlock()
23+
}
24+
return value
2125
}
2226

2327
public func set(_ newValue: Value) {
24-
queue.sync { value = newValue }
28+
lock.lock()
29+
defer {
30+
lock.unlock()
31+
}
32+
value = newValue
2533
}
2634

2735
/// Sets AtomicValue to `newValue` and returns the old value
2836
public func getAndSet(_ newValue: Value) -> Value {
29-
return queue.sync {
30-
let oldValue = value
31-
value = newValue
32-
return oldValue
37+
lock.lock()
38+
defer {
39+
lock.unlock()
3340
}
41+
let oldValue = value
42+
value = newValue
43+
return oldValue
3444
}
3545

3646
/// Performs `block` with the current value, preventing other access until the block exits.
3747
public func atomicallyPerform(block: (Value) -> Void) {
38-
queue.sync {
39-
block(value)
48+
lock.lock()
49+
defer {
50+
lock.unlock()
4051
}
52+
block(value)
4153
}
4254

4355
/// Performs `block` with an `inout` value, preventing other access until the block exits,
4456
/// and enabling the block to mutate the value
4557
public func with(block: (inout Value) -> Void) {
46-
queue.sync {
47-
block(&value)
58+
lock.lock()
59+
defer {
60+
lock.unlock()
4861
}
62+
block(&value)
4963
}
5064

5165
}

AmplifyTests/CoreTests/AtomicDictionaryTests.swift

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ import XCTest
1010

1111
class AtomicDictionaryTests: XCTestCase {
1212

13+
func testPerformance() {
14+
let atomicDictionary = AtomicDictionary(initialValue: [Int: Int]())
15+
measure {
16+
DispatchQueue.concurrentPerform(iterations: 10_000) { iteration in
17+
atomicDictionary.set(value: iteration, forKey: iteration)
18+
}
19+
}
20+
}
21+
1322
/// Given: An AtomicDictionary
1423
/// When:
1524
/// - I `get` a nonexistent key

AmplifyTests/CoreTests/AtomicValueTests.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@ import XCTest
1111
// These tests must be run with ThreadSanitizer enabled
1212
class AtomicValueTests: XCTestCase {
1313

14+
func testPerformance() {
15+
let atomicInt = AtomicValue(initialValue: 0)
16+
measure {
17+
DispatchQueue.concurrentPerform(iterations: 10_000) { _ in
18+
_ = atomicInt.increment()
19+
}
20+
}
21+
}
22+
1423
func testSimpleSet() {
1524
let atomicInt = AtomicValue(initialValue: -1)
1625

1726
DispatchQueue.concurrentPerform(iterations: 10_000) { iteration in
18-
_ = atomicInt.set(iteration)
27+
atomicInt.set(iteration)
1928
}
2029

2130
XCTAssertNotEqual(atomicInt.get(), -1)

0 commit comments

Comments
 (0)