Skip to content

Commit bd9af4d

Browse files
Hongyan JiangGitHub Enterprise
authored andcommitted
fix duplicated beacons for some users (#96)
* add `deleteOldBeacons` feature to remove older beacons * add `maxBeaconResendTries` to configure retry times on beacon send failure
1 parent a09f23e commit bd9af4d

20 files changed

+259
-40
lines changed

Changelog.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# Changelog
22

3+
## 1.9.4
4+
- add `deleteOldBeacons` feature to remove older beacons
5+
- add `maxBeaconResendTries` to configure retry times on beacon send failure
6+
37
## 1.9.3
4-
- address dulpicated beacon issue for slow network
8+
- address duplicated beacon issue for slow network
59
- crash beacon improvement
610

711
## 1.9.2

InstanaAgent.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
1616
#
1717

1818
s.name = "InstanaAgent"
19-
s.version = "1.9.3"
19+
s.version = "1.9.4"
2020
s.summary = "Instana iOS agent."
2121

2222
# This description is used to generate tags and improve search results.

Sources/InstanaAgent/Beacons/BeaconFlusher.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,12 @@ class BeaconFlusher {
120120
disapatchGroup.notify(queue: queue) { [weak self] in
121121
guard let self = self else { return }
122122
self.urlTasks.removeAll()
123-
if self.shouldPerformRetry() {
124-
self.retry()
125-
} else {
126-
self.complete()
127-
}
123+
self.complete()
124+
// if self.shouldPerformRetry() {
125+
// self.retry()
126+
// } else {
127+
// self.complete()
128+
// }
128129
}
129130
didStartFlush?()
130131
}

Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon+Extensions.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ extension CoreBeacon {
2323

2424
func formattedKVPair(key: String, value: Any) -> String? {
2525
guard Mirror.isNotNil(value: value) else { return nil }
26+
if key == "rct" {
27+
// retry count is internal to agent
28+
return nil
29+
}
2630
if let dict = value as? [String: String] {
2731
return dict.asString(prefix: key)
2832
}

Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,21 @@ struct CoreBeacon: Codable {
534534
}
535535
return "\(iOSAgentVersion):\(hybridAgentId):\(hybridAgentVersion)"
536536
}
537+
538+
/**
539+
* retry count
540+
*
541+
* Example: 3
542+
*
543+
*/
544+
var rct: Int?
545+
mutating func increaseRetryCount() {
546+
if rct == nil {
547+
rct = 1
548+
} else {
549+
rct! += 1
550+
}
551+
}
537552
}
538553

539554
extension CoreBeacon: Hashable {

Sources/InstanaAgent/Beacons/Reporter.swift

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class Reporter {
4747
self.batterySafeForNetworking = batterySafeForNetworking
4848
self.rateLimiter = rateLimiter ?? ReporterRateLimiter(configs: session.configuration.reporterRateLimits)
4949
self.queue = queue ?? InstanaPersistableQueue<CoreBeacon>(identifier: "com.instana.ios.mainqueue", maxItems: session.configuration.maxQueueSize)
50+
purgeOldBeacons(session.configuration.deleteOldBeacons)
5051
networkUtility.connectionUpdateHandler = { [weak self] connectionType in
5152
guard let self = self else { return }
5253
self.updateNetworkConnection(connectionType)
@@ -60,6 +61,20 @@ public class Reporter {
6061
dispatchQueue.asyncAfter(deadline: .now() + session.configuration.preQueueUsageTime, execute: emptyPreQueueIfNeeded)
6162
}
6263

64+
func purgeOldBeacons(_ deleteOldBeacons: Bool, minutes: Double = 15.0) {
65+
if !deleteOldBeacons {
66+
return
67+
}
68+
let cutoffTime = Date().millisecondsSince1970 - Int64(minutes * 60.0 * 1000.0)
69+
var updatedBeacons: Set<CoreBeacon> = []
70+
for coreBeacon in queue.items {
71+
if let timestamp = Int64(coreBeacon.ti), timestamp > cutoffTime {
72+
updatedBeacons.insert(coreBeacon)
73+
}
74+
}
75+
queue.items = updatedBeacons
76+
}
77+
6378
func submit(_ beacon: Beacon, _ completion: ((Bool) -> Void)? = nil) {
6479
dispatchQueue.async { [weak self] in
6580
guard let self = self else { return }
@@ -196,13 +211,14 @@ public class Reporter {
196211
debounce = calcDebounceTime()
197212
beacons = queue.items
198213
}
214+
199215
let flusher = BeaconFlusher(reporter: self, items: beacons, debounce: debounce,
200216
config: session.configuration, queue: dispatchQueue,
201217
send: send) { [weak self] result in
202218
guard let self = self else { return }
203219
self.dispatchQueue.async { [weak self] in
204-
guard let self = self else { return }
205-
self.handle(flushResult: result, start, fromBeaconFlusherCompletion: true)
220+
let originalBeacons: Set<CoreBeacon>? = (result.sentBeacons.count < beacons.count) ? beacons : nil
221+
self?.handle(flushResult: result, start, fromBeaconFlusherCompletion: true, originalBeacons: originalBeacons)
206222
}
207223
}
208224
flusher.schedule()
@@ -237,7 +253,8 @@ public class Reporter {
237253

238254
private func handle(flushResult: BeaconFlusher.Result,
239255
_ start: Date = Date(),
240-
fromBeaconFlusherCompletion: Bool = false) {
256+
fromBeaconFlusherCompletion: Bool = false,
257+
originalBeacons: Set<CoreBeacon>? = nil) {
241258
let result: BeaconResult
242259
let errors = flushResult.errors
243260
let sent = flushResult.sentBeacons
@@ -253,13 +270,36 @@ public class Reporter {
253270
result = BeaconResult.failure(error)
254271
session.logger.add("Failed to send beacons in \(end * 1000) ms")
255272
}
256-
queue.remove(sent) { [weak self] _ in
257-
guard let self = self else { return }
258-
self.completionHandler.forEach { $0(result) }
273+
274+
if originalBeacons == nil {
275+
// All beacons were successfully sent. Remove them from local file.
276+
queue.remove(sent) { [weak self] _ in
277+
self?.completionHandler.forEach { $0(result) }
278+
}
279+
} else {
280+
// For beacons failed sending, increase retry count and save back to file.
281+
queue.items.subtract(sent)
282+
var updatedBeacons: Set<CoreBeacon> = []
283+
for var cBeacon in queue.items {
284+
if originalBeacons!.contains(cBeacon) {
285+
cBeacon.increaseRetryCount()
286+
if cBeacon.rct! < session.configuration.maxBeaconResendTries {
287+
updatedBeacons.insert(cBeacon)
288+
} // else: throw away the beacon
289+
} else {
290+
// new beacons, keep as is
291+
updatedBeacons.insert(cBeacon)
292+
}
293+
}
294+
queue.items = updatedBeacons
295+
queue.write { [weak self] _ in
296+
self?.completionHandler.forEach { $0(result) }
297+
}
259298
}
260299

261300
if fromBeaconFlusherCompletion {
262301
flusher = nil // mark this round flush done
302+
lastFlushStartTime = nil
263303
if inSlowModeBeforeFlush {
264304
// Another flush either resend 1 beacon (still in slow mode currently)
265305
// or flush remaining beacons (got out of slow send mode already)

Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class InstanaConfiguration {
7575
var anrThreshold: Double?
7676
var trustDeviceTiming: Bool?
7777
var enableW3CHeaders: Bool?
78+
var deleteOldBeacons: Bool
79+
var maxBeaconResendTries: Int = 3
7880
var enableAppStateDetection: Bool?
7981
var reporterSendDebounce: Instana.Types.Seconds
8082
var reporterSendLowBatteryDebounce: Instana.Types.Seconds
@@ -97,6 +99,8 @@ class InstanaConfiguration {
9799
perfConfig: InstanaPerformanceConfig? = nil,
98100
trustDeviceTiming: Bool? = nil,
99101
enableW3CHeaders: Bool? = nil,
102+
deleteOldBeacons: Bool,
103+
maxBeaconResendTries: Int,
100104
hybridAgentId: String?,
101105
hybridAgentVersion: String?) {
102106
self.reportingURL = reportingURL
@@ -134,9 +138,11 @@ class InstanaConfiguration {
134138
maxQueueSize = Defaults.maxQueueSize
135139
preQueueUsageTime = Defaults.preQueueUsageTime
136140
reporterRateLimits = Defaults.reporterRateLimits
137-
configRateLimit(rateLimits)
138141
self.trustDeviceTiming = trustDeviceTiming
139142
self.enableW3CHeaders = enableW3CHeaders
143+
self.deleteOldBeacons = deleteOldBeacons
144+
self.maxBeaconResendTries = maxBeaconResendTries
145+
configRateLimit(rateLimits)
140146
}
141147

142148
/**
@@ -179,6 +185,8 @@ class InstanaConfiguration {
179185
perfConfig: InstanaPerformanceConfig? = nil,
180186
trustDeviceTiming: Bool? = nil,
181187
enableW3CHeaders: Bool? = nil,
188+
deleteOldBeacons: Bool,
189+
maxBeaconResendTries: Int,
182190
hybridAgentId: String? = nil,
183191
hybridAgentVersion: String? = nil)
184192
-> InstanaConfiguration {
@@ -191,6 +199,8 @@ class InstanaConfiguration {
191199
perfConfig: perfConfig,
192200
trustDeviceTiming: trustDeviceTiming,
193201
enableW3CHeaders: enableW3CHeaders,
202+
deleteOldBeacons: deleteOldBeacons,
203+
maxBeaconResendTries: maxBeaconResendTries,
194204
hybridAgentId: hybridAgentId,
195205
hybridAgentVersion: hybridAgentVersion)
196206
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
struct VersionConfig {
2-
static let agentVersion = "1.9.3"
2+
static let agentVersion = "1.9.4"
33
}

Sources/InstanaAgent/Instana.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ import Foundation
104104
var perfConfig: InstanaPerformanceConfig?
105105
var trustDeviceTiming: Bool?
106106
var enableW3CHeaders: Bool?
107+
var deleteOldBeacons: Bool
108+
var maxBeaconResendTries: Int
107109

108110
if let options = options {
109111
httpCaptureConfig = options.httpCaptureConfig
@@ -136,6 +138,12 @@ import Foundation
136138
perfConfig = options.perfConfig
137139
trustDeviceTiming = options.trustDeviceTiming
138140
enableW3CHeaders = options.enableW3CHeaders
141+
deleteOldBeacons = options.deleteOldBeacons
142+
maxBeaconResendTries = options.maxBeaconResendTries
143+
} else {
144+
let defaultOptions = InstanaSetupOptions()
145+
deleteOldBeacons = defaultOptions.deleteOldBeacons
146+
maxBeaconResendTries = defaultOptions.maxBeaconResendTries
139147
}
140148

141149
var hybridAgentId: String?
@@ -155,6 +163,8 @@ import Foundation
155163
perfConfig: perfConfig,
156164
trustDeviceTiming: trustDeviceTiming,
157165
enableW3CHeaders: enableW3CHeaders,
166+
deleteOldBeacons: deleteOldBeacons,
167+
maxBeaconResendTries: maxBeaconResendTries,
158168
hybridAgentId: hybridAgentId,
159169
hybridAgentVersion: hybridAgentVersion)
160170
let session = InstanaSession(configuration: config, propertyHandler: InstanaPropertyHandler(),
@@ -179,9 +189,12 @@ import Foundation
179189
@available(*, deprecated, message: "Use method setup(key: String, reportingURL: URL, options: InstanaSetupOptions?)")
180190
@objc
181191
public static func setup(key: String, reportingURL: URL, enableCrashReporting: Bool = false) {
192+
let defaultOptions = InstanaSetupOptions()
182193
let config = InstanaConfiguration.default(key: key, reportingURL: reportingURL,
183194
httpCaptureConfig: .automatic,
184-
enableCrashReporting: enableCrashReporting)
195+
enableCrashReporting: enableCrashReporting,
196+
deleteOldBeacons: defaultOptions.deleteOldBeacons,
197+
maxBeaconResendTries: defaultOptions.maxBeaconResendTries)
185198
let session = InstanaSession(configuration: config, propertyHandler: InstanaPropertyHandler(), collectionEnabled: true)
186199
Instana.current = Instana(session: session)
187200
}
@@ -202,9 +215,12 @@ import Foundation
202215
httpCaptureConfig: HTTPCaptureConfig = .automatic,
203216
collectionEnabled: Bool = true,
204217
enableCrashReporting: Bool = false) {
218+
let defaultOptions = InstanaSetupOptions()
205219
let config = InstanaConfiguration.default(key: key, reportingURL: reportingURL,
206220
httpCaptureConfig: httpCaptureConfig,
207-
enableCrashReporting: enableCrashReporting)
221+
enableCrashReporting: enableCrashReporting,
222+
deleteOldBeacons: defaultOptions.deleteOldBeacons,
223+
maxBeaconResendTries: defaultOptions.maxBeaconResendTries)
208224
let session = InstanaSession(configuration: config, propertyHandler: InstanaPropertyHandler(), collectionEnabled: collectionEnabled)
209225
Instana.current = Instana(session: session)
210226
}

Sources/InstanaAgent/InstanaSetupOptions.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Foundation
1010
@objc public var enableCrashReporting: Bool = false
1111
@objc public var suspendReportingOnLowBattery: Bool = false
1212
@objc public var suspendReportingOnCellular: Bool = false
13+
@available(*, deprecated, message: "Do not configure.")
1314
@objc public var slowSendInterval: Instana.Types.Seconds = 0.0
1415
@objc public var usiRefreshTimeIntervalInHrs: Double = defaultUsiRefreshTimeIntervalInHrs
1516

@@ -37,6 +38,17 @@ import Foundation
3738
// ensuring compatibility with W3C standards for tracing.
3839
@objc public var enableW3CHeaders: Bool = false
3940

41+
/**
42+
* When enabled, this prevents beacons older than 15 minutes (saved while the device was offline) from being sent to the backend.
43+
*/
44+
45+
@objc public var deleteOldBeacons: Bool = false
46+
/**
47+
* Maximum beacon resend tries on sending failure
48+
* (note: On low battery or offline, beacon is not sent.)
49+
*/
50+
@objc public var maxBeaconResendTries: Int = 3
51+
4052
/// Instana custom configuration for setup.
4153
///
4254
/// - Parameters:
@@ -59,7 +71,9 @@ import Foundation
5971
rateLimits: RateLimits = .DEFAULT_LIMITS,
6072
perfConfig: InstanaPerformanceConfig? = nil,
6173
trustDeviceTiming: Bool = false,
62-
enableW3CHeaders: Bool = false) {
74+
enableW3CHeaders: Bool = false,
75+
deleteOldBeacons: Bool = false,
76+
maxBeaconResendTries: Int = 3) {
6377
self.httpCaptureConfig = httpCaptureConfig
6478
self.collectionEnabled = collectionEnabled
6579
self.enableCrashReporting = enableCrashReporting
@@ -75,6 +89,8 @@ import Foundation
7589
self.perfConfig = perfConfig
7690
self.trustDeviceTiming = trustDeviceTiming
7791
self.enableW3CHeaders = enableW3CHeaders
92+
self.deleteOldBeacons = deleteOldBeacons
93+
self.maxBeaconResendTries = maxBeaconResendTries
7894
super.init()
7995
}
8096

0 commit comments

Comments
 (0)