Skip to content

Conversation

@karel-rehor
Copy link
Contributor

@karel-rehor karel-rehor commented Nov 12, 2025

Closes #301

Proposed Changes

  1. in the implementation queryData method, leave the QueryOptions argument unchanged. Use instead a local copy.

Explanation: The passed QueryOptions object might be reused, in which case mutating a field like Deadline in GrpcOptions could lead to reusing an already expired deadline in future calls.

  1. Add test to verify Deadline is ticking down.
  2. Add test to verify that a QueryOptions object has not been modified. N.B. in support of this test the clone method is overridden to make a deep copy of the original.

N.B. IDE reformatted indentations in the QueryOptionsTest.java file.

Checklist

  • CHANGELOG.md updated
  • Rebased/mergeable
  • A test has been added if appropriate
  • Tests pass
  • Commit messages are conventional
  • Sign CLA (if not already signed)

@codecov
Copy link

codecov bot commented Nov 12, 2025

Codecov Report

❌ Patch coverage is 75.00000% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.22%. Comparing base (4d74671) to head (e169db7).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ava/com/influxdb/v3/client/query/QueryOptions.java 50.00% 1 Missing and 4 partials ⚠️
...nfluxdb/v3/client/internal/InfluxDBClientImpl.java 87.50% 0 Missing and 2 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines 419 to 427
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about simplest solution?

Suggested change
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();

Copy link
Contributor Author

@karel-rehor karel-rehor Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 198 to 224
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this?

Copy link
Contributor Author

@karel-rehor karel-rehor Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I'll move it to a static method in the test class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx

Comment on lines 420 to 432
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));
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about something simple like:

Suggested change
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.");
}

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated logic based on further conversation.

@karel-rehor karel-rehor requested a review from bednar November 13, 2025 13:48
Comment on lines 439 to 440
// Assertions.assertThat(thrown).isInstanceOf(FlightRuntimeException.class);
// Assertions.assertThat(thrown.getMessage()).matches(".*deadline.*exceeded.*");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove or uncomment this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

Copy link
Member

@bednar bednar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update JavaDoc in GrpcCallIOptions.Builder.withDeadline that the preferred way is set deadline globally.

@karel-rehor karel-rehor requested a review from bednar November 13, 2025 14:32
Copy link
Member

@bednar bednar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

@karel-rehor karel-rehor merged commit 5e559a7 into main Nov 13, 2025
7 of 9 checks passed
@karel-rehor karel-rehor deleted the fix/CallOptionDeadlineTimeout branch November 13, 2025 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Grpc CallOption Deadline appears to be set to 0

3 participants