This repository was archived by the owner on Oct 29, 2026. It is now read-only.
Repository navigation
fix: Unary Callables Deadline values respect the TotalTimeout in RetrySettings #1603
Merged
Merged
Changes from all commits
Commits
Show all changes
143 commits
Select commit
Hold shift + click to select a range
dca83d7
chore: Add retry test
lqiu96 45c0765
chore: Check the timeout for unary callables
lqiu96 7862762
chore: Fix tests
lqiu96 7f5622f
chore: Address code smell
lqiu96 94b5017
chore: Add tests for DEADLINE_EXCEEDED
lqiu96 608d44b
chore: Add tests for Server-side streaming
lqiu96 0c1782f
chore: Start the timeout after http request invocation
lqiu96 f75f67e
chore: Fix format issues
lqiu96 7c4c841
chore: Remove Instant calculation with System clock
lqiu96 3668093
Merge branch 'main' into main-showcase_retries
lqiu96 d440b8e
chore: Add ITRetry test cases
lqiu96 bbf7f4f
chore: Fix the Retry showcase test
lqiu96 bee8c0e
chore: Fix the Retry showcase test
lqiu96 73882a7
chore: Convert duration to nanos
lqiu96 b0d83b9
chore: Set the readTimeout min to 20s
lqiu96 fc5cf13
chore: Add sucessful retry tests cases
lqiu96 2f1bc8a
chore: Add comments to timeout
lqiu96 ead0eb9
chore: Update the connect timeout
lqiu96 ca0770b
chore: Refactor timeout logic
lqiu96 21ed291
chore: Fix format issues
lqiu96 bb39513
chore: Add logic for deadlineScheduler
lqiu96 fab86be
chore: Fix format issues
lqiu96 95ef1a1
chore: Update logic
lqiu96 25190ec
chore: Update comment
lqiu96 f59de69
Merge branch 'main' into main-showcase_retries
lqiu96 133af94
chore: Update showcase test
lqiu96 ff5108f
chore: Fix format issues
lqiu96 e800373
chore: Fix logic
lqiu96 d757c05
Merge branch 'main' into main-showcase_retries
lqiu96 f139797
chore: Do not disconnect the connection
lqiu96 e1d12c5
chore: Disconnect after end
lqiu96 8ee7513
chore: Resolve steam close error
lqiu96 bed960e
chore: Fix disconnect logic
lqiu96 5c5eaec
chore: Fix disconnect logic
lqiu96 ab93117
chore: Update CI
lqiu96 f3d19b6
chore: Fix native test
lqiu96 1e38e5f
chore: Revert changes
lqiu96 5a10f4c
chore: try with rpc timeout 100ms
lqiu96 9b877ac
chore: Fix format issues
lqiu96 eff3513
chore: Re-run delivery loop with deadlineschedule priority
lqiu96 1d1dffa
chore: Check for timeoutExceeded
lqiu96 887f3bb
chore: Do not send message is time exceeded
lqiu96 102ab99
chore: Fix format issues
lqiu96 3f4b9a5
chore: Add timeout for tests
lqiu96 dffe194
chore: Fix format issues
lqiu96 47c8da6
chore: Refactor trailer logic
lqiu96 e5ccddd
chore: Refactor trailer logic
lqiu96 fdf6c63
chore: Rename variables
lqiu96 64b2c77
chore: Increase the wait to 1s
lqiu96 c5c2f54
chore: Fix format issues
lqiu96 d0893ea
chore: Set closed var as volatile
lqiu96 1281ab5
chore: Update logic for onClose
lqiu96 1ae5d3d
chore: Attempt with longer timeout
lqiu96 36e3788
chore: Empty commit
lqiu96 452fc97
chore: Fix format issues
lqiu96 0ad9442
chore: Trigger deliver loop instead of notifyListeners
lqiu96 0ad7a4d
chore: Remove variable
lqiu96 a769ee1
chore: Remove variable
lqiu96 65e9e67
chore: Fix close logic
lqiu96 0b926d5
chore: Revert graalvm ci
lqiu96 6b70a50
chore: Use 2s as delay
lqiu96 9af022a
chore: Update to 5s delay
lqiu96 66c757c
Merge branch 'main' into main-showcase_retries
lqiu96 fc59f98
chore: Add comments for timeout method
lqiu96 b773f89
chore: Use deliver loop in timeout
lqiu96 b571b6e
chore: Run matrix jobs sequentially
lqiu96 f97e7c0
chore: Fix format issues
lqiu96 1aec63c
chore: Fix format issues
lqiu96 de5927d
chore: Increase the wait to 10s
lqiu96 6a337c5
chore: Use 110ms delay
lqiu96 8be44c8
chore: Set delay to be 30s
lqiu96 42dd7ed
chore: Fix format issues
lqiu96 1de756c
chore: Log the onClose message
lqiu96 8756e27
chore: Remove localRunnable
lqiu96 51df7ea
chore: Fix format issues
lqiu96 a9ff512
chore: Lower the retry amounts
lqiu96 8f1627e
chore: Lower the retry amounts
lqiu96 becbc25
chore: Fix shouldRetry logic
lqiu96 c1f609f
chore: Log results of shouldRetry
lqiu96 be1f9fb
chore: Ignore other retry tests
lqiu96 490ccc1
chore: Add more logging
lqiu96 3a9bdfd
chore: Fix shouldRetry logic
lqiu96 87a41dc
chore: Remove small optimization
lqiu96 da26a56
chore: Temp ignore tests
lqiu96 0978549
chore: Temp ignore tests
lqiu96 93c8fdc
chore: Add more logging
lqiu96 2a9d2df
chore: revert back to checking for negative duration
lqiu96 a517d95
chore: Revert ignored test
lqiu96 0f8bbc8
chore: Fix logging
lqiu96 c1f914f
chore: Log timeout
lqiu96 6bb2d04
chore: Set min RPC timeout to be 1ms
lqiu96 3da7937
chore: Update the retry algorithms
lqiu96 d28b87f
chore: Clean up the algoritms
lqiu96 7c316a1
chore: Uncomment out ITRetry tests
lqiu96 ae3c2ec
chore: Refactor the retryAlgorithms
lqiu96 3fb78f7
chore: Add more comments
lqiu96 8cd3e3b
chore: Add in the parallel execution for ITs
lqiu96 b8704ff
chore: Add LRO showcase tests
lqiu96 eb6635a
chore: Fix format
lqiu96 0b76faf
chore: Remove deadline getters
lqiu96 acc3ee1
chore: Remove sonar changes
lqiu96 8b6445e
chore: Fix algorithm test
lqiu96 d458167
chore: Log the flaky test
lqiu96 09e7ff2
chore: Fix format
lqiu96 c20ee95
chore: Check for rpcTimeout being zero or negative
lqiu96 6552dbe
chore: Fix tests
lqiu96 0454e02
chore: Fix format issues
lqiu96 a1dcfdd
chore: Remove unused code
lqiu96 f9afed4
chore: Update comment for RetryAlgorithm
lqiu96 1d5c00a
chore: Fix format issues
lqiu96 a7983d3
chore: Use millis for timeout
lqiu96 15448d2
chore: Await termination for clients
lqiu96 924f7a0
chore: Fix format issues
lqiu96 f4ba8af
chore: Update LRO first call timeout value
lqiu96 a305123
chore: Update LRO test names
lqiu96 6e35172
chore: Remove the parallel showcase tests
lqiu96 d4bc792
chore: Add showcase sequence tests for retries
lqiu96 ccdf1e2
chore: Add showcase sequence tests for retries
lqiu96 561ffa3
chore: Update retry test name
lqiu96 3607a30
chore: Fix typos
lqiu96 cb34160
chore: Fix server streaming callable test
lqiu96 cf5ba07
chore: Clean up tests
lqiu96 07ecd7a
chore: Remove retry tests
lqiu96 b4812e3
chore: Fix format issues
lqiu96 ed6b53c
chore: Address PR comments
lqiu96 2095745
chore: Update java.time.Duration import
lqiu96 a8e3153
chore: Update values for LRO showcase test
lqiu96 760f5fe
chore: Update values for LRO showcase test
lqiu96 d890dee
chore: Fix format issues
lqiu96 3086821
chore: Update variable name
lqiu96 084b592
chore: Convert shouldRetry logic to use milliseconds
lqiu96 145d084
chore: Remove jitter from tests
lqiu96 e9c1645
chore: Fix showcase test
lqiu96 600a2dc
chore: Log the attempt callable timeout
lqiu96 5d81e85
chore: Update LRO test case
lqiu96 c429d96
chore: Add logging
lqiu96 5527700
chore: Use millis for timeout
lqiu96 13881bb
chore: Address PR comments
lqiu96 c052a87
chore: Update to use TestClientInitializer class
lqiu96 069acc6
Merge branch 'main' into main-showcase_retries
lqiu96 6cc15fb
chore: Fix client initialize method names
lqiu96 e099ac1
chore: Add back public method
lqiu96 0a3cadf
chore: Add back public methods
lqiu96 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,15 +33,19 @@ | |
| import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; | ||
| import com.google.api.gax.httpjson.HttpRequestRunnable.ResultListener; | ||
| import com.google.api.gax.httpjson.HttpRequestRunnable.RunnableResult; | ||
| import com.google.api.gax.rpc.StatusCode; | ||
| import com.google.common.base.Preconditions; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.io.Reader; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.time.Duration; | ||
| import java.util.ArrayDeque; | ||
| import java.util.Queue; | ||
| import java.util.concurrent.CancellationException; | ||
| import java.util.concurrent.Executor; | ||
| import java.util.concurrent.ScheduledExecutorService; | ||
| import java.util.concurrent.TimeUnit; | ||
| import javax.annotation.Nullable; | ||
| import javax.annotation.concurrent.GuardedBy; | ||
|
|
||
|
|
@@ -88,6 +92,7 @@ final class HttpJsonClientCallImpl<RequestT, ResponseT> | |
| private final ApiMethodDescriptor<RequestT, ResponseT> methodDescriptor; | ||
| private final HttpTransport httpTransport; | ||
| private final Executor executor; | ||
| private final ScheduledExecutorService deadlineCancellationExecutor; | ||
|
|
||
| // | ||
| // Request-specific data (provided by client code) before we get a response. | ||
|
|
@@ -114,19 +119,21 @@ final class HttpJsonClientCallImpl<RequestT, ResponseT> | |
| private ProtoMessageJsonStreamIterator responseStreamIterator; | ||
|
|
||
| @GuardedBy("lock") | ||
| private boolean closed; | ||
| private volatile boolean closed; | ||
|
lqiu96 marked this conversation as resolved.
Resolved
|
||
|
|
||
| HttpJsonClientCallImpl( | ||
| ApiMethodDescriptor<RequestT, ResponseT> methodDescriptor, | ||
| String endpoint, | ||
| HttpJsonCallOptions callOptions, | ||
| HttpTransport httpTransport, | ||
| Executor executor) { | ||
| Executor executor, | ||
| ScheduledExecutorService deadlineCancellationExecutor) { | ||
| this.methodDescriptor = methodDescriptor; | ||
| this.endpoint = endpoint; | ||
| this.callOptions = callOptions; | ||
| this.httpTransport = httpTransport; | ||
| this.executor = executor; | ||
| this.deadlineCancellationExecutor = deadlineCancellationExecutor; | ||
| this.closed = false; | ||
| } | ||
|
|
||
|
|
@@ -161,6 +168,38 @@ public void start(Listener<ResponseT> responseListener, HttpJsonMetadata request | |
| this.listener = responseListener; | ||
| this.requestHeaders = requestHeaders; | ||
| } | ||
|
|
||
| // Use the timeout duration value instead of calculating the future Instant | ||
| // Only schedule the deadline if the RPC timeout has been set in the RetrySettings | ||
| Duration timeout = callOptions.getTimeout(); | ||
| if (timeout != null) { | ||
| // The future timeout value is guaranteed to not be a negative value as the | ||
| // RetryAlgorithm will not retry | ||
| long timeoutMs = timeout.toMillis(); | ||
| this.deadlineCancellationExecutor.schedule(this::timeout, timeoutMs, TimeUnit.MILLISECONDS); | ||
| } | ||
| } | ||
|
|
||
| // Notify the FutureListener that the there is a timeout exception from this RPC | ||
| // call (DEADLINE_EXCEEDED). For retrying RPCs, this code is returned for every attempt | ||
| // that exceeds the timeout. The RetryAlgorithm will check both the timing and code to | ||
| // ensure another attempt is made. | ||
| private void timeout() { | ||
| // There is a race between the deadline scheduler and response being returned from | ||
| // the server. The deadline scheduler has priority as it will clear out the pending | ||
| // notifications queue and add the DEADLINE_EXCEEDED event once it is able to obtain | ||
| // the lock. | ||
| synchronized (lock) { | ||
| close( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah the cancellation in the runnable doesn't seem like it's actually cancel's the runnable. I'll look into the possibility of being able to actually disconnect the connection instead of waiting for the socket timeout. |
||
| StatusCode.Code.DEADLINE_EXCEEDED.getHttpStatusCode(), | ||
| "Deadline exceeded", | ||
| new HttpJsonStatusRuntimeException( | ||
| StatusCode.Code.DEADLINE_EXCEEDED.getHttpStatusCode(), "Deadline exceeded", null), | ||
| true); | ||
| } | ||
|
|
||
| // trigger delivery loop if not already running | ||
| deliver(); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -260,9 +299,10 @@ private void deliver() { | |
| throw new InterruptedException("Message delivery has been interrupted"); | ||
| } | ||
|
|
||
| // All listeners must be called under delivery loop (but outside the lock) to ensure that no | ||
| // two notifications come simultaneously from two different threads and that we do not go | ||
| // indefinitely deep in the stack if delivery logic is called recursively via listeners. | ||
| // All listeners must be called under delivery loop (but outside the lock) to ensure that | ||
| // no two notifications come simultaneously from two different threads and that we do not | ||
| // go indefinitely deep in the stack if delivery logic is called recursively via | ||
| // listeners. | ||
| notifyListeners(); | ||
|
|
||
| // The synchronized block around message reading and cancellation notification processing | ||
|
|
@@ -302,7 +342,7 @@ private void deliver() { | |
| inDelivery = false; | ||
| break; | ||
| } else { | ||
| // We still have some stuff in notiticationTasksQueue so continue the loop, most | ||
| // We still have some stuff in notificationTasksQueue so continue the loop, most | ||
| // likely we will finally terminate on the next cycle. | ||
| continue; | ||
| } | ||
|
|
@@ -319,8 +359,8 @@ private void deliver() { | |
| // can do in such an unlikely situation (otherwise we would stay forever in the delivery | ||
| // loop). | ||
| synchronized (lock) { | ||
| // Close the call immediately marking it cancelled. If already closed close() will have no | ||
| // effect. | ||
| // Close the call immediately marking it cancelled. If already closed, close() will have | ||
| // no effect. | ||
| close(ex.getStatusCode(), ex.getMessage(), ex, true); | ||
| } | ||
| } | ||
|
|
@@ -352,7 +392,7 @@ private boolean consumeMessageFromStream() throws IOException { | |
| boolean allMessagesConsumed; | ||
| Reader responseReader; | ||
| if (methodDescriptor.getType() == MethodType.SERVER_STREAMING) { | ||
| // Lazily initialize responseStreamIterator in case if it is a server steraming response | ||
| // Lazily initialize responseStreamIterator in case if it is a server streaming response | ||
| if (responseStreamIterator == null) { | ||
| responseStreamIterator = | ||
| new ProtoMessageJsonStreamIterator( | ||
|
|
@@ -384,7 +424,7 @@ private boolean consumeMessageFromStream() throws IOException { | |
|
|
||
| @GuardedBy("lock") | ||
| private void close( | ||
| int statusCode, String message, Throwable cause, boolean terminateImmediatelly) { | ||
| int statusCode, String message, Throwable cause, boolean terminateImmediately) { | ||
| try { | ||
| if (closed) { | ||
| return; | ||
|
|
@@ -399,12 +439,12 @@ private void close( | |
| requestRunnable = null; | ||
| } | ||
|
|
||
| HttpJsonMetadata.Builder meatadaBuilder = HttpJsonMetadata.newBuilder(); | ||
| HttpJsonMetadata.Builder metadataBuilder = HttpJsonMetadata.newBuilder(); | ||
| if (runnableResult != null && runnableResult.getTrailers() != null) { | ||
| meatadaBuilder = runnableResult.getTrailers().toBuilder(); | ||
| metadataBuilder = runnableResult.getTrailers().toBuilder(); | ||
| } | ||
| meatadaBuilder.setException(cause); | ||
| meatadaBuilder.setStatusMessage(message); | ||
| metadataBuilder.setException(cause); | ||
| metadataBuilder.setStatusMessage(message); | ||
| if (responseStreamIterator != null) { | ||
| responseStreamIterator.close(); | ||
| } | ||
|
|
@@ -415,19 +455,19 @@ private void close( | |
| // onClose() suppresses all other pending notifications. | ||
| // there should be no place in the code which inserts something in this queue before checking | ||
| // the `closed` flag under the lock and refusing to insert anything if `closed == true`. | ||
| if (terminateImmediatelly) { | ||
| if (terminateImmediately) { | ||
| // This usually means we are cancelling the call before processing the response in full. | ||
| // It may happen if a user explicitly cancels the call or in response to an unexpected | ||
| // exception either from server or a call listener execution. | ||
| pendingNotifications.clear(); | ||
| } | ||
|
|
||
| pendingNotifications.offer( | ||
| new OnCloseNotificationTask<>(listener, statusCode, meatadaBuilder.build())); | ||
| new OnCloseNotificationTask<>(listener, statusCode, metadataBuilder.build())); | ||
|
|
||
| } catch (Throwable e) { | ||
| // suppress stream closing exceptions in favor of the actual call closing cause. This method | ||
| // should not throw, otherwise we may stuck in an infinite loop of exception processing. | ||
| // should not throw, otherwise we may be stuck in an infinite loop of exception processing. | ||
| } | ||
| } | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we try to reuse the
executorabove? I know it's not aScheduledExecutorServiceat this point, but the what's being passed in is from here, which is indeed aScheduledExecutorService, so we should be able to change the type and reuse it. I did a quick search and I don't think most of the change would be breaking, there is one public builder method that could be considered breaking.The reason we may want to reuse the existing executor is that we can leverage the existing client lifecycles for shutting down the executor, otherwise we need to explicitly shut down this new
deadlineCancellationExecutorsomewhere to prevent from resource leaking, which is missing from this PR currently. Reusing the existing executor may also have other issues like the ThreadPool is not large enough so new requests may have to wait for new thread. The bottom line is that we need to be careful with anything related to executors, so we have enough resource for large number of concurrent requests and we don't leak resource accidentally.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, makes sense. I'll take another look into how grpc handles the multiple executors. I'd imagine they would also possibly run into this issue.