Skip to content

feat: urlsessionconfiguration change requestTimeout#6

Open
polatolu wants to merge 4 commits intomainfrom
feature/urlsessionconfiguration-expose-properties
Open

feat: urlsessionconfiguration change requestTimeout#6
polatolu wants to merge 4 commits intomainfrom
feature/urlsessionconfiguration-expose-properties

Conversation

@polatolu
Copy link
Copy Markdown
Collaborator

@polatolu polatolu commented Oct 10, 2025

Description

Addresses a requirement for changing the requestTimeout parameter in URLSessionConfiguration

Regression Test Recommendations

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • ~ 🛠️ Bug fix (non-breaking change which fixes an issue) ~
  • ~ ❌ Breaking change (fix or feature that would cause existing functionality to change) ~
  • ~ 🧹 Code refactor ~
  • ~ ✅ Build configuration change ~
  • ~ 📝 Documentation ~
  • 🗑️ Chore

Unit Test Results

Screenshot 2025-10-10 at 12 19 09 Screenshot 2025-10-10 at 12 18 57

Summary by CodeRabbit

  • New Features

    • Added NetworkConfig to customize SDK networking (request timeouts, connectivity, cellular access, connection limits, pipelining/cookies, custom headers). Sessions for API and streaming are recreated when settings change.
    • Added a runnable example demonstrating timeout scenarios and network configuration usage.
  • Documentation

    • New NetworkConfig guide with usage examples, migration notes, thread-safety guidance.
  • Tests

    • New unit and integration tests covering defaults, customization, thread-safety, session recreation, and performance.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds a new public NetworkConfig API exposed on Flagsmith.shared, updates APIManager and SSEManager to recreate URLSession instances from NetworkConfig, and includes example usage, documentation, and unit/integration tests validating propagation and timeout behavior.

Changes

Cohort / File(s) Summary
Public API: NetworkConfig & Flagsmith exposure
FlagsmithClient/Classes/Flagsmith.swift
Adds public NetworkConfig type and a thread-safe Flagsmith.shared.networkConfig property (backed by a private _networkConfig). Default requestTimeout = 60.0.
API manager: session lifecycle & configuration
FlagsmithClient/Classes/Internal/APIManager.swift
Makes session internally settable with guarded getter/setter; adds createURLSessionConfiguration() overloads; initializes session from configuration; recreates and swaps URLSession using current NetworkConfig for requests.
SSE manager: session lifecycle & configuration
FlagsmithClient/Classes/Internal/SSEManager.swift
Makes session internally settable; adds createURLSessionConfiguration() overloads; initializes session from configuration; recreates and swaps URLSession on start, binds tasks to new session, and clears tasks on stop.
Examples & docs
Example/NetworkConfigExample.swift, NETWORK_CONFIG_README.md
Adds example class demonstrating configuring Flagsmith.shared.networkConfig (timeouts, headers) and README documenting NetworkConfig options, thread-safety, migration notes, and usage.
Tests: unit & integration
FlagsmithClient/Tests/NetworkConfigTests.swift, FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift
Adds tests for default/custom values, thread-safety, equality, session configuration propagation to APIManager/SSEManager, session recreation on config change, and performance of repeated updates.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App as App
  participant FS as Flagsmith.shared
  participant API as APIManager
  participant SSE as SSEManager
  participant URLS as URLSession

  rect rgba(235,245,255,0.6)
    App->>FS: set networkConfig (e.g., requestTimeout)
    Note right of FS: thread-safe store of NetworkConfig
  end

  par REST request
    App->>API: performRequest(...)
    API->>API: createURLSessionConfiguration(from: FS.networkConfig)
    API->>URLS: create URLSession(configuration)
    API->>URLS: dataTask(with: request)
    URLS-->>API: response / error
    API-->>App: result
  and SSE stream
    App->>SSE: start(...)
    SSE->>SSE: createURLSessionConfiguration(from: FS.networkConfig)
    SSE->>URLS: create URLSession(configuration)
    SSE->>URLS: dataTask(with: stream URL)
    URLS-->>SSE: events / error
    SSE-->>App: callbacks
  end

  Note over API,SSE: Sessions are recreated to reflect current NetworkConfig before use
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nudge the timeout, small and neat,
Sessions hop up on nimble feet.
Headers twitch and streams align,
Flags arrive—on pace, on time.
A rabbit’s network, set and bright. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title specifies a new feature enabling changes to the requestTimeout on URLSessionConfiguration, which aligns directly with the primary additions in the PR to expose and configure a network timeout parameter.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/urlsessionconfiguration-expose-properties

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (14)
FlagsmithClient/Tests/NetworkConfigTests.swift (3)

45-49: Fix lint: short identifier 'i'

Rename loop variable to satisfy swiftlint.

-        for i in 0..<10 {
+        for idx in 0..<10 {
             DispatchQueue.global().async {
                 // Test that we can safely set values concurrently
-                flagsmith.networkConfig.requestTimeout = Double(i)
+                flagsmith.networkConfig.requestTimeout = Double(idx)
                 expectation.fulfill()
             }
         }

1-1: Remove leading blank line (swiftlint: leading_whitespace)

Top-of-file leading whitespace violates lint rules.

- 
+// (no leading blank line)

80-94: Equality test is too narrow if more fields are added

This compares only requestTimeout. If NetworkConfig gains more properties (as docs/example show), consider Equatable on NetworkConfig to compare all fields, or adjust the test name to reflect single-field comparison.

FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift (5)

1-1: Remove leading blank line (swiftlint: leading_whitespace)

-
+// (no leading blank line)

45-50: Replace unused closure parameter with underscore (swiftlint)

-        apiManager.request(.getFlags) { result in
+        apiManager.request(.getFlags) { _ in
             // Check the session configuration inside the completion handler
             let sessionConfig = apiManager.session.configuration
             XCTAssertEqual(sessionConfig.timeoutIntervalForRequest, 1.0, "Request timeout should be applied")
             expectation.fulfill()
         }

68-70: Replace unused closure parameter with underscore (swiftlint)

-        sseManager.start { result in
+        sseManager.start { _ in
             // This will be called immediately when start() is called
         }

89-91: Replace unused closure parameters with underscore (swiftlint)

-        apiManager.request(.getFlags) { result in
+        apiManager.request(.getFlags) { _ in
             initialExpectation.fulfill()
         }
...
-        apiManager.request(.getFlags) { result in
+        apiManager.request(.getFlags) { _ in
             expectation.fulfill()
         }

Also applies to: 103-105


120-122: Fix lint: short identifier 'i' in performance loop

-            for i in 0..<100 {
-                flagsmith.networkConfig.requestTimeout = Double(i)
+            for iteration in 0..<100 {
+                flagsmith.networkConfig.requestTimeout = Double(iteration)
             }
Example/NetworkConfigExample.swift (2)

45-55: Replace unused closure parameter with underscore (swiftlint)

-        flagsmith.getFeatureFlags { result in
+        flagsmith.getFeatureFlags { _ in
             // Handle result...
         }

(Apply in all indicated closures)

Also applies to: 95-98, 110-113, 125-128


1-1: Remove leading blank line (swiftlint: leading_whitespace)

-
+// (no leading blank line)
FlagsmithClient/Classes/Internal/SSEManager.swift (2)

189-196: Consider invalidating the previous session before swap

Repeatedly recreating sessions without invalidating the old one can accumulate resources during reconnects.

-        // Update session using the property setter to ensure thread-safe access
-        self.session = newSession
+        // Invalidate previous session before swapping to avoid resource buildup
+        let oldSession = self.session
+        self.session = newSession
+        oldSession.invalidateAndCancel()

Make sure no tasks are still needed on the old session when invalidating.


202-205: Nil out dataTask on stop

Minor cleanup to release references.

 func stop() {
-        dataTask?.cancel()
-        completionHandler = nil
+        dataTask?.cancel()
+        dataTask = nil
+        completionHandler = nil
 }
FlagsmithClient/Classes/Internal/APIManager.swift (2)

188-200: Recreating a URLSession per request is costly

Creating a new session on every request can add overhead and increase connection churn. Prefer reusing a session and only recreating when a config “generation” changes.

Consider tracking a configVersion on Flagsmith/NetworkConfig and only rebuilding when it changes, or cache a session keyed by effective NetworkConfig+CacheConfig.

I can propose a lightweight “generation” mechanism if desired.


188-200: Optionally invalidate previous session before swap

To avoid resource buildup during rapid requests:

-            let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main)
-            
-            // Update session using the property setter to ensure thread-safe access
-            self.session = newSession
+            let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main)
+            let oldSession = self.session
+            self.session = newSession
+            // Invalidate the old session if no tasks are expected to keep running
+            oldSession.invalidateAndCancel()

Ensure no active tasks depend on the old session when invalidating.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 702d5a5 and 3e312d1.

📒 Files selected for processing (7)
  • Example/NetworkConfigExample.swift (1 hunks)
  • FlagsmithClient/Classes/Flagsmith.swift (2 hunks)
  • FlagsmithClient/Classes/Internal/APIManager.swift (3 hunks)
  • FlagsmithClient/Classes/Internal/SSEManager.swift (3 hunks)
  • FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift (1 hunks)
  • FlagsmithClient/Tests/NetworkConfigTests.swift (1 hunks)
  • NETWORK_CONFIG_README.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
FlagsmithClient/Classes/Internal/SSEManager.swift (1)
FlagsmithClient/Classes/Internal/APIManager.swift (5)
  • createURLSessionConfiguration (78-95)
  • createURLSessionConfiguration (98-108)
  • request (163-203)
  • request (210-219)
  • request (227-249)
Example/NetworkConfigExample.swift (1)
FlagsmithClient/Classes/Flagsmith.swift (1)
  • getFeatureFlags (139-184)
FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift (2)
FlagsmithClient/Classes/Internal/APIManager.swift (3)
  • request (163-203)
  • request (210-219)
  • request (227-249)
FlagsmithClient/Classes/Internal/SSEManager.swift (2)
  • start (173-200)
  • stop (202-205)
FlagsmithClient/Classes/Internal/APIManager.swift (1)
FlagsmithClient/Classes/Internal/SSEManager.swift (2)
  • createURLSessionConfiguration (83-97)
  • createURLSessionConfiguration (100-107)
🪛 GitHub Check: swift-lint
Example/NetworkConfigExample.swift

[warning] 125-125:
Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)


[warning] 110-110:
Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)


[warning] 95-95:
Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)


[warning] 1-1:
Leading Whitespace Violation: Files should not contain leading whitespace (leading_whitespace)

FlagsmithClient/Tests/NetworkConfigTests.swift

[failure] 45-45:
Identifier Name Violation: Variable name 'i' should be between 3 and 40 characters long (identifier_name)

FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift

[warning] 103-103:
Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)


[warning] 89-89:
Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)


[warning] 68-68:
Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)


[warning] 45-45:
Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)


[warning] 1-1:
Leading Whitespace Violation: Files should not contain leading whitespace (leading_whitespace)


[failure] 120-120:
Identifier Name Violation: Variable name 'i' should be between 3 and 40 characters long (identifier_name)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: macos-latest

Comment on lines +114 to +125
/// Configuration class for network settings
private var _networkConfig: NetworkConfig = .init()
public var networkConfig: NetworkConfig {
get {
apiManager.propertiesSerialAccessQueue.sync { _networkConfig }
}
set {
apiManager.propertiesSerialAccessQueue.sync {
_networkConfig = newValue
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Exposing a mutable reference is not thread-safe

networkConfig returns a shared reference to a class with unsynchronized vars. Concurrent mutations (as tested) create data races. Make NetworkConfig internally synchronized or expose a value-type snapshot API.

Follow the NetworkConfig refactor below to make properties thread-safe. This keeps the external API intact while preventing races.

🤖 Prompt for AI Agents
In FlagsmithClient/Classes/Flagsmith.swift around lines 114–125, returning a
shared mutable NetworkConfig instance causes data races because its properties
are unsynchronized; replace this by exposing a thread-safe snapshot or by making
NetworkConfig itself thread-safe: either (A) change networkConfig to return a
value-type snapshot (e.g. a struct copy or a deep-cloned immutable copy) from
inside apiManager.propertiesSerialAccessQueue.sync, or (B) make NetworkConfig
encapsulate its mutable state with its own serial queue and provide thread-safe
getters/setters for each property, then keep the getter/setter on Flagsmith
simply delegating through the queue. Ensure all external mutations occur via the
synchronized API (or by setting a whole new immutable value) so no shared,
unsynchronized class instance is exposed.

Comment on lines +425 to +429
/// Configuration class for network settings
public final class NetworkConfig {
/// The timeout interval for URL requests, in seconds. Default is 60.0 seconds.
public var requestTimeout: TimeInterval = 60.0
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Implement full NetworkConfig (thread-safe) to match docs/example

Add missing properties and make them thread-safe. Also mark as Sendable if accessed across concurrency domains.

-public final class NetworkConfig {
-    /// The timeout interval for URL requests, in seconds. Default is 60.0 seconds.
-    public var requestTimeout: TimeInterval = 60.0
-}
+public final class NetworkConfig: @unchecked Sendable {
+    private let queue = DispatchQueue(label: "flagsmith.networkConfig.queue", attributes: .concurrent)
+
+    // Backing storage
+    private var _requestTimeout: TimeInterval = 60.0
+    private var _resourceTimeout: TimeInterval = 604_800.0 // 7 days
+    private var _waitsForConnectivity: Bool = true
+    private var _allowsCellularAccess: Bool = true
+    private var _httpMaximumConnectionsPerHost: Int = 6
+    private var _httpAdditionalHeaders: [String: String] = [:]
+    private var _httpShouldUsePipelining: Bool = true
+    private var _httpShouldSetCookies: Bool = true
+
+    public init() {}
+
+    /// Timeout for individual requests (seconds). Default 60.0
+    public var requestTimeout: TimeInterval {
+        get { queue.sync { _requestTimeout } }
+        set { queue.async(flags: .barrier) { self._requestTimeout = newValue } }
+    }
+    /// Total timeout for a resource request (seconds). Default 7 days
+    public var resourceTimeout: TimeInterval {
+        get { queue.sync { _resourceTimeout } }
+        set { queue.async(flags: .barrier) { self._resourceTimeout = newValue } }
+    }
+    public var waitsForConnectivity: Bool {
+        get { queue.sync { _waitsForConnectivity } }
+        set { queue.async(flags: .barrier) { self._waitsForConnectivity = newValue } }
+    }
+    public var allowsCellularAccess: Bool {
+        get { queue.sync { _allowsCellularAccess } }
+        set { queue.async(flags: .barrier) { self._allowsCellularAccess = newValue } }
+    }
+    public var httpMaximumConnectionsPerHost: Int {
+        get { queue.sync { _httpMaximumConnectionsPerHost } }
+        set { queue.async(flags: .barrier) { self._httpMaximumConnectionsPerHost = newValue } }
+    }
+    public var httpAdditionalHeaders: [String: String] {
+        get { queue.sync { _httpAdditionalHeaders } }
+        set { queue.async(flags: .barrier) { self._httpAdditionalHeaders = newValue } }
+    }
+    public var httpShouldUsePipelining: Bool {
+        get { queue.sync { _httpShouldUsePipelining } }
+        set { queue.async(flags: .barrier) { self._httpShouldUsePipelining = newValue } }
+    }
+    public var httpShouldSetCookies: Bool {
+        get { queue.sync { _httpShouldSetCookies } }
+        set { queue.async(flags: .barrier) { self._httpShouldSetCookies = newValue } }
+    }
+}

Additionally, ensure APIManager/SSEManager apply all of these properties when creating configurations (see file-specific suggestions).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Configuration class for network settings
public final class NetworkConfig {
/// The timeout interval for URL requests, in seconds. Default is 60.0 seconds.
public var requestTimeout: TimeInterval = 60.0
}
/// Configuration class for network settings
public final class NetworkConfig: @unchecked Sendable {
private let queue = DispatchQueue(label: "flagsmith.networkConfig.queue", attributes: .concurrent)
// Backing storage
private var _requestTimeout: TimeInterval = 60.0
private var _resourceTimeout: TimeInterval = 604_800.0 // 7 days
private var _waitsForConnectivity: Bool = true
private var _allowsCellularAccess: Bool = true
private var _httpMaximumConnectionsPerHost: Int = 6
private var _httpAdditionalHeaders: [String: String] = [:]
private var _httpShouldUsePipelining: Bool = true
private var _httpShouldSetCookies: Bool = true
public init() {}
/// Timeout for individual requests (seconds). Default 60.0
public var requestTimeout: TimeInterval {
get { queue.sync { _requestTimeout } }
set { queue.async(flags: .barrier) { self._requestTimeout = newValue } }
}
/// Total timeout for a resource request (seconds). Default 7 days
public var resourceTimeout: TimeInterval {
get { queue.sync { _resourceTimeout } }
set { queue.async(flags: .barrier) { self._resourceTimeout = newValue } }
}
public var waitsForConnectivity: Bool {
get { queue.sync { _waitsForConnectivity } }
set { queue.async(flags: .barrier) { self._waitsForConnectivity = newValue } }
}
public var allowsCellularAccess: Bool {
get { queue.sync { _allowsCellularAccess } }
set { queue.async(flags: .barrier) { self._allowsCellularAccess = newValue } }
}
public var httpMaximumConnectionsPerHost: Int {
get { queue.sync { _httpMaximumConnectionsPerHost } }
set { queue.async(flags: .barrier) { self._httpMaximumConnectionsPerHost = newValue } }
}
public var httpAdditionalHeaders: [String: String] {
get { queue.sync { _httpAdditionalHeaders } }
set { queue.async(flags: .barrier) { self._httpAdditionalHeaders = newValue } }
}
public var httpShouldUsePipelining: Bool {
get { queue.sync { _httpShouldUsePipelining } }
set { queue.async(flags: .barrier) { self._httpShouldUsePipelining = newValue } }
}
public var httpShouldSetCookies: Bool {
get { queue.sync { _httpShouldSetCookies } }
set { queue.async(flags: .barrier) { self._httpShouldSetCookies = newValue } }
}
}

Comment on lines +97 to +108
/// Creates a URLSessionConfiguration with specific network and cache settings
private func createURLSessionConfiguration(networkConfig: NetworkConfig, cacheConfig: CacheConfig) -> URLSessionConfiguration {
let configuration = URLSessionConfiguration.default

// Apply network configuration
configuration.timeoutIntervalForRequest = networkConfig.requestTimeout

// Apply cache configuration
configuration.urlCache = cacheConfig.cache

return configuration
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Apply full NetworkConfig and CacheConfig when creating session config

Only requestTimeout/urlCache are applied. Propagate all documented settings for consistency.

-    private func createURLSessionConfiguration(networkConfig: NetworkConfig, cacheConfig: CacheConfig) -> URLSessionConfiguration {
+    private func createURLSessionConfiguration(networkConfig: NetworkConfig, cacheConfig: CacheConfig) -> URLSessionConfiguration {
         let configuration = URLSessionConfiguration.default
         
         // Apply network configuration
-        configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
+        configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
+        configuration.timeoutIntervalForResource = networkConfig.resourceTimeout
+        configuration.waitsForConnectivity = networkConfig.waitsForConnectivity
+        configuration.allowsCellularAccess = networkConfig.allowsCellularAccess
+        configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost
+        configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders as [AnyHashable: Any]
+        configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining
+        configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies
         
         // Apply cache configuration
         configuration.urlCache = cacheConfig.cache
         
         return configuration
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Creates a URLSessionConfiguration with specific network and cache settings
private func createURLSessionConfiguration(networkConfig: NetworkConfig, cacheConfig: CacheConfig) -> URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
// Apply network configuration
configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
// Apply cache configuration
configuration.urlCache = cacheConfig.cache
return configuration
}
/// Creates a URLSessionConfiguration with specific network and cache settings
private func createURLSessionConfiguration(networkConfig: NetworkConfig, cacheConfig: CacheConfig) -> URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
// Apply network configuration
configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
configuration.timeoutIntervalForResource = networkConfig.resourceTimeout
configuration.waitsForConnectivity = networkConfig.waitsForConnectivity
configuration.allowsCellularAccess = networkConfig.allowsCellularAccess
configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost
configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders as [AnyHashable: Any]
configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining
configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies
// Apply cache configuration
configuration.urlCache = cacheConfig.cache
return configuration
}
🤖 Prompt for AI Agents
In FlagsmithClient/Classes/Internal/APIManager.swift around lines 97 to 108, the
URLSessionConfiguration only applies requestTimeout and urlCache; update the
method to propagate all relevant NetworkConfig and CacheConfig fields: set
timeoutIntervalForRequest and timeoutIntervalForResource, waitsForConnectivity,
allowsCellularAccess, httpMaximumConnectionsPerHost, networkServiceType,
requestCachePolicy and httpAdditionalHeaders from NetworkConfig as available,
and set configuration.urlCache plus configuration.requestCachePolicy (and any
cache-specific timeouts/limits) from CacheConfig; ensure any optional values are
conditionally applied and preserve existing defaults when fields are nil.

Comment on lines +99 to +107
/// Creates a URLSessionConfiguration with specific network settings
private func createURLSessionConfiguration(networkConfig: NetworkConfig) -> URLSessionConfiguration {
let configuration = URLSessionConfiguration.default

// Apply network configuration
configuration.timeoutIntervalForRequest = networkConfig.requestTimeout

return configuration
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Apply full NetworkConfig when creating session config

Currently only requestTimeout is propagated. Apply the rest for consistency with docs/example.

-    private func createURLSessionConfiguration(networkConfig: NetworkConfig) -> URLSessionConfiguration {
+    private func createURLSessionConfiguration(networkConfig: NetworkConfig) -> URLSessionConfiguration {
         let configuration = URLSessionConfiguration.default
         
         // Apply network configuration
-        configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
+        configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
+        configuration.timeoutIntervalForResource = networkConfig.resourceTimeout
+        configuration.waitsForConnectivity = networkConfig.waitsForConnectivity
+        configuration.allowsCellularAccess = networkConfig.allowsCellularAccess
+        configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost
+        configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders as [AnyHashable: Any]
+        configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining
+        configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies
         
         return configuration
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Creates a URLSessionConfiguration with specific network settings
private func createURLSessionConfiguration(networkConfig: NetworkConfig) -> URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
// Apply network configuration
configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
return configuration
}
/// Creates a URLSessionConfiguration with specific network settings
private func createURLSessionConfiguration(networkConfig: NetworkConfig) -> URLSessionConfiguration {
let configuration = URLSessionConfiguration.default
// Apply network configuration
configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
configuration.timeoutIntervalForResource = networkConfig.resourceTimeout
configuration.waitsForConnectivity = networkConfig.waitsForConnectivity
configuration.allowsCellularAccess = networkConfig.allowsCellularAccess
configuration.httpMaximumConnectionsPerHost = networkConfig.httpMaximumConnectionsPerHost
configuration.httpAdditionalHeaders = networkConfig.httpAdditionalHeaders as [AnyHashable: Any]
configuration.httpShouldUsePipelining = networkConfig.httpShouldUsePipelining
configuration.httpShouldSetCookies = networkConfig.httpShouldSetCookies
return configuration
}
🤖 Prompt for AI Agents
In FlagsmithClient/Classes/Internal/SSEManager.swift around lines 99 to 107, the
URLSessionConfiguration only sets requestTimeout; update the function to
propagate the rest of NetworkConfig onto the configuration object. Map
NetworkConfig fields such as timeoutIntervalForResource (resource timeout),
waitsForConnectivity, allowsCellularAccess, allowsExpensiveNetworkAccess,
allowsConstrainedNetworkAccess, httpAdditionalHeaders (or headers) and any other
relevant network flags onto the URLSessionConfiguration so the session honors
the full NetworkConfig used elsewhere in docs/examples.

Comment on lines +45 to +51
for i in 0..<10 {
DispatchQueue.global().async {
// Test that we can safely set values concurrently
flagsmith.networkConfig.requestTimeout = Double(i)
expectation.fulfill()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test asserts thread-safety that code does not guarantee

flagsmith.networkConfig.requestTimeout = Double(i) mutates a shared reference without synchronization; NetworkConfig is not thread-safe. This test may pass nondeterministically, masking a data race. Either make NetworkConfig thread-safe (preferred) or drop this test’s concurrency claim.

Apply thread-safety as proposed in the NetworkConfig class comment; this test will then legitimately exercise concurrent writes.

🧰 Tools
🪛 GitHub Check: swift-lint

[failure] 45-45:
Identifier Name Violation: Variable name 'i' should be between 3 and 40 characters long (identifier_name)

🤖 Prompt for AI Agents
In FlagsmithClient/Tests/NetworkConfigTests.swift around lines 45 to 51, the
test concurrently mutates flagsmith.networkConfig.requestTimeout but
NetworkConfig is not thread-safe; either remove the concurrent writes or
(preferred) make NetworkConfig thread-safe as the class comment suggests. Fix by
adding synchronization to NetworkConfig (e.g., a private serial DispatchQueue or
an NSLock) and wrap all mutable property getters/setters (including
requestTimeout) in that lock/queue so concurrent reads/writes are safe, update
any internal state accesses similarly, and then keep the concurrent test as-is.

Comment on lines +67 to +70
// Test negative timeout values (should be allowed as URLSessionConfiguration accepts them)
networkConfig.requestTimeout = -1.0
XCTAssertEqual(networkConfig.requestTimeout, -1.0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify negative timeout semantics

Docs often treat 0 as “no timeout”; negative intervals may be undefined. Please confirm before codifying acceptance of negatives.


🌐 Web query:

Does URLSessionConfiguration.timeoutIntervalForRequest support negative values, or should 0 be used to indicate no timeout?

💡 Result:

Short answer: Don’t use negative values — they’re not documented or supported. Apple documents timeoutIntervalForRequest as a TimeInterval (seconds) with a default of 60 and does not define negative semantics. If you need “no timeout”, avoid relying on undocumented negative behavior; instead use a very large timeout (or manage the lifetime yourself). See Apple’s URLSessionConfiguration docs and the Developer Forums thread explaining timeout semantics. [1][2]

Sources

  • URLSessionConfiguration.timeoutIntervalForRequest (Apple docs). [1]
  • Apple Developer Forums: explanation of timeoutInterval / timeoutIntervalForRequest semantics. [2]

Disallow negative timeout values—clamp to ≥0
URLSessionConfiguration.timeoutIntervalForRequest doesn’t support negative intervals. Update your setter to clamp negative inputs to 0 (or throw) and adjust/remove the negative-timeout test in FlagsmithClient/Tests/NetworkConfigTests.swift (lines 67–70).

🤖 Prompt for AI Agents
In FlagsmithClient/Tests/NetworkConfigTests.swift around lines 67 to 70, the
test currently allows a negative requestTimeout value but
URLSessionConfiguration does not support negative intervals; update the
NetworkConfig.requestTimeout setter to clamp negative values to 0 (or
alternatively throw), and then change the test to assert the clamped value
(XCTAssertEqual(networkConfig.requestTimeout, 0.0)) or remove the
negative-timeout test entirely to reflect the new behavior.

Comment on lines +7 to +17
The `NetworkConfig` class exposes the following commonly used `URLSessionConfiguration` parameters:

- **Request Timeout**: `requestTimeout` - Timeout for individual requests (default: 60.0 seconds)
- **Resource Timeout**: `resourceTimeout` - Total timeout for the entire resource request (default: 604800.0 seconds / 7 days)
- **Connectivity**: `waitsForConnectivity` - Whether to wait for connectivity (default: true)
- **Cellular Access**: `allowsCellularAccess` - Whether to allow cellular access (default: true)
- **Connection Limits**: `httpMaximumConnectionsPerHost` - Max concurrent connections per host (default: 6)
- **Custom Headers**: `httpAdditionalHeaders` - Additional HTTP headers to send with requests (default: empty)
- **HTTP Pipelining**: `httpShouldUsePipelining` - Whether to use HTTP pipelining (default: true)
- **HTTP Cookies**: `httpShouldSetCookies` - Whether to automatically set cookies (default: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Docs list properties not implemented in NetworkConfig

resourceTimeout, waitsForConnectivity, allowsCellularAccess, httpMaximumConnectionsPerHost, httpAdditionalHeaders, httpShouldUsePipelining, httpShouldSetCookies are documented but not present in the code. Example code won’t compile as-is.

Either implement these properties (recommended; see proposed NetworkConfig diff) and apply them in APIManager/SSEManager, or update docs/example to only reflect supported fields.

🤖 Prompt for AI Agents
In NETWORK_CONFIG_README.md around lines 7–17, the docs list NetworkConfig
properties (resourceTimeout, waitsForConnectivity, allowsCellularAccess,
httpMaximumConnectionsPerHost, httpAdditionalHeaders, httpShouldUsePipelining,
httpShouldSetCookies) that are not implemented causing example code to not
compile; fix this by adding those properties to the NetworkConfig type with the
documented defaults and updating the places that create URLSessionConfiguration
(APIManager and SSEManager) to apply each value to the session configuration
(set timeoutIntervalForResource, waitsForConnectivity, allowsCellularAccess,
httpMaximumConnectionsPerHost, httpAdditionalHeaders, httpShouldUsePipelining,
httpShouldSetCookies) or, if you prefer not to implement, update the README and
example to remove these fields and document only the supported properties so
docs and code match.

Comment on lines +113 to +116
## Thread Safety

The `NetworkConfig` is thread-safe and can be modified from any thread. Changes to the configuration will automatically trigger the recreation of the underlying `URLSession` with the new settings.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Thread-safety claim is inaccurate

NetworkConfig is currently not thread-safe. Update the implementation to synchronize access (recommended), or remove this guarantee from the docs.

🤖 Prompt for AI Agents
In NETWORK_CONFIG_README.md around lines 113 to 116, the doc incorrectly states
NetworkConfig is thread-safe; update the implementation to actually synchronize
access or remove the claim. To fix, add a concurrency primitive (e.g., a private
serial DispatchQueue or an NSLock/RWLock) and wrap all mutations and reads of
the config properties and the URLSession recreation logic with that lock/queue
so state and session lifecycle are atomic; ensure session recreation uses a
snapshot of the config inside the lock and publish any external notifications on
the appropriate queue (main if UI); alternatively, if you prefer not to change
code, remove the thread-safety sentence from the README and clearly state the
object is not thread-safe and must be accessed from a single thread.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
FlagsmithClient/Tests/NetworkConfigTests.swift (1)

66-76: Do not accept negative request timeouts.

URLSessionConfiguration.timeoutIntervalForRequest treats negative values as invalid. Allowing networkConfig.requestTimeout = -1.0 will hand a negative interval to the session, which is undefined behaviour and can result in the request timing out immediately or the configuration being rejected. Clamp to >= 0 (or throw) in NetworkConfig and adjust this test to assert the clamped value instead of expecting -1.0.

-        networkConfig.requestTimeout = -1.0
-        XCTAssertEqual(networkConfig.requestTimeout, -1.0)
+        networkConfig.requestTimeout = -1.0
+        XCTAssertEqual(networkConfig.requestTimeout, 0.0)
FlagsmithClient/Classes/Internal/APIManager.swift (1)

97-106: Preserve the default network configuration when recreating sessions.

createURLSessionConfiguration() initialises the session with waitsForConnectivity, timeoutIntervalForResource, httpShouldUsePipelining, etc. As soon as the first request runs, you rebuild the session via createURLSessionConfiguration(networkConfig:cacheConfig:) which only copies requestTimeout. All of the other defaults silently revert to Apple’s defaults (e.g. waitsForConnectivity becomes false), changing behaviour at runtime. Mirror the same defaults in this overload (or move the shared configuration into a helper) so subsequent sessions remain configured identically apart from the overridden timeout.

     private func createURLSessionConfiguration(networkConfig: NetworkConfig, cacheConfig: CacheConfig) -> URLSessionConfiguration {
         let configuration = URLSessionConfiguration.default
         
         // Apply network configuration
         configuration.timeoutIntervalForRequest = networkConfig.requestTimeout
+        configuration.timeoutIntervalForResource = 604800.0
+        configuration.waitsForConnectivity = true
+        configuration.allowsCellularAccess = true
+        configuration.httpMaximumConnectionsPerHost = 6
+        configuration.httpAdditionalHeaders = [:]
+        configuration.httpShouldUsePipelining = true
+        configuration.httpShouldSetCookies = true
         
         // Apply cache configuration
         configuration.urlCache = cacheConfig.cache
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e312d1 and 645f644.

📒 Files selected for processing (5)
  • Example/NetworkConfigExample.swift (1 hunks)
  • FlagsmithClient/Classes/Internal/APIManager.swift (3 hunks)
  • FlagsmithClient/Classes/Internal/SSEManager.swift (3 hunks)
  • FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift (1 hunks)
  • FlagsmithClient/Tests/NetworkConfigTests.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
FlagsmithClient/Classes/Internal/SSEManager.swift (1)
FlagsmithClient/Classes/Internal/APIManager.swift (5)
  • createURLSessionConfiguration (78-95)
  • createURLSessionConfiguration (98-108)
  • request (163-205)
  • request (212-221)
  • request (229-251)
FlagsmithClient/Classes/Internal/APIManager.swift (1)
FlagsmithClient/Classes/Internal/SSEManager.swift (2)
  • createURLSessionConfiguration (83-97)
  • createURLSessionConfiguration (100-107)
Example/NetworkConfigExample.swift (1)
FlagsmithClient/Classes/Flagsmith.swift (1)
  • getFeatureFlags (139-184)
FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift (2)
FlagsmithClient/Classes/Internal/APIManager.swift (3)
  • request (163-205)
  • request (212-221)
  • request (229-251)
FlagsmithClient/Classes/Internal/SSEManager.swift (2)
  • start (173-202)
  • stop (204-208)
🪛 GitHub Check: swift-lint
FlagsmithClient/Tests/NetworkConfigTests.swift

[warning] 94-94:
Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)

Example/NetworkConfigExample.swift

[warning] 76-76:
Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)

FlagsmithClient/Tests/NetworkConfigIntegrationTests.swift

[warning] 121-121:
Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: macos-latest

Comment on lines 186 to 203
// we must use the delegate form here, not the completion handler, to be able to modify the cache
serialAccessQueue.sync {
// Update session cache configuration if it has changed (must be done inside the serial queue)
if session.configuration.urlCache !== Flagsmith.shared.cacheConfig.cache {
let configuration = URLSessionConfiguration.default
configuration.urlCache = Flagsmith.shared.cacheConfig.cache
session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
}
// Always recreate session with current network and cache configuration
// This ensures that any changes to network config are applied immediately
let networkConfig = Flagsmith.shared.networkConfig
let cacheConfig = Flagsmith.shared.cacheConfig

let newConfig = createURLSessionConfiguration(networkConfig: networkConfig, cacheConfig: cacheConfig)
let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main)

// Invalidate previous session before swapping to avoid resource buildup
let oldSession = self.session
self.session = newSession
oldSession.invalidateAndCancel()

let task = session.dataTask(with: request)
let task = newSession.dataTask(with: request)
tasksToCompletionHandlers[task.taskIdentifier] = completion
task.resume()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not cancel in-flight requests when rebuilding the session.

Calling oldSession.invalidateAndCancel() here tears down any tasks still running on the previous session. If a second request arrives before the first completes, you cancel the earlier request mid-flight and return an error to its caller. Swap to finishTasksAndInvalidate() (or delay the swap until no tasks are active) so existing tasks finish before recreating the session.

-            let oldSession = self.session
-            self.session = newSession
-            oldSession.invalidateAndCancel()
+            let oldSession = self.session
+            self.session = newSession
+            oldSession.finishTasksAndInvalidate()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// we must use the delegate form here, not the completion handler, to be able to modify the cache
serialAccessQueue.sync {
// Update session cache configuration if it has changed (must be done inside the serial queue)
if session.configuration.urlCache !== Flagsmith.shared.cacheConfig.cache {
let configuration = URLSessionConfiguration.default
configuration.urlCache = Flagsmith.shared.cacheConfig.cache
session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
}
// Always recreate session with current network and cache configuration
// This ensures that any changes to network config are applied immediately
let networkConfig = Flagsmith.shared.networkConfig
let cacheConfig = Flagsmith.shared.cacheConfig
let newConfig = createURLSessionConfiguration(networkConfig: networkConfig, cacheConfig: cacheConfig)
let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main)
// Invalidate previous session before swapping to avoid resource buildup
let oldSession = self.session
self.session = newSession
oldSession.invalidateAndCancel()
let task = session.dataTask(with: request)
let task = newSession.dataTask(with: request)
tasksToCompletionHandlers[task.taskIdentifier] = completion
task.resume()
// we must use the delegate form here, not the completion handler, to be able to modify the cache
serialAccessQueue.sync {
// Always recreate session with current network and cache configuration
// This ensures that any changes to network config are applied immediately
let networkConfig = Flagsmith.shared.networkConfig
let cacheConfig = Flagsmith.shared.cacheConfig
let newConfig = createURLSessionConfiguration(networkConfig: networkConfig, cacheConfig: cacheConfig)
let newSession = URLSession(configuration: newConfig, delegate: self, delegateQueue: OperationQueue.main)
// Invalidate previous session before swapping to avoid resource buildup
let oldSession = self.session
self.session = newSession
oldSession.finishTasksAndInvalidate()
let task = newSession.dataTask(with: request)
tasksToCompletionHandlers[task.taskIdentifier] = completion
task.resume()
🤖 Prompt for AI Agents
In FlagsmithClient/Classes/Internal/APIManager.swift around lines 186-203, the
code currently calls oldSession.invalidateAndCancel(), which cancels any
in-flight tasks; change this to call oldSession.finishTasksAndInvalidate() so
existing tasks are allowed to complete before the session is torn down, or
alternatively delay swapping sessions until oldSession has no active tasks
(e.g., check oldSession.getAllTasks and wait/completion handler); ensure you
still assign self.session to the new session and resume the new task without
cancelling existing requests.

@gazreese gazreese changed the title Feature urlsessionconfiguration change requestTimeout feat: urlsessionconfiguration change requestTimeout Oct 15, 2025
@gazreese
Copy link
Copy Markdown
Member

Hi @polatolu let's pause on this one for now, it looks like it's getting complicated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants