@@ -114,37 +114,51 @@ public final class GRPCClient: Sendable {
114
114
115
115
private let interceptorPipeline : [ ClientInterceptorPipelineOperation ]
116
116
117
- /// A collection of interceptors providing cross-cutting functionality to each accepted RPC, keyed by the method to which they apply.
118
- ///
119
- /// The list of interceptors for each method is computed from `interceptorsPipeline` when calling a method for the first time.
120
- /// This caching is done to avoid having to compute the applicable interceptors for each request made.
121
- ///
122
- /// The order in which interceptors are added reflects the order in which they are called. The
123
- /// first interceptor added will be the first interceptor to intercept each request. The last
124
- /// interceptor added will be the final interceptor to intercept each request before calling
125
- /// the appropriate handler.
126
- private let interceptorsPerMethod : Mutex < [ MethodDescriptor : [ any ClientInterceptor ] ] >
127
-
128
117
/// The current state of the client.
129
118
private let state : Mutex < State >
130
119
131
120
/// The state of the client.
132
121
private enum State : Sendable {
122
+
133
123
/// The client hasn't been started yet. Can transition to `running` or `stopped`.
134
- case notStarted
124
+ case notStarted(
125
+ /// A collection of interceptors providing cross-cutting functionality to each accepted RPC, keyed by the method to which they apply.
126
+ ///
127
+ /// The list of interceptors for each method is computed from `interceptorsPipeline` when calling a method for the first time.
128
+ /// This caching is done to avoid having to compute the applicable interceptors for each request made.
129
+ ///
130
+ /// The order in which interceptors are added reflects the order in which they are called. The
131
+ /// first interceptor added will be the first interceptor to intercept each request. The last
132
+ /// interceptor added will be the final interceptor to intercept each request before calling
133
+ /// the appropriate handler.
134
+ interceptorsPerMethod: [ MethodDescriptor : [ any ClientInterceptor ] ]
135
+ )
135
136
/// The client is running and can send RPCs. Can transition to `stopping`.
136
- case running
137
+ case running(
138
+ /// A collection of interceptors providing cross-cutting functionality to each accepted RPC, keyed by the method to which they apply.
139
+ ///
140
+ /// The list of interceptors for each method is computed from `interceptorsPipeline` when calling a method for the first time.
141
+ /// This caching is done to avoid having to compute the applicable interceptors for each request made.
142
+ ///
143
+ /// The order in which interceptors are added reflects the order in which they are called. The
144
+ /// first interceptor added will be the first interceptor to intercept each request. The last
145
+ /// interceptor added will be the final interceptor to intercept each request before calling
146
+ /// the appropriate handler.
147
+ interceptorsPerMethod: [ MethodDescriptor : [ any ClientInterceptor ] ]
148
+ )
137
149
/// The client is stopping and no new RPCs will be sent. Existing RPCs may run to
138
150
/// completion. May transition to `stopped`.
139
151
case stopping
140
152
/// The client has stopped, no RPCs are in flight and no more will be accepted. This state
141
153
/// is terminal.
142
154
case stopped
155
+ /// Temporary state to avoid CoWs.
156
+ case _modifying
143
157
144
158
mutating func run( ) throws {
145
159
switch self {
146
- case . notStarted:
147
- self = . running
160
+ case . notStarted( let interceptorsPerMethod ) :
161
+ self = . running( interceptorsPerMethod : interceptorsPerMethod )
148
162
149
163
case . running:
150
164
throw RuntimeError (
@@ -157,6 +171,9 @@ public final class GRPCClient: Sendable {
157
171
code: . clientIsStopped,
158
172
message: " The client has stopped and can only be started once. "
159
173
)
174
+
175
+ case . _modifying:
176
+ fatalError ( " Internal inconsistency " )
160
177
}
161
178
}
162
179
@@ -174,6 +191,8 @@ public final class GRPCClient: Sendable {
174
191
return true
175
192
case . stopping, . stopped:
176
193
return false
194
+ case . _modifying:
195
+ fatalError ( " Internal inconsistency " )
177
196
}
178
197
}
179
198
@@ -188,6 +207,8 @@ public final class GRPCClient: Sendable {
188
207
code: . clientIsStopped,
189
208
message: " Client has been stopped. Can't make any more RPCs. "
190
209
)
210
+ case . _modifying:
211
+ fatalError ( " Internal inconsistency " )
191
212
}
192
213
}
193
214
}
@@ -226,8 +247,7 @@ public final class GRPCClient: Sendable {
226
247
) {
227
248
self . transport = transport
228
249
self . interceptorPipeline = interceptorPipeline
229
- self . interceptorsPerMethod = Mutex ( [ : ] )
230
- self . state = Mutex ( . notStarted)
250
+ self . state = Mutex ( . notStarted( interceptorsPerMethod: [ : ] ) )
231
251
}
232
252
233
253
/// Start the client.
@@ -386,15 +406,39 @@ public final class GRPCClient: Sendable {
386
406
var options = options
387
407
options. formUnion ( with: methodConfig)
388
408
389
- let applicableInterceptors = self . interceptorsPerMethod. withLock {
390
- if let interceptors = $0 [ descriptor] {
391
- return interceptors
392
- } else {
393
- let interceptors = self . interceptorPipeline
394
- . filter { $0. applies ( to: descriptor) }
395
- . map { $0. interceptor }
396
- $0 [ descriptor] = interceptors
397
- return interceptors
409
+ let applicableInterceptors = self . state. withLock {
410
+ switch $0 {
411
+ case . notStarted( var interceptorsPerMethod) :
412
+ if let interceptors = interceptorsPerMethod [ descriptor] {
413
+ return interceptors
414
+ } else {
415
+ $0 = . _modifying
416
+ let interceptors = self . interceptorPipeline
417
+ . filter { $0. applies ( to: descriptor) }
418
+ . map { $0. interceptor }
419
+ interceptorsPerMethod [ descriptor] = interceptors
420
+ $0 = . notStarted( interceptorsPerMethod: interceptorsPerMethod)
421
+ return interceptors
422
+ }
423
+
424
+ case . running( var interceptorsPerMethod) :
425
+ if let interceptors = interceptorsPerMethod [ descriptor] {
426
+ return interceptors
427
+ } else {
428
+ $0 = . _modifying
429
+ let interceptors = self . interceptorPipeline
430
+ . filter { $0. applies ( to: descriptor) }
431
+ . map { $0. interceptor }
432
+ interceptorsPerMethod [ descriptor] = interceptors
433
+ $0 = . running( interceptorsPerMethod: interceptorsPerMethod)
434
+ return interceptors
435
+ }
436
+
437
+ case . stopping, . stopped:
438
+ fatalError ( " The checkExecutable call should have failed. " )
439
+
440
+ case . _modifying:
441
+ fatalError ( " Internal inconsistency " )
398
442
}
399
443
}
400
444
0 commit comments