Uh oh!
There was an error while loading. Please reload this page.
ServerStreaming retries - #449
Conversation
Codecov Report
@@ Coverage Diff @@## master #449 +/- ##
============================================
- Coverage 71.7% 71.13% -0.57% - Complexity 751 787 +36
============================================
Files 155 159 +4 Lines 3354 3707 +353 Branches 261 300 +39 ============================================
+ Hits 2405 2637 +232 - Misses 847 945 +98 - Partials 102 125 +23
Continue to review full report at Codecov.
|
7016b42 to
c665770Compare| @@ -0,0 +1,405 @@ | |||
| /* | |||
| * Copyright 2017, Google LLC All rights reserved. | |||
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| * | ||
| * <p>This class includes settings that are applicable to all server streaming calls | ||
| * <p>This class includes settings that are applicable to all server streaming calls, which | ||
| * currently is retries. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| * <p>Retry configuration allows for the stream to be restarted and resumed. it is composed of 3 | ||
| * parts: the retryable codes, the retry settings and the stream tracker. The retryable codes | ||
| * indicate which codes cause a retry to occur, the retry settings configure the retry logic when | ||
| * the retry needs to happen and the stream tracker composes the request to resume the stream. To |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| * parts: the retryable codes, the retry settings and the stream tracker. The retryable codes | ||
| * indicate which codes cause a retry to occur, the retry settings configure the retry logic when | ||
| * the retry needs to happen and the stream tracker composes the request to resume the stream. To | ||
| * turn off retries, set the retryable codes needs to be set to the empty set. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| * <p>Implementations don't have to be threadsafe because all of the calls will be serialized. | ||
| */ | ||
| @BetaApi("The surface for streaming is not stable yet and may change in the future.") | ||
| public interface StreamTracker<RequestT, ResponseT> { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| /** | ||
| * Checks if this stream has over run any of its timeouts and cancels it if it does. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| clientContext.getClock(), | ||
| callSettings.getTimeoutCheckInterval(), | ||
| callSettings.getIdleTimeout()); | ||
| watchdog.start(); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| /** Wraps the target observer with timing constraints. */ | ||
| public ResponseObserver<ResponseT> wrapWithTimeout( |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| Preconditions.checkArgument(count > 0, "count must be > 0"); | ||
| Preconditions.checkState(!autoAutoFlowControl, "Auto flow control is enabled"); | ||
| // Only reset the request water mark if there is no outstanding requests. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| @Override | ||
| public void onResponse(ResponseT response) { | ||
| synchronized (lock) { | ||
| state = State.DELIVERING; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
842f2cd to
429d445Compareigorbernstein2
commented
Jan 4, 2018
PTAL, all feedback should be addressed |
| // NOTE: This creates a Watchdog per streaming API method. Ideally, there should only be a | ||
| // single Watchdog for the entire process, however that change would be fairly invasive and | ||
| // the cost of multiple Watchdogs is fairly small, they all use the same executor. If this |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| import com.google.api.core.BetaApi; | ||
| /** | ||
| * This is part of the server streaming retry api. It's implementers are responsible for tracking |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| /** Creates a new instance of this StreamResumptionStrategy without accumulated state */ | ||
| StreamResumptionStrategy<RequestT, ResponseT> createNew(); | ||
| /** Called by the {@link RetryingServerStream} to notify of a successfully received response. */ |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| /** | ||
| * Called when a stream needs to be restarted, the implementation should generate a request that | ||
| * will yield a stream whose first response would come right after the last response in | ||
| * onProgress. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
garrettjonesgoogle
commented
Jan 5, 2018
@vam-google could you take a look too since you did the unary retry implementation? |
igorbernstein2
commented
Jan 7, 2018
All feedback addressed |
ce5390d to
7349b38Compareigorbernstein2
commented
Jan 9, 2018
rebased |
7367c0c to
c1e5f32Compare
vam-google
left a comment
There was a problem hiding this comment.
(Assuming that Reframer portion of streaming api is moved fom gax to google-cloud-java directly).
Also please check ReframingResponseObserver's thread safety implementation, some of @GuardedBy fields look not guarded by lock, like numPending on L136.
| import java.util.concurrent.TimeUnit; | ||
| import org.threeten.bp.Duration; | ||
| /** |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| private final ResponseObserver<ResponseT> outerObserver; | ||
| // Start state | ||
| private boolean autoFlowControl = true; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| final StreamController localInnerController; | ||
| synchronized (lock) { | ||
| int maxInc = Integer.MAX_VALUE - pendingRequests; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| pendingRequests = Integer.MAX_VALUE; | ||
| } | ||
| timedAttemptSettings = retryAlgorithm.createFirstAttempt(); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| * | ||
| * @see ResponseObserver#onError(Throwable) | ||
| */ | ||
| private void onAttemptError(Throwable t) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| Duration checkInterval, | ||
| Duration idleTimeout) { | ||
| Preconditions.checkNotNull(executor, "executor can't be null"); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| WatchdogStream(ResponseObserver<ResponseT> responseObserver, Duration waitTimeout) { | ||
| this.waitTimeout = waitTimeout; | ||
| this.outerResponseObserver = responseObserver; | ||
| this.lastActivityAt = clock.millisTime(); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| // Increment the request count without overflow | ||
| int maxIncrement = Integer.MAX_VALUE - pendingCount; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| /** The marker exception thrown when a timeout is exceeded. */ | ||
| public static class IdleConnectionException extends ApiException { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| } | ||
| public static final StatusCode LOCAL_ABORTED_STATUS_CODE = |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
igorbernstein2
commented
Jan 26, 2018
I've implemented most feedback, still outstanding on my part:
Things that I addressed, but don't think need code changes:
Discussion of pendingRequestCount and ServerStreamingCallable call method naming should be moved to a separate issue |
igorbernstein2
commented
Feb 1, 2018
Closing in favor of #463 |
Add retries and resume support. This is last part of #433
This PR consists of 3 parts split by commit:
Conceptually, retries for streams can be split into 2 phases: before the first response is observed and after. Before the first response is observed, retries can be handled automatically similar to unary RPCs. After the first response is observed, manual work needs to done to recalculate the retry request. This work is delegated to a StreamTracker, which is notified of incoming responses and can be asked to build a resume request on failure. Gax should offer out of box support for the first kind of retries and allow client developers to manually add support for resumes via handwritten code. I envision the workflow for client developers as: add retry codes & settings to GAPIC yaml to enable simple retries, then implement a StreamTracker and set it on the ServerStreamingCallSettings.
This PR also implements timeouts for RPCs. The existing concepts of RPC & total timeouts are extended: RPC timeout now limits the time interval between a consumer signaling demand for the next response via StreamController#request() and receiving a response in ResponseObserver#onResponse. Total timeout limits the total duration of the stream from the initial call() until the last onComplete/onError of the last retry attempt. Furthermore, the concept of idle timeout is added to streams with manual flow control, this limits the time between last observed consumer activity (the time between finishing processing a response in onResponse to the next call to request). This is meant to address the possibility of the caller forgetting to cancel a partially read stream.
Since GRPC does not implement the concept of RPC timeouts for streams, this PR implements it as a watchdog helper. The helper tracks activity per stream and schedules periodic garbage collection sweeps. So stream RPC timeouts are a lot looser than their unary counterparts. Furthermore, the helper needs to receive more information from its callers than is currently available: it needs to be able to accept both an rpc timeout and a total stream deadline. I couldn't figure out how to generalize this and extend the ApiContext to carry this information. So the watchdog helper steps outside the callable chain pattern and is implemented as a factory for ResponseObserver decorators.