Skip to content

Commit d9dd07a

Browse files
authored
[Swift6] Fix TSan race condition in Swift6 SynchronizedDictionary (#23091)
Problem SynchronizedDictionary is a struct held as a var property on shared singleton classes (URLSessionRequestBuilderConfiguration and AlamofireRequestBuilderConfiguration). When multiple concurrent network requests mutate credentialStore or managerStore from different threads, Swift's memory exclusivity rules treat each subscript mutation as a modifying access to the entire struct — causing a real ThreadSanitizer "Swift access race". Changes struct → class (SynchronizedDictionary.mustache + 13 sample files) Changing SynchronizedDictionary from a value type to a reference type makes mutations go through pointer indirection. Concurrent subscript writes now only touch the object's internal state (already protected by NSRecursiveLock), rather than triggering a modifying access on the owning class's stored property. var → let (URLSessionImplementations.mustache + 11 sample files, AlamofireImplementations.mustache + 2 sample files) Since SynchronizedDictionary is now a class, the properties credentialStore and managerStore only need to hold a stable reference — they never get reassigned. Declaring them as let makes this intent explicit and prevents any residual exclusivity issues on the property itself.
1 parent d11bc86 commit d9dd07a

File tree

29 files changed

+29
-29
lines changed

29 files changed

+29
-29
lines changed

modules/openapi-generator/src/main/resources/swift6/SynchronizedDictionary.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Foundation
88

9-
internal struct SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
9+
internal class SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
1010
1111
private var dictionary = [K: V]()
1212
private let lock = NSRecursiveLock()

modules/openapi-generator/src/main/resources/swift6/libraries/alamofire/AlamofireImplementations.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fileprivate class AlamofireRequestBuilderConfiguration: @unchecked Sendable {
2424
static let shared = AlamofireRequestBuilderConfiguration()
2525

2626
// Store manager to retain its reference
27-
var managerStore = SynchronizedDictionary<String, Alamofire.Session>()
27+
let managerStore = SynchronizedDictionary<String, Alamofire.Session>()
2828
}
2929

3030
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireRequestBuilder<T: Sendable>: RequestBuilder<T>, @unchecked Sendable {

modules/openapi-generator/src/main/resources/swift6/libraries/urlsession/URLSessionImplementations.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
6666
let defaultURLSession: URLSession
6767

6868
// Store current URLCredential for every URLSessionTask
69-
var credentialStore = SynchronizedDictionary<Int, URLCredential>()
69+
let credentialStore = SynchronizedDictionary<Int, URLCredential>()
7070
}
7171

7272
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionRequestBuilder<T: Sendable>: RequestBuilder<T>, @unchecked Sendable {

samples/client/petstore/swift6/alamofireLibrary/Sources/PetstoreClient/Infrastructure/AlamofireImplementations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fileprivate class AlamofireRequestBuilderConfiguration: @unchecked Sendable {
2424
static let shared = AlamofireRequestBuilderConfiguration()
2525

2626
// Store manager to retain its reference
27-
var managerStore = SynchronizedDictionary<String, Alamofire.Session>()
27+
let managerStore = SynchronizedDictionary<String, Alamofire.Session>()
2828
}
2929

3030
open class AlamofireRequestBuilder<T: Sendable>: RequestBuilder<T>, @unchecked Sendable {

samples/client/petstore/swift6/alamofireLibrary/Sources/PetstoreClient/Infrastructure/SynchronizedDictionary.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Foundation
88

9-
internal struct SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
9+
internal class SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
1010

1111
private var dictionary = [K: V]()
1212
private let lock = NSRecursiveLock()

samples/client/petstore/swift6/apiNonStaticMethod/Sources/PetstoreClient/Infrastructure/AlamofireImplementations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fileprivate class AlamofireRequestBuilderConfiguration: @unchecked Sendable {
2424
static let shared = AlamofireRequestBuilderConfiguration()
2525

2626
// Store manager to retain its reference
27-
var managerStore = SynchronizedDictionary<String, Alamofire.Session>()
27+
let managerStore = SynchronizedDictionary<String, Alamofire.Session>()
2828
}
2929

3030
open class AlamofireRequestBuilder<T: Sendable>: RequestBuilder<T>, @unchecked Sendable {

samples/client/petstore/swift6/apiNonStaticMethod/Sources/PetstoreClient/Infrastructure/SynchronizedDictionary.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Foundation
88

9-
internal struct SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
9+
internal class SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
1010

1111
private var dictionary = [K: V]()
1212
private let lock = NSRecursiveLock()

samples/client/petstore/swift6/asyncAwaitLibrary/Sources/PetstoreClient/Infrastructure/SynchronizedDictionary.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Foundation
88

9-
internal struct SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
9+
internal class SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
1010

1111
private var dictionary = [K: V]()
1212
private let lock = NSRecursiveLock()

samples/client/petstore/swift6/asyncAwaitLibrary/Sources/PetstoreClient/Infrastructure/URLSessionImplementations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
6666
let defaultURLSession: URLSession
6767

6868
// Store current URLCredential for every URLSessionTask
69-
var credentialStore = SynchronizedDictionary<Int, URLCredential>()
69+
let credentialStore = SynchronizedDictionary<Int, URLCredential>()
7070
}
7171

7272
open class URLSessionRequestBuilder<T: Sendable>: RequestBuilder<T>, @unchecked Sendable {

samples/client/petstore/swift6/combineDeferredLibrary/PetstoreClient/Classes/OpenAPIs/Infrastructure/SynchronizedDictionary.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Foundation
88

9-
internal struct SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
9+
internal class SynchronizedDictionary<K: Hashable, V> : @unchecked Sendable {
1010

1111
private var dictionary = [K: V]()
1212
private let lock = NSRecursiveLock()

0 commit comments

Comments
 (0)