Skip to content

Commit 3a65339

Browse files
committed
fix: lock based solution for threading issues
1 parent c8f12b6 commit 3a65339

File tree

5 files changed

+160
-106
lines changed

5 files changed

+160
-106
lines changed

Sources/UnleashProxyClientSwift/Client/UnleashProxyClientSwift.swift

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,22 @@ public class UnleashClientBase {
77

88
public var context: Context {
99
get {
10-
var value: Context!
11-
queue.sync {
12-
value = self._context
13-
}
10+
lock.lock()
11+
let value = self._context
12+
lock.unlock()
1413
return value
1514
}
1615
set {
17-
queue.async(flags: .barrier) {
18-
self._context = newValue
19-
}
16+
lock.lock()
17+
self._context = newValue
18+
lock.unlock()
2019
}
2120
}
2221
var timer: DispatchSourceTimer?
2322
var poller: Poller
2423
var metrics: Metrics
2524
var connectionId: UUID
26-
private let queue = DispatchQueue(label: "com.unleash.clientbase", attributes: .concurrent)
25+
private let lock = NSLock()
2726

2827
public init(
2928
unleashUrl: String,
@@ -106,15 +105,19 @@ public class UnleashClientBase {
106105

107106
public func stop() -> Void {
108107
self.stopPolling()
108+
lock.lock()
109109
timer?.cancel()
110110
timer = nil
111+
lock.unlock()
111112
UnleashEvent.allCases.forEach { self.unsubscribe($0) }
112113
}
113114

114115
public func isEnabled(name: String) -> Bool {
115116
let toggle = poller.getFeature(name: name)
116117
let enabled = toggle?.enabled ?? false
117-
let contextSnapshot = queue.sync { self._context }
118+
lock.lock()
119+
let contextSnapshot = self._context
120+
lock.unlock()
118121

119122
metrics.count(name: name, enabled: enabled)
120123

@@ -135,8 +138,10 @@ public class UnleashClientBase {
135138
let toggle = poller.getFeature(name: name)
136139
let variant = toggle?.variant ?? .defaultDisabled
137140
let enabled = toggle?.enabled ?? false
138-
let contextSnapshot = queue.sync { self._context }
139-
141+
lock.lock()
142+
let contextSnapshot = self._context
143+
lock.unlock()
144+
140145
metrics.count(name: name, enabled: enabled)
141146
metrics.countVariant(name: name, variant: variant.name)
142147

@@ -225,11 +230,15 @@ public class UnleashClientBase {
225230
newProperties[key] = value
226231
}
227232

228-
let sessionId = context["sessionId"] ?? self.context.sessionId;
233+
lock.lock()
234+
let currentContext = self._context
235+
lock.unlock()
236+
237+
let sessionId = context["sessionId"] ?? currentContext.sessionId;
229238

230239
let newContext = Context(
231-
appName: self.context.appName,
232-
environment: self.context.environment,
240+
appName: currentContext.appName,
241+
environment: currentContext.environment,
233242
userId: context["userId"],
234243
sessionId: sessionId,
235244
remoteAddress: context["remoteAddress"],

Sources/UnleashProxyClientSwift/Metrics/Metrics.swift

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class Metrics {
1515
let customHeaders: [String: String]
1616
let connectionId: UUID
1717

18-
private let queue: DispatchQueue
18+
private let lock = NSLock()
1919

2020
init(appName: String,
2121
metricsInterval: TimeInterval,
@@ -25,8 +25,7 @@ public class Metrics {
2525
url: URL,
2626
clientKey: String,
2727
customHeaders: [String: String] = [:],
28-
connectionId: UUID,
29-
queue: DispatchQueue = DispatchQueue(label: "io.getunleash.metrics")) {
28+
connectionId: UUID) {
3029
self.appName = appName
3130
self.metricsInterval = metricsInterval
3231
self.clock = clock
@@ -37,68 +36,80 @@ public class Metrics {
3736
self.bucket = Bucket(clock: clock)
3837
self.customHeaders = customHeaders
3938
self.connectionId = connectionId
40-
self.queue = queue
4139
}
4240

4341
func start() {
44-
if disableMetrics { return }
42+
lock.lock()
43+
let isDisabled = self.disableMetrics
44+
lock.unlock()
4545

46-
queue.sync {
47-
self.timer?.cancel()
48-
self.timer = nil
49-
}
46+
if isDisabled { return }
47+
48+
lock.lock()
49+
self.timer?.cancel()
50+
self.timer = nil
51+
let interval = self.metricsInterval
52+
lock.unlock()
5053

5154
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .background))
52-
timer.schedule(deadline: .now() + self.metricsInterval, repeating: self.metricsInterval)
55+
timer.schedule(deadline: .now() + interval, repeating: interval)
5356
timer.setEventHandler { [weak self] in
5457
guard let self = self else { return }
5558
self.sendMetrics()
5659
}
5760
timer.resume()
5861

59-
queue.sync {
60-
self.timer = timer
61-
}
62+
lock.lock()
63+
self.timer = timer
64+
lock.unlock()
6265
}
6366

6467
func stop() {
65-
queue.sync {
66-
self.timer?.cancel()
67-
self.timer = nil
68-
}
68+
lock.lock()
69+
self.timer?.cancel()
70+
self.timer = nil
71+
lock.unlock()
6972
}
7073

7174
func count(name: String, enabled: Bool) {
72-
if disableMetrics { return }
73-
74-
queue.sync {
75-
var toggle = bucket.toggles[name] ?? ToggleMetrics()
76-
if enabled {
77-
toggle.yes += 1
78-
} else {
79-
toggle.no += 1
80-
}
81-
bucket.toggles[name] = toggle
75+
lock.lock()
76+
let isDisabled = self.disableMetrics
77+
if isDisabled {
78+
lock.unlock()
79+
return
8280
}
81+
82+
var toggle = bucket.toggles[name] ?? ToggleMetrics()
83+
if enabled {
84+
toggle.yes += 1
85+
} else {
86+
toggle.no += 1
87+
}
88+
bucket.toggles[name] = toggle
89+
lock.unlock()
8390
}
8491

8592
func countVariant(name: String, variant: String) {
86-
if disableMetrics { return }
87-
88-
queue.sync {
89-
var toggle = bucket.toggles[name] ?? ToggleMetrics()
90-
toggle.variants[variant, default: 0] += 1
91-
bucket.toggles[name] = toggle
93+
lock.lock()
94+
let isDisabled = self.disableMetrics
95+
if isDisabled {
96+
lock.unlock()
97+
return
9298
}
99+
100+
var toggle = bucket.toggles[name] ?? ToggleMetrics()
101+
toggle.variants[variant, default: 0] += 1
102+
bucket.toggles[name] = toggle
103+
lock.unlock()
93104
}
94105

95106
func sendMetrics() {
96-
let localBucket: Bucket = queue.sync {
97-
bucket.closeBucket()
98-
let result = bucket
99-
bucket = Bucket(clock: clock)
100-
return result
101-
}
107+
lock.lock()
108+
bucket.closeBucket()
109+
let localBucket = bucket
110+
let clockFunction = self.clock
111+
bucket = Bucket(clock: clockFunction)
112+
lock.unlock()
102113

103114
guard !localBucket.isEmpty() else { return }
104115

@@ -122,18 +133,26 @@ public class Metrics {
122133
}
123134

124135
func createRequest(payload: Data) -> URLRequest {
125-
var request = URLRequest(url: url.appendingPathComponent("client/metrics"))
136+
lock.lock()
137+
let urlValue = self.url
138+
let clientKeyValue = self.clientKey
139+
let appNameValue = self.appName
140+
let connectionIdValue = self.connectionId
141+
let customHeadersValue = self.customHeaders
142+
lock.unlock()
143+
144+
var request = URLRequest(url: urlValue.appendingPathComponent("client/metrics"))
126145
request.httpMethod = "POST"
127146
request.httpBody = payload
128147
request.addValue("application/json", forHTTPHeaderField: "Accept")
129148
request.addValue("no-cache", forHTTPHeaderField: "Cache")
130149
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
131-
request.addValue(clientKey, forHTTPHeaderField: "Authorization")
132-
request.addValue(appName, forHTTPHeaderField: "unleash-appname")
133-
request.addValue(connectionId.uuidString, forHTTPHeaderField: "unleash-connection-id")
150+
request.addValue(clientKeyValue, forHTTPHeaderField: "Authorization")
151+
request.addValue(appNameValue, forHTTPHeaderField: "unleash-appname")
152+
request.addValue(connectionIdValue.uuidString, forHTTPHeaderField: "unleash-connection-id")
134153
request.setValue("unleash-client-swift:\(LibraryInfo.version)", forHTTPHeaderField: "unleash-sdk")
135-
if !self.customHeaders.isEmpty {
136-
for (key, value) in self.customHeaders {
154+
if !customHeadersValue.isEmpty {
155+
for (key, value) in customHeadersValue {
137156
request.setValue(value, forHTTPHeaderField: key)
138157
}
139158
}

Sources/UnleashProxyClientSwift/Poller/DictionaryStorageProvider.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,26 @@ import Foundation
22

33
public class DictionaryStorageProvider: StorageProvider {
44
private var storage: [String: Toggle] = [:]
5-
private let queue = DispatchQueue(label: "com.unleash.storage", attributes: .concurrent)
5+
private let lock = NSLock()
66

77
public init() {}
88

99
public func set(values: [String: Toggle]) {
10-
queue.async(flags: .barrier){
11-
self.storage = values
12-
}
10+
lock.lock()
11+
self.storage = values
12+
lock.unlock()
1313
}
1414

1515
public func value(key: String) -> Toggle? {
16-
queue.sync {
17-
return self.storage[key]
18-
}
16+
lock.lock()
17+
let result = self.storage[key]
18+
lock.unlock()
19+
return result
1920
}
2021

2122
public func clear() {
22-
queue.async(flags: .barrier) {
23-
self.storage = [:]
24-
}
23+
lock.lock()
24+
self.storage = [:]
25+
lock.unlock()
2526
}
2627
}

0 commit comments

Comments
 (0)