25
25
import com .google .api .core .BetaApi ;
26
26
import com .google .api .core .SettableApiFuture ;
27
27
import com .google .api .gax .batching .BatchingSettings ;
28
+ import com .google .api .gax .batching .FlowControlSettings ;
29
+ import com .google .api .gax .batching .FlowController ;
28
30
import com .google .api .gax .core .BackgroundResource ;
29
31
import com .google .api .gax .core .BackgroundResourceAggregation ;
30
32
import com .google .api .gax .core .CredentialsProvider ;
55
57
import java .util .List ;
56
58
import java .util .Map ;
57
59
import java .util .concurrent .Callable ;
60
+ import java .util .concurrent .CountDownLatch ;
58
61
import java .util .concurrent .ScheduledExecutorService ;
59
62
import java .util .concurrent .ScheduledFuture ;
60
63
import java .util .concurrent .TimeUnit ;
@@ -108,6 +111,8 @@ public class Publisher {
108
111
private ScheduledFuture <?> currentAlarmFuture ;
109
112
private final ApiFunction <PubsubMessage , PubsubMessage > messageTransform ;
110
113
114
+ private MessageFlowController flowController = null ;
115
+
111
116
/** The maximum number of messages in one request. Defined by the API. */
112
117
public static long getApiMaxRequestElementCount () {
113
118
return 1000L ;
@@ -122,6 +127,16 @@ private Publisher(Builder builder) throws IOException {
122
127
topicName = builder .topicName ;
123
128
124
129
this .batchingSettings = builder .batchingSettings ;
130
+ FlowControlSettings flowControl = this .batchingSettings .getFlowControlSettings ();
131
+ if (flowControl != null
132
+ && flowControl .getLimitExceededBehavior () != FlowController .LimitExceededBehavior .Ignore ) {
133
+ this .flowController =
134
+ new MessageFlowController (
135
+ flowControl .getMaxOutstandingElementCount (),
136
+ flowControl .getMaxOutstandingRequestBytes (),
137
+ flowControl .getLimitExceededBehavior ());
138
+ }
139
+
125
140
this .enableMessageOrdering = builder .enableMessageOrdering ;
126
141
this .messageTransform = builder .messageTransform ;
127
142
@@ -221,6 +236,19 @@ public ApiFuture<String> publish(PubsubMessage message) {
221
236
222
237
final OutstandingPublish outstandingPublish =
223
238
new OutstandingPublish (messageTransform .apply (message ));
239
+
240
+ if (flowController != null ) {
241
+ try {
242
+ flowController .acquire (outstandingPublish .messageSize );
243
+ } catch (FlowController .FlowControlException e ) {
244
+ if (!orderingKey .isEmpty ()) {
245
+ sequentialExecutor .stopPublish (orderingKey );
246
+ }
247
+ outstandingPublish .publishResult .setException (e );
248
+ return outstandingPublish .publishResult ;
249
+ }
250
+ }
251
+
224
252
List <OutstandingBatch > batchesToSend ;
225
253
messagesBatchLock .lock ();
226
254
try {
@@ -454,7 +482,7 @@ public ApiFuture<PublishResponse> call() {
454
482
ApiFutures .addCallback (future , futureCallback , directExecutor ());
455
483
}
456
484
457
- private static final class OutstandingBatch {
485
+ private final class OutstandingBatch {
458
486
final List <OutstandingPublish > outstandingPublishes ;
459
487
final long creationTime ;
460
488
int attempt ;
@@ -484,14 +512,21 @@ private List<PubsubMessage> getMessages() {
484
512
485
513
private void onFailure (Throwable t ) {
486
514
for (OutstandingPublish outstandingPublish : outstandingPublishes ) {
515
+ if (flowController != null ) {
516
+ flowController .release (outstandingPublish .messageSize );
517
+ }
487
518
outstandingPublish .publishResult .setException (t );
488
519
}
489
520
}
490
521
491
522
private void onSuccess (Iterable <String > results ) {
492
523
Iterator <OutstandingPublish > messagesResultsIt = outstandingPublishes .iterator ();
493
524
for (String messageId : results ) {
494
- messagesResultsIt .next ().publishResult .set (messageId );
525
+ OutstandingPublish nextPublish = messagesResultsIt .next ();
526
+ if (flowController != null ) {
527
+ flowController .release (nextPublish .messageSize );
528
+ }
529
+ nextPublish .publishResult .set (messageId );
495
530
}
496
531
}
497
532
}
@@ -602,6 +637,10 @@ public static final class Builder {
602
637
.setDelayThreshold (DEFAULT_DELAY_THRESHOLD )
603
638
.setRequestByteThreshold (DEFAULT_REQUEST_BYTES_THRESHOLD )
604
639
.setElementCountThreshold (DEFAULT_ELEMENT_COUNT_THRESHOLD )
640
+ .setFlowControlSettings (
641
+ FlowControlSettings .newBuilder ()
642
+ .setLimitExceededBehavior (FlowController .LimitExceededBehavior .Ignore )
643
+ .build ())
605
644
.build ();
606
645
static final RetrySettings DEFAULT_RETRY_SETTINGS =
607
646
RetrySettings .newBuilder ()
@@ -759,7 +798,135 @@ public Publisher build() throws IOException {
759
798
}
760
799
}
761
800
762
- private static class MessagesBatch {
801
+ private static class MessageFlowController {
802
+ private final Lock lock ;
803
+ private final Long messageLimit ;
804
+ private final Long byteLimit ;
805
+ private final FlowController .LimitExceededBehavior limitBehavior ;
806
+
807
+ private Long outstandingMessages ;
808
+ private Long outstandingBytes ;
809
+ private LinkedList <CountDownLatch > awaitingMessageAcquires ;
810
+ private LinkedList <CountDownLatch > awaitingBytesAcquires ;
811
+
812
+ MessageFlowController (
813
+ Long messageLimit , Long byteLimit , FlowController .LimitExceededBehavior limitBehavior ) {
814
+ this .messageLimit = messageLimit ;
815
+ this .byteLimit = byteLimit ;
816
+ this .limitBehavior = limitBehavior ;
817
+ this .lock = new ReentrantLock ();
818
+
819
+ this .outstandingMessages = 0L ;
820
+ this .outstandingBytes = 0L ;
821
+
822
+ this .awaitingMessageAcquires = new LinkedList <CountDownLatch >();
823
+ this .awaitingBytesAcquires = new LinkedList <CountDownLatch >();
824
+ }
825
+
826
+ void acquire (long messageSize ) throws FlowController .FlowControlException {
827
+ lock .lock ();
828
+ try {
829
+ if (outstandingMessages >= messageLimit
830
+ && limitBehavior == FlowController .LimitExceededBehavior .ThrowException ) {
831
+ throw new FlowController .MaxOutstandingElementCountReachedException (messageLimit );
832
+ }
833
+ if (outstandingBytes + messageSize >= byteLimit
834
+ && limitBehavior == FlowController .LimitExceededBehavior .ThrowException ) {
835
+ throw new FlowController .MaxOutstandingRequestBytesReachedException (byteLimit );
836
+ }
837
+
838
+ // We can acquire or we should wait until we can acquire.
839
+ // Start by acquiring a slot for a message.
840
+ CountDownLatch messageWaiter = null ;
841
+ while (outstandingMessages >= messageLimit ) {
842
+ if (messageWaiter == null ) {
843
+ // This message gets added to the back of the line.
844
+ messageWaiter = new CountDownLatch (1 );
845
+ awaitingMessageAcquires .addLast (messageWaiter );
846
+ } else {
847
+ // This message already in line stays at the head of the line.
848
+ messageWaiter = new CountDownLatch (1 );
849
+ awaitingMessageAcquires .set (0 , messageWaiter );
850
+ }
851
+ lock .unlock ();
852
+ try {
853
+ messageWaiter .await ();
854
+ } catch (InterruptedException e ) {
855
+ logger .log (Level .WARNING , "Interrupted while waiting to acquire flow control tokens" );
856
+ }
857
+ lock .lock ();
858
+ }
859
+ ++outstandingMessages ;
860
+ if (messageWaiter != null ) {
861
+ awaitingMessageAcquires .removeFirst ();
862
+ }
863
+
864
+ // There may be some surplus messages left; let the next message waiting for a token have
865
+ // one.
866
+ if (!awaitingMessageAcquires .isEmpty () && outstandingMessages < messageLimit ) {
867
+ awaitingMessageAcquires .getFirst ().countDown ();
868
+ }
869
+
870
+ // Now acquire space for bytes.
871
+ CountDownLatch bytesWaiter = null ;
872
+ Long bytesRemaining = messageSize ;
873
+ while (outstandingBytes + bytesRemaining >= byteLimit ) {
874
+ // Take what is available.
875
+ Long available = byteLimit - outstandingBytes ;
876
+ bytesRemaining -= available ;
877
+ outstandingBytes = byteLimit ;
878
+ if (bytesWaiter == null ) {
879
+ // This message gets added to the back of the line.
880
+ bytesWaiter = new CountDownLatch (1 );
881
+ awaitingBytesAcquires .addLast (bytesWaiter );
882
+ } else {
883
+ // This message already in line stays at the head of the line.
884
+ bytesWaiter = new CountDownLatch (1 );
885
+ awaitingBytesAcquires .set (0 , bytesWaiter );
886
+ }
887
+ lock .unlock ();
888
+ try {
889
+ bytesWaiter .await ();
890
+ } catch (InterruptedException e ) {
891
+ logger .log (Level .WARNING , "Interrupted while waiting to acquire flow control tokens" );
892
+ }
893
+ lock .lock ();
894
+ }
895
+
896
+ outstandingBytes += bytesRemaining ;
897
+ if (bytesWaiter != null ) {
898
+ awaitingBytesAcquires .removeFirst ();
899
+ }
900
+ // There may be some surplus bytes left; let the next message waiting for bytes have some.
901
+ if (!awaitingBytesAcquires .isEmpty () && outstandingBytes < byteLimit ) {
902
+ awaitingBytesAcquires .getFirst ().countDown ();
903
+ }
904
+ } finally {
905
+ lock .unlock ();
906
+ }
907
+ }
908
+
909
+ private void notifyNextAcquires () {
910
+ if (!awaitingMessageAcquires .isEmpty ()) {
911
+ CountDownLatch awaitingAcquire = awaitingMessageAcquires .getFirst ();
912
+ awaitingAcquire .countDown ();
913
+ }
914
+ if (!awaitingBytesAcquires .isEmpty ()) {
915
+ CountDownLatch awaitingAcquire = awaitingBytesAcquires .getFirst ();
916
+ awaitingAcquire .countDown ();
917
+ }
918
+ }
919
+
920
+ void release (long messageSize ) {
921
+ lock .lock ();
922
+ --outstandingMessages ;
923
+ outstandingBytes -= messageSize ;
924
+ notifyNextAcquires ();
925
+ lock .unlock ();
926
+ }
927
+ }
928
+
929
+ private class MessagesBatch {
763
930
private List <OutstandingPublish > messages ;
764
931
private int batchedBytes ;
765
932
private String orderingKey ;
0 commit comments