1717package com .google .cloud .spanner ;
1818
1919import static com .google .cloud .spanner .SpannerExceptionFactory .newSpannerException ;
20+ import static com .google .cloud .spanner .SpannerExceptionFactory .newSpannerExceptionForCancellation ;
2021import static com .google .common .base .Preconditions .checkArgument ;
2122import static com .google .common .base .Preconditions .checkNotNull ;
2223import static com .google .common .base .Preconditions .checkState ;
2324
2425import com .google .api .client .util .BackOff ;
26+ import com .google .api .client .util .ExponentialBackOff ;
27+ import com .google .api .gax .retrying .RetrySettings ;
2528import com .google .cloud .ByteArray ;
2629import com .google .cloud .Date ;
2730import com .google .cloud .Timestamp ;
2831import com .google .cloud .spanner .spi .v1 .SpannerRpc ;
32+ import com .google .cloud .spanner .v1 .stub .SpannerStubSettings ;
2933import com .google .common .annotations .VisibleForTesting ;
3034import com .google .common .base .Function ;
3135import com .google .common .collect .AbstractIterator ;
4650import io .opencensus .trace .Span ;
4751import io .opencensus .trace .Tracer ;
4852import io .opencensus .trace .Tracing ;
53+ import java .io .IOException ;
4954import java .io .Serializable ;
5055import java .util .AbstractList ;
5156import java .util .ArrayList ;
5560import java .util .LinkedList ;
5661import java .util .List ;
5762import java .util .concurrent .BlockingQueue ;
63+ import java .util .concurrent .CountDownLatch ;
64+ import java .util .concurrent .Executor ;
5865import java .util .concurrent .LinkedBlockingQueue ;
66+ import java .util .concurrent .TimeUnit ;
5967import java .util .logging .Level ;
6068import java .util .logging .Logger ;
6169import javax .annotation .Nullable ;
@@ -820,8 +828,10 @@ void setCall(SpannerRpc.StreamingCall call) {
820828 @ VisibleForTesting
821829 abstract static class ResumableStreamIterator extends AbstractIterator <PartialResultSet >
822830 implements CloseableIterator <PartialResultSet > {
831+ private static final RetrySettings STREAMING_RETRY_SETTINGS =
832+ SpannerStubSettings .newBuilder ().executeStreamingSqlSettings ().getRetrySettings ();
823833 private static final Logger logger = Logger .getLogger (ResumableStreamIterator .class .getName ());
824- private final BackOff backOff = SpannerImpl . newBackOff ();
834+ private final BackOff backOff = newBackOff ();
825835 private final LinkedList <PartialResultSet > buffer = new LinkedList <>();
826836 private final int maxBufferSize ;
827837 private final Span span ;
@@ -841,6 +851,70 @@ protected ResumableStreamIterator(int maxBufferSize, String streamName, Span par
841851 this .span = tracer .spanBuilderWithExplicitParent (streamName , parent ).startSpan ();
842852 }
843853
854+ private static ExponentialBackOff newBackOff () {
855+ return new ExponentialBackOff .Builder ()
856+ .setMultiplier (STREAMING_RETRY_SETTINGS .getRetryDelayMultiplier ())
857+ .setInitialIntervalMillis (
858+ (int ) STREAMING_RETRY_SETTINGS .getInitialRetryDelay ().toMillis ())
859+ .setMaxIntervalMillis ((int ) STREAMING_RETRY_SETTINGS .getMaxRetryDelay ().toMillis ())
860+ .setMaxElapsedTimeMillis (Integer .MAX_VALUE ) // Prevent Backoff.STOP from getting returned.
861+ .build ();
862+ }
863+
864+ private static void backoffSleep (Context context , BackOff backoff ) throws SpannerException {
865+ backoffSleep (context , nextBackOffMillis (backoff ));
866+ }
867+
868+ private static long nextBackOffMillis (BackOff backoff ) throws SpannerException {
869+ try {
870+ return backoff .nextBackOffMillis ();
871+ } catch (IOException e ) {
872+ throw newSpannerException (ErrorCode .INTERNAL , e .getMessage (), e );
873+ }
874+ }
875+
876+ private static void backoffSleep (Context context , long backoffMillis ) throws SpannerException {
877+ tracer
878+ .getCurrentSpan ()
879+ .addAnnotation (
880+ "Backing off" ,
881+ ImmutableMap .of ("Delay" , AttributeValue .longAttributeValue (backoffMillis )));
882+ final CountDownLatch latch = new CountDownLatch (1 );
883+ final Context .CancellationListener listener =
884+ new Context .CancellationListener () {
885+ @ Override
886+ public void cancelled (Context context ) {
887+ // Wakeup on cancellation / DEADLINE_EXCEEDED.
888+ latch .countDown ();
889+ }
890+ };
891+
892+ context .addListener (listener , DirectExecutor .INSTANCE );
893+ try {
894+ if (backoffMillis == BackOff .STOP ) {
895+ // Highly unlikely but we handle it just in case.
896+ backoffMillis = STREAMING_RETRY_SETTINGS .getMaxRetryDelay ().toMillis ();
897+ }
898+ if (latch .await (backoffMillis , TimeUnit .MILLISECONDS )) {
899+ // Woken by context cancellation.
900+ throw newSpannerExceptionForCancellation (context , null );
901+ }
902+ } catch (InterruptedException interruptExcept ) {
903+ throw newSpannerExceptionForCancellation (context , interruptExcept );
904+ } finally {
905+ context .removeListener (listener );
906+ }
907+ }
908+
909+ private enum DirectExecutor implements Executor {
910+ INSTANCE ;
911+
912+ @ Override
913+ public void execute (Runnable command ) {
914+ command .run ();
915+ }
916+ }
917+
844918 abstract CloseableIterator <PartialResultSet > startStream (@ Nullable ByteString resumeToken );
845919
846920 @ Override
@@ -915,9 +989,9 @@ protected PartialResultSet computeNext() {
915989 try (Scope s = tracer .withSpan (span )) {
916990 long delay = e .getRetryDelayInMillis ();
917991 if (delay != -1 ) {
918- SpannerImpl . backoffSleep (context , delay );
992+ backoffSleep (context , delay );
919993 } else {
920- SpannerImpl . backoffSleep (context , backOff );
994+ backoffSleep (context , backOff );
921995 }
922996 }
923997 continue ;
0 commit comments