Skip to content

Commit ce163d0

Browse files
authored
feat(ats): add configurable timeouts for ODP segment fetch and event dispatch (#471)
1 parent 2898a18 commit ce163d0

14 files changed

+126
-23
lines changed

.github/workflows/source_clear_cron.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99

1010
jobs:
1111
source_clear:
12-
runs-on: macos-latest
12+
runs-on: macos-12
1313
steps:
1414
- uses: actions/checkout@v3
1515
- name: Source clear scan

.github/workflows/swift.yml

+6-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }}
3333

3434
lint:
35-
runs-on: macos-latest
35+
runs-on: macos-12
3636
steps:
3737
- uses: actions/checkout@v3
3838
- uses: maxim-lobanov/setup-xcode@v1
@@ -47,10 +47,12 @@ jobs:
4747
4848
unittests:
4949
if: "${{ github.event.inputs.PREP == '' && github.event.inputs.RELEASE == '' }}"
50-
uses: optimizely/swift-sdk/.github/workflows/unit_tests.yml@master
50+
# restore back to master after merging.
51+
#uses: optimizely/swift-sdk/.github/workflows/unit_tests.yml@master
52+
uses: optimizely/swift-sdk/.github/workflows/unit_tests.yml@jae/ats-timeout
5153

5254
prepare_for_release:
53-
runs-on: macos-latest
55+
runs-on: macos-12
5456
if: "${{ github.event.inputs.PREP == 'true' && github.event_name == 'workflow_dispatch' }}"
5557
steps:
5658
- uses: actions/checkout@v3
@@ -74,7 +76,7 @@ jobs:
7476

7577
release:
7678
if: "${{github.event.inputs.RELEASE == 'true' && github.event_name == 'workflow_dispatch' }}"
77-
runs-on: macos-latest
79+
runs-on: macos-12
7880
steps:
7981
- uses: actions/checkout@v3
8082
- uses: maxim-lobanov/setup-xcode@v1

.github/workflows/unit_tests.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ jobs:
5050
- uses: actions/checkout@v3
5151
- uses: maxim-lobanov/setup-xcode@v1
5252
with:
53-
xcode-version: 14.1.0
53+
# macos version and supported simulator_xcode_versions are all related to this xcode_version, so be careful when you upgrade this.
54+
xcode-version: 12.4
5455
- name: set SDK Branch if PR
5556
if: ${{ github.event_name == 'pull_request' }}
5657
run: |

Sources/ODP/OdpEventApiManager.swift

+16-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,15 @@ import Foundation
3131
*/
3232

3333
class OdpEventApiManager {
34-
34+
let resourceTimeoutInSecs: Int?
35+
36+
/// OdpEventApiManager init
37+
/// - Parameters:
38+
/// - timeout: timeout for segment fetch
39+
init(timeout: Int? = nil) {
40+
self.resourceTimeoutInSecs = timeout
41+
}
42+
3543
func sendOdpEvents(apiKey: String,
3644
apiHost: String,
3745
events: [OdpEvent],
@@ -99,8 +107,12 @@ class OdpEventApiManager {
99107
task.resume()
100108
}
101109

102-
func getSession() -> URLSession {
103-
return URLSession(configuration: .ephemeral)
110+
open func getSession() -> URLSession {
111+
let config = URLSessionConfiguration.ephemeral
112+
if let timeout = resourceTimeoutInSecs, timeout > 0 {
113+
config.timeoutIntervalForResource = TimeInterval(timeout)
114+
}
115+
return URLSession(configuration: config)
104116
}
105-
117+
106118
}

Sources/ODP/OdpEventManager.swift

+11-2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,18 @@ class OdpEventManager {
2828

2929
let logger = OPTLoggerFactory.getLogger()
3030

31-
init(sdkKey: String, odpConfig: OdpConfig? = nil, apiManager: OdpEventApiManager? = nil) {
31+
/// OdpEventManager init
32+
/// - Parameters:
33+
/// - sdkKey: datafile sdkKey
34+
/// - odpConfig: ODP config (apiKey, apiHost, ...)
35+
/// - apiManager: OdpEventApiManager
36+
/// - resourceTimeoutInSecs: timeout for event dispatch
37+
init(sdkKey: String,
38+
odpConfig: OdpConfig? = nil,
39+
apiManager: OdpEventApiManager? = nil,
40+
resourceTimeoutInSecs: Int? = nil) {
3241
self.odpConfig = odpConfig ?? OdpConfig()
33-
self.apiMgr = apiManager ?? OdpEventApiManager()
42+
self.apiMgr = apiManager ?? OdpEventApiManager(timeout: resourceTimeoutInSecs)
3443

3544
self.queueLock = DispatchQueue(label: "event")
3645

Sources/ODP/OdpManager.swift

+17-2
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,22 @@ class OdpManager {
3030
return vuidManager.vuid
3131
}
3232

33+
/// OdpManager init
34+
/// - Parameters:
35+
/// - sdkKey: datafile sdkKey
36+
/// - disable: disable ODP
37+
/// - cacheSize: segment cache size
38+
/// - cacheTimeoutInSecs: segment cache timeout
39+
/// - timeoutForSegmentFetchInSecs: timeout for segment fetch
40+
/// - timeoutForEventDispatchInSecs: timeout for event dispatch
41+
/// - segmentManager: ODPSegmentManager
42+
/// - eventManager: ODPEventManager
3343
init(sdkKey: String,
3444
disable: Bool,
3545
cacheSize: Int,
3646
cacheTimeoutInSecs: Int,
47+
timeoutForSegmentFetchInSecs: Int? = nil,
48+
timeoutForEventDispatchInSecs: Int? = nil,
3749
segmentManager: OdpSegmentManager? = nil,
3850
eventManager: OdpEventManager? = nil) {
3951

@@ -53,14 +65,17 @@ class OdpManager {
5365
} else {
5466
self.segmentManager = OdpSegmentManager(cacheSize: cacheSize,
5567
cacheTimeoutInSecs: cacheTimeoutInSecs,
56-
odpConfig: odpConfig)
68+
odpConfig: odpConfig,
69+
resourceTimeoutInSecs: timeoutForSegmentFetchInSecs)
5770
}
5871

5972
if let eventManager = eventManager {
6073
eventManager.odpConfig = odpConfig
6174
self.eventManager = eventManager
6275
} else {
63-
self.eventManager = OdpEventManager(sdkKey: sdkKey, odpConfig: odpConfig)
76+
self.eventManager = OdpEventManager(sdkKey: sdkKey,
77+
odpConfig: odpConfig,
78+
resourceTimeoutInSecs: timeoutForEventDispatchInSecs)
6479
}
6580

6681
self.eventManager.registerVUID(vuid: self.vuidManager.vuid)

Sources/ODP/OdpSegmentApiManager.swift

+14-2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ import Foundation
9797

9898
class OdpSegmentApiManager {
9999
let logger = OPTLoggerFactory.getLogger()
100+
let resourceTimeoutInSecs: Int?
101+
102+
/// OdpSegmentApiManager init
103+
/// - Parameters:
104+
/// - timeout: timeout for segment fetch
105+
init(timeout: Int? = nil) {
106+
self.resourceTimeoutInSecs = timeout
107+
}
100108

101109
func fetchSegments(apiKey: String,
102110
apiHost: String,
@@ -175,8 +183,12 @@ class OdpSegmentApiManager {
175183
task.resume()
176184
}
177185

178-
func getSession() -> URLSession {
179-
return URLSession(configuration: .ephemeral)
186+
open func getSession() -> URLSession {
187+
let config = URLSessionConfiguration.ephemeral
188+
if let timeout = resourceTimeoutInSecs, timeout > 0 {
189+
config.timeoutIntervalForResource = TimeInterval(timeout)
190+
}
191+
return URLSession(configuration: config)
180192
}
181193

182194
func makeQuery(userKey: String, userValue: String, segmentsToCheck: [String]) -> [String: Any] {

Sources/ODP/OdpSegmentManager.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,20 @@ class OdpSegmentManager {
2323

2424
let logger = OPTLoggerFactory.getLogger()
2525

26+
/// OdpSegmentManager init
27+
/// - Parameters:
28+
/// - cacheSize: segment cache size
29+
/// - cacheTimeoutInSecs: segment cache timeout
30+
/// - odpConfig: ODP config (apiKey, apiHost, ...)
31+
/// - apiManager: OdpSegmentApiManager
32+
/// - resourceTimeoutInSecs: timeout for segment fetch
2633
init(cacheSize: Int,
2734
cacheTimeoutInSecs: Int,
2835
odpConfig: OdpConfig? = nil,
29-
apiManager: OdpSegmentApiManager? = nil) {
36+
apiManager: OdpSegmentApiManager? = nil,
37+
resourceTimeoutInSecs: Int? = nil) {
3038
self.odpConfig = odpConfig ?? OdpConfig()
31-
self.apiMgr = apiManager ?? OdpSegmentApiManager()
39+
self.apiMgr = apiManager ?? OdpSegmentApiManager(timeout: resourceTimeoutInSecs)
3240

3341
self.segmentsCache = LruCache<String, [String]>(size: cacheSize,
3442
timeoutInSecs: cacheTimeoutInSecs)

Sources/ODP/OptimizelySdkSettings.swift

+10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public struct OptimizelySdkSettings {
2121
let segmentsCacheSize: Int
2222
/// The timeout in seconds of audience segments cache - timeout is disabled if this is set to zero.
2323
let segmentsCacheTimeoutInSecs: Int
24+
/// The timeout in seconds of odp segment fetch - OS default timeout will be used if this is set to zero.
25+
let timeoutForSegmentFetchInSecs: Int
26+
/// The timeout in seconds of odp event dispatch - OS default timeout will be used if this is set to zero.
27+
let timeoutForOdpEventInSecs: Int
2428
/// ODP features are disabled if this is set to true.
2529
let disableOdp: Bool
2630

@@ -29,12 +33,18 @@ public struct OptimizelySdkSettings {
2933
/// - Parameters:
3034
/// - segmentsCacheSize: The maximum size of audience segments cache (optional. default = 100). Set to zero to disable caching.
3135
/// - segmentsCacheTimeoutInSecs: The timeout in seconds of audience segments cache (optional. default = 600). Set to zero to disable timeout.
36+
/// - timeoutForSegmentFetchInSecs: The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero.
37+
/// - timeoutForOdpEventInSecs: The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero.
3238
/// - disableOdp: Set this flag to true (default = false) to disable ODP features
3339
public init(segmentsCacheSize: Int = 100,
3440
segmentsCacheTimeoutInSecs: Int = 600,
41+
timeoutForSegmentFetchInSecs: Int = 10,
42+
timeoutForOdpEventInSecs: Int = 10,
3543
disableOdp: Bool = false) {
3644
self.segmentsCacheSize = segmentsCacheSize
3745
self.segmentsCacheTimeoutInSecs = segmentsCacheTimeoutInSecs
46+
self.timeoutForSegmentFetchInSecs = timeoutForSegmentFetchInSecs
47+
self.timeoutForOdpEventInSecs = timeoutForOdpEventInSecs
3848
self.disableOdp = disableOdp
3949
}
4050
}

Sources/Optimizely/OptimizelyClient.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ open class OptimizelyClient: NSObject {
9191
self.odpManager = OdpManager(sdkKey: sdkKey,
9292
disable: sdkSettings.disableOdp,
9393
cacheSize: sdkSettings.segmentsCacheSize,
94-
cacheTimeoutInSecs: sdkSettings.segmentsCacheTimeoutInSecs)
94+
cacheTimeoutInSecs: sdkSettings.segmentsCacheTimeoutInSecs,
95+
timeoutForSegmentFetchInSecs: sdkSettings.timeoutForSegmentFetchInSecs,
96+
timeoutForEventDispatchInSecs: sdkSettings.timeoutForOdpEventInSecs)
9597

9698
super.init()
9799

Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift

+12-3
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,29 @@ class OptimizelyClientTests_ODP: XCTestCase {
3030

3131
// MARK: - ODP configuration
3232

33-
func testConfigurableSettings_default() {
33+
func testSdkSettings_default() {
3434
let optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey)
3535

3636
XCTAssertEqual(100, optimizely.odpManager.segmentManager?.segmentsCache.maxSize)
3737
XCTAssertEqual(600, optimizely.odpManager.segmentManager?.segmentsCache.timeoutInSecs)
38+
XCTAssertEqual(10, optimizely.odpManager.segmentManager?.apiMgr.resourceTimeoutInSecs)
39+
XCTAssertEqual(10, optimizely.odpManager.eventManager?.apiMgr.resourceTimeoutInSecs)
3840
XCTAssertEqual(true, optimizely.odpManager.enabled)
3941
}
4042

41-
func testConfigurableSettings_custom() {
42-
var sdkSettings = OptimizelySdkSettings(segmentsCacheSize: 12, segmentsCacheTimeoutInSecs: 345)
43+
func testSdkSettings_custom() {
44+
var sdkSettings = OptimizelySdkSettings(segmentsCacheSize: 12,
45+
segmentsCacheTimeoutInSecs: 345)
4346
var optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, settings: sdkSettings)
4447
XCTAssertEqual(12, optimizely.odpManager.segmentManager?.segmentsCache.maxSize)
4548
XCTAssertEqual(345, optimizely.odpManager.segmentManager?.segmentsCache.timeoutInSecs)
4649

50+
sdkSettings = OptimizelySdkSettings(timeoutForSegmentFetchInSecs: 34,
51+
timeoutForOdpEventInSecs: 45)
52+
optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, settings: sdkSettings)
53+
XCTAssertEqual(34, optimizely.odpManager.segmentManager?.apiMgr.resourceTimeoutInSecs)
54+
XCTAssertEqual(45, optimizely.odpManager.eventManager?.apiMgr.resourceTimeoutInSecs)
55+
4756
sdkSettings = OptimizelySdkSettings(disableOdp: true)
4857
optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, settings: sdkSettings)
4958
XCTAssertEqual(false, optimizely.odpManager.enabled)

Tests/OptimizelyTests-Common/OdpEventApiManagerTests.swift

+12
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ class OdpEventApiManagerTests: XCTestCase {
138138
XCTAssert(jsonDispatched.contains("\"key3\":null"))
139139
}
140140

141+
// MARK: - timeout
142+
143+
func testTimeout() {
144+
let api = OdpEventApiManager(timeout: 3)
145+
XCTAssertEqual(3, api.getSession().configuration.timeoutIntervalForResource)
146+
}
147+
148+
func testTimeout_useOSDefaultIfTimeoutIsNotProvided() {
149+
let api = OdpEventApiManager()
150+
XCTAssertEqual(604800, api.getSession().configuration.timeoutIntervalForResource)
151+
}
152+
141153
}
142154

143155
// MARK: - Tests with live ODP server

Tests/OptimizelyTests-Common/OdpManagerTests.swift

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class OdpManagerTests: XCTestCase {
5959
disable: true,
6060
cacheSize: cacheSize,
6161
cacheTimeoutInSecs: cacheTimeout)
62-
6362
XCTAssertTrue(manager.vuid.starts(with: "vuid_"), "vuid should be serverved even when ODP is disabled.")
6463

6564
let sem = DispatchSemaphore(value: 0)

Tests/OptimizelyTests-Common/OdpSegmentApiManagerTests.swift

+12
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,18 @@ class OdpSegmentApiManagerTests: XCTestCase {
209209
XCTAssertEqual(.success, sem.wait(timeout: .now() + .seconds(1)))
210210
}
211211

212+
// MARK: - timeout
213+
214+
func testTimeout() {
215+
let api = OdpSegmentApiManager(timeout: 3)
216+
XCTAssertEqual(3, api.getSession().configuration.timeoutIntervalForResource)
217+
}
218+
219+
func testTimeout_useOSDefaultIfTimeoutIsNotProvided() {
220+
let api = OdpSegmentApiManager()
221+
XCTAssertEqual(604800, api.getSession().configuration.timeoutIntervalForResource)
222+
}
223+
212224
// MARK: - Others
213225

214226
func testMakeQuery() {

0 commit comments

Comments
 (0)