fix: call option deadline timeout unchanged in query method call#310
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #310 +/- ##
==========================================
- Coverage 87.48% 87.22% -0.26%
==========================================
Files 20 20
Lines 1262 1284 +22
Branches 204 213 +9
==========================================
+ Hits 1104 1120 +16
- Misses 74 75 +1
- Partials 84 89 +5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| GrpcCallOptions grpcCallOptionsCopy = options.grpcCallOptions(); | ||
| if (grpcCallOptionsCopy.getDeadline() == null && config.getQueryTimeout() != null) { | ||
| grpcCallOptionsCopy = new GrpcCallOptions.Builder() | ||
| .fromGrpcCallOptions(grpcCallOptionsCopy) | ||
| .withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)) | ||
| .build(); | ||
| } | ||
|
|
||
| CallOption[] callOptions = grpcCallOptionsCopy.getCallOptions(); |
There was a problem hiding this comment.
What about simplest solution?
| GrpcCallOptions grpcCallOptionsCopy = options.grpcCallOptions(); | |
| if (grpcCallOptionsCopy.getDeadline() == null && config.getQueryTimeout() != null) { | |
| grpcCallOptionsCopy = new GrpcCallOptions.Builder() | |
| .fromGrpcCallOptions(grpcCallOptionsCopy) | |
| .withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)) | |
| .build(); | |
| } | |
| CallOption[] callOptions = grpcCallOptionsCopy.getCallOptions(); | |
| GrpcCallOptions.Builder builder = new GrpcCallOptions.Builder() | |
| .fromGrpcCallOptions(options.grpcCallOptions()); | |
| if (config.getQueryTimeout() != null) { | |
| builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)) | |
| } | |
| CallOption[] callOptions = builder.build().getCallOptions(); |
There was a problem hiding this comment.
I think we still need to check in line 421 whether grpcCallOptions in the passed options object, doesn't already have a Deadline defined. options.grpcCallOptions().getDeadline() passed in a single call should supersede config.getQueryTimeout() . But, I'll try something similar now locally.
But, yes I think we can reduce the complexity by getting rid of grpcCallOptionsCopy which was suggested by copilot.
| protected QueryOptions clone() { | ||
| QueryOptions clone; | ||
| HashMap<String, String> cloneHeaders = new HashMap<>(this.headers); | ||
| for (String key : this.headers.keySet()) { | ||
| cloneHeaders.put(key, this.headers.get(key)); | ||
| } | ||
| try { | ||
| clone = (QueryOptions) super.clone(); | ||
| } catch (final CloneNotSupportedException e) { | ||
| clone = new QueryOptions(this.database, this.queryType, cloneHeaders); | ||
| } | ||
| if (this.grpcCallOptions != null) { | ||
| GrpcCallOptions.Builder grpcOptsBuilder = new GrpcCallOptions.Builder(); | ||
| if (this.grpcCallOptions.getDeadline() != null) { | ||
| grpcOptsBuilder.withDeadline(this.grpcCallOptions.getDeadline().offset(0, TimeUnit.MILLISECONDS)); | ||
| } | ||
| grpcOptsBuilder.withExecutor(this.grpcCallOptions.getExecutor()); | ||
| grpcOptsBuilder.withCompressorName(this.grpcCallOptions.getCompressorName()); | ||
| if (this.grpcCallOptions.getWaitForReady() != null | ||
| && this.grpcCallOptions.getWaitForReady()) { | ||
| grpcOptsBuilder.withWaitForReady(); | ||
| } | ||
| grpcOptsBuilder.withMaxInboundMessageSize(this.grpcCallOptions.getMaxInboundMessageSize()); | ||
| grpcOptsBuilder.withMaxOutboundMessageSize(this.grpcCallOptions.getMaxOutboundMessageSize()); | ||
| clone.grpcCallOptions = grpcOptsBuilder.build(); | ||
| } | ||
| return clone; |
There was a problem hiding this comment.
This is mainly for the test queryOptionsUnchangedByCall(). A deep copy of QueryOptions from before when the query() call is made is needed for comparison to the QueryOptions object passed to query() after the call is made, to make sure that the query call did not mutate the passed QueryOptions object in any way.
There was a problem hiding this comment.
If this is only for testing, we should remove it. It will be difficult to maintain, because whenever a new property is added to GrpcCallOptions, we would have to update this as well.
There was a problem hiding this comment.
OK. I'll move it to a static method in the test class.
| if (options.grpcCallOptions().getDeadline() == null) { | ||
| if (config.getQueryTimeout() != null) { | ||
| builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)); | ||
| } | ||
| } else if (options.grpcCallOptions().getDeadline().timeRemaining(TimeUnit.NANOSECONDS) <= 0) { | ||
| LOG.warning("Received impractical gRPC call options deadline " | ||
| + options.grpcCallOptions().getDeadline()); | ||
| if (config.getQueryTimeout() != null) { | ||
| LOG.warning("Using configuration query timeout " | ||
| + config.getQueryTimeout() + " as a replacement."); | ||
| builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)); | ||
| } | ||
| } |
There was a problem hiding this comment.
What about something simple like:
| if (options.grpcCallOptions().getDeadline() == null) { | |
| if (config.getQueryTimeout() != null) { | |
| builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)); | |
| } | |
| } else if (options.grpcCallOptions().getDeadline().timeRemaining(TimeUnit.NANOSECONDS) <= 0) { | |
| LOG.warning("Received impractical gRPC call options deadline " | |
| + options.grpcCallOptions().getDeadline()); | |
| if (config.getQueryTimeout() != null) { | |
| LOG.warning("Using configuration query timeout " | |
| + config.getQueryTimeout() + " as a replacement."); | |
| builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)); | |
| } | |
| } | |
| if (config.getQueryTimeout() != null) { | |
| builder.withDeadline(Deadline.after(config.getQueryTimeout().toMillis(), TimeUnit.MILLISECONDS)); | |
| } else if (options.grpcCallOptions().getDeadline() != null) { | |
| LOG.warning("No query timeout configured in ClientConfig, but gRPC call options contains a deadline " | |
| + options.grpcCallOptions().getDeadline() | |
| + ". This may lead to unexpected behavior - use setting query timeout in ClientConfig."); | |
| } |
?
There was a problem hiding this comment.
Updated logic based on further conversation.
… QueryOptions.clone()
| // Assertions.assertThat(thrown).isInstanceOf(FlightRuntimeException.class); | ||
| // Assertions.assertThat(thrown.getMessage()).matches(".*deadline.*exceeded.*"); |
There was a problem hiding this comment.
Please remove or uncomment this.
bednar
left a comment
There was a problem hiding this comment.
Update JavaDoc in GrpcCallIOptions.Builder.withDeadline that the preferred way is set deadline globally.
Closes #301
Proposed Changes
queryDatamethod, leave theQueryOptionsargument unchanged. Use instead a local copy.Explanation: The passed
QueryOptionsobject might be reused, in which case mutating a field likeDeadlineinGrpcOptionscould lead to reusing an already expired deadline in future calls.QueryOptionsobject has not been modified. N.B. in support of this test theclonemethod is overridden to make a deep copy of the original.N.B. IDE reformatted indentations in the QueryOptionsTest.java file.
Checklist