3030import java .util .Collections ;
3131import java .util .List ;
3232import java .util .Queue ;
33- import java .util .concurrent .atomic .AtomicBoolean ;
33+ import java .util .concurrent .atomic .AtomicInteger ;
3434import javax .annotation .Nullable ;
3535import javax .annotation .concurrent .GuardedBy ;
3636
@@ -52,14 +52,21 @@ public class GcpClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
5252 private ClientCall <ReqT , RespT > delegateCall = null ;
5353 private List <String > keys = null ;
5454 private boolean received = false ;
55- private final AtomicBoolean decremented = new AtomicBoolean (false );
55+ // 0 = not counted, 1 = counted, 2 = finished.
56+ private final AtomicInteger countState = new AtomicInteger ();
5657
5758 @ GuardedBy ("this" )
5859 private final Queue <Runnable > calls = new ArrayDeque <>();
5960
6061 @ GuardedBy ("this" )
6162 private boolean started ;
6263
64+ @ GuardedBy ("this" )
65+ private boolean cancelQueued ;
66+
67+ @ GuardedBy ("this" )
68+ private boolean cancelled ;
69+
6370 private long startNanos = 0 ;
6471
6572 protected GcpClientCall (
@@ -90,7 +97,22 @@ public void setMessageCompression(boolean enabled) {
9097
9198 @ Override
9299 public void cancel (@ Nullable String message , @ Nullable Throwable cause ) {
93- checkSendMessage (() -> checkedCancel (message , cause ));
100+ synchronized (this ) {
101+ if (cancelQueued || cancelled ) {
102+ return ;
103+ }
104+ cancelQueued = true ;
105+ Runnable cancelCall =
106+ () -> {
107+ cancelled = true ;
108+ checkedCancel (message , cause );
109+ };
110+ if (started ) {
111+ cancelCall .run ();
112+ } else {
113+ calls .add (cancelCall );
114+ }
115+ }
94116 }
95117
96118 @ Override
@@ -104,6 +126,7 @@ public void halfClose() {
104126 */
105127 @ Override
106128 public void sendMessage (ReqT message ) {
129+ boolean send ;
107130 synchronized (this ) {
108131 if (!started ) {
109132 startNanos = System .nanoTime ();
@@ -123,17 +146,29 @@ public void sendMessage(ReqT message) {
123146 delegateChannelRef = delegateChannel .getChannelRef (key );
124147 }
125148 delegateChannelRef .activeStreamsCountIncr ();
126-
127- // Create the client call and do the previous operations.
128- delegateCall = delegateChannelRef .getChannel ().newCall (methodDescriptor , callOptions );
129- for (Runnable call : calls ) {
130- call .run ();
149+ countState .set (1 );
150+
151+ try {
152+ // Create the client call and do the previous operations.
153+ CallOptions callOptionsWithChannelId =
154+ callOptions .withOption (GcpManagedChannel .CHANNEL_ID_KEY , delegateChannelRef .getId ());
155+ delegateCall =
156+ delegateChannelRef .getChannel ().newCall (methodDescriptor , callOptionsWithChannelId );
157+ for (Runnable call : calls ) {
158+ call .run ();
159+ }
160+ } catch (RuntimeException | Error failure ) {
161+ finishCount (Status .fromThrowable (failure ), true );
162+ throw failure ;
131163 }
132164 calls .clear ();
133165 started = true ;
134166 }
167+ send = !cancelled ;
168+ }
169+ if (send ) {
170+ delegateCall .sendMessage (message );
135171 }
136- delegateCall .sendMessage (message );
137172 }
138173
139174 /** Calls that send exactly one message should not check this method. */
@@ -162,14 +197,21 @@ public String toString() {
162197 }
163198
164199 private void checkedCancel (@ Nullable String message , @ Nullable Throwable cause ) {
165- if (!decremented .getAndSet (true )) {
166- delegateChannelRef .activeStreamsCountDecr (startNanos , Status .CANCELLED , true );
167- }
200+ finishCount (Status .CANCELLED , true );
168201 delegateCall .cancel (message , cause );
169202 }
170203
204+ private void finishCount (Status status , boolean fromClientSide ) {
205+ if (countState .compareAndSet (1 , 2 )) {
206+ delegateChannelRef .activeStreamsCountDecr (startNanos , status , fromClientSide );
207+ }
208+ }
209+
171210 private void checkSendMessage (Runnable call ) {
172211 synchronized (this ) {
212+ if (cancelQueued || cancelled ) {
213+ return ;
214+ }
173215 if (started ) {
174216 call .run ();
175217 } else {
@@ -185,9 +227,7 @@ private Listener<RespT> getListener(final Listener<RespT> responseListener) {
185227 // Decrement the stream number by one when the call is closed.
186228 @ Override
187229 public void onClose (Status status , Metadata trailers ) {
188- if (!decremented .getAndSet (true )) {
189- delegateChannelRef .activeStreamsCountDecr (startNanos , status , false );
190- }
230+ finishCount (status , false );
191231 // If the operation completed successfully, bind/unbind the affinity key.
192232 if (keys != null && status .getCode () == Status .Code .OK ) {
193233 if (affinity .getCommand () == AffinityConfig .Command .UNBIND ) {
@@ -219,7 +259,8 @@ public void onMessage(RespT message) {
219259 * A simple wrapper of ClientCall.
220260 *
221261 * <p>It defines the callback function to manage the number of active streams of a ChannelRef
222- * everytime a call is started/closed.
262+ * every time a call is created/closed. Stream capacity is reserved in the constructor, before
263+ * {@link #start(Listener, Metadata)}, and remains reserved until close or cancel.
223264 */
224265 public static class SimpleGcpClientCall <ReqT , RespT > extends ForwardingClientCall <ReqT , RespT > {
225266
@@ -230,7 +271,13 @@ public static class SimpleGcpClientCall<ReqT, RespT> extends ForwardingClientCal
230271 private final boolean unbindOnComplete ;
231272 private long startNanos = 0 ;
232273
233- private final AtomicBoolean decremented = new AtomicBoolean (false );
274+ private final Object countLock = new Object ();
275+
276+ @ GuardedBy ("countLock" )
277+ private boolean counted ;
278+
279+ @ GuardedBy ("countLock" )
280+ private boolean finished ;
234281
235282 protected SimpleGcpClientCall (
236283 GcpManagedChannel delegateChannel ,
@@ -244,8 +291,18 @@ protected SimpleGcpClientCall(
244291 // Set the actual channel ID in callOptions so downstream interceptors can access it.
245292 CallOptions callOptionsWithChannelId =
246293 callOptions .withOption (GcpManagedChannel .CHANNEL_ID_KEY , channelRef .getId ());
247- this .delegateCall =
248- channelRef .getChannel ().newCall (methodDescriptor , callOptionsWithChannelId );
294+ startNanos = System .nanoTime ();
295+ synchronized (countLock ) {
296+ channelRef .activeStreamsCountIncr ();
297+ counted = true ;
298+ }
299+ try {
300+ this .delegateCall =
301+ channelRef .getChannel ().newCall (methodDescriptor , callOptionsWithChannelId );
302+ } catch (RuntimeException | Error failure ) {
303+ finishCount (Status .fromThrowable (failure ), true );
304+ throw failure ;
305+ }
249306 }
250307
251308 @ Override
@@ -255,16 +312,12 @@ protected ClientCall<ReqT, RespT> delegate() {
255312
256313 @ Override
257314 public void start (Listener <RespT > responseListener , Metadata headers ) {
258- startNanos = System .nanoTime ();
259-
260315 Listener <RespT > listener =
261316 new ForwardingClientCallListener .SimpleForwardingClientCallListener <RespT >(
262317 responseListener ) {
263318 @ Override
264319 public void onClose (Status status , Metadata trailers ) {
265- if (!decremented .getAndSet (true )) {
266- channelRef .activeStreamsCountDecr (startNanos , status , false );
267- }
320+ finishCount (status , false );
268321 // Unbind the affinity key when the caller explicitly requests it
269322 // (e.g., on terminal RPCs like Commit or Rollback) to prevent
270323 // unbounded growth of the affinity map.
@@ -281,20 +334,34 @@ public void onMessage(RespT message) {
281334 }
282335 };
283336
284- channelRef .activeStreamsCountIncr ();
285- delegateCall .start (listener , headers );
337+ try {
338+ delegateCall .start (listener , headers );
339+ } catch (RuntimeException | Error failure ) {
340+ finishCount (Status .fromThrowable (failure ), true );
341+ throw failure ;
342+ }
286343 }
287344
288345 @ Override
289346 public void cancel (String message , Throwable cause ) {
290- if (!decremented .getAndSet (true )) {
291- channelRef .activeStreamsCountDecr (startNanos , Status .CANCELLED , true );
292- }
347+ finishCount (Status .CANCELLED , true );
293348 // Always unbind on cancel — the transaction is being abandoned.
294349 if (affinityKey != null ) {
295350 delegateChannel .unbind (Collections .singletonList (affinityKey ));
296351 }
297352 delegateCall .cancel (message , cause );
298353 }
354+
355+ private void finishCount (Status status , boolean fromClientSide ) {
356+ synchronized (countLock ) {
357+ if (finished ) {
358+ return ;
359+ }
360+ finished = true ;
361+ if (counted ) {
362+ channelRef .activeStreamsCountDecr (startNanos , status , fromClientSide );
363+ }
364+ }
365+ }
299366 }
300367}
0 commit comments