Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(defaultRequestTimeout) add defaultRequestTimeout property for the client #1132

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
28b073d
Feat(deadline) add deadline property for client
Aug 3, 2024
0da4da7
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 4, 2024
3b295c7
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 4, 2024
e14b9fe
Feat(deadline) changed zero deadline behavior, javadoc corrections
Sep 4, 2024
7505c7d
Feat(deadline) removed wasted DeadlineSetupClientInterceptor calls in…
Sep 4, 2024
42ebf40
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 5, 2024
809bc5b
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 5, 2024
a8bbe07
Feat(deadline) replaced bean deadlineStubTransformer with deadlineGrp…
Sep 5, 2024
5a219a7
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 5, 2024
2492865
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 5, 2024
dd26ff4
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 5, 2024
f365af7
Feat(deadline) add a test showing that the manually configured deadli…
Sep 5, 2024
650a978
Feat(timeout) changed the name of deadline property to timeout.
Sep 6, 2024
0ebcc0a
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 6, 2024
6ab4d0d
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 6, 2024
e6403b6
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 6, 2024
20dc197
Update grpc-client-spring-boot-starter/src/main/resources/META-INF/ad…
batsura-sa Sep 6, 2024
9db6998
Feat(timeout) constructor and test changed.
Sep 6, 2024
102d09e
Feat(timeout) changed the name of timeout property to defaultRequestT…
Sep 6, 2024
da2f32c
Update grpc-client-spring-boot-starter/src/main/java/net/devh/boot/gr…
batsura-sa Sep 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2016-2024 The gRPC-Spring Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.devh.boot.grpc.client.autoconfigure;

import static java.util.Objects.requireNonNull;

import java.time.Duration;

import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.grpc.ClientInterceptor;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.channelfactory.GrpcChannelConfigurer;
import net.devh.boot.grpc.client.config.GrpcChannelsProperties;
import net.devh.boot.grpc.client.interceptor.DefaultRequestTimeoutSetupClientInterceptor;

/**
* The default request timeout autoconfiguration for the client.
*
* <p>
* You can disable this config by using:
* </p>
*
* <pre>
* <code>@ImportAutoConfiguration(exclude = GrpcClientDefaultRequestTimeoutAutoConfiguration.class)</code>
* </pre>
*
* @author Sergei Batsura ([email protected])
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(GrpcClientAutoConfiguration.class)
public class GrpcClientDefaultRequestTimeoutAutoConfiguration {

/**
* Creates a {@link GrpcChannelConfigurer} bean applying the default request timeout from config to each new call
* using a {@link ClientInterceptor}.
*
* @param props The properties for timeout configuration.
* @return The GrpcChannelConfigurer bean with interceptor if timeout is configured.
* @see DefaultRequestTimeoutSetupClientInterceptor
*/
@Bean
GrpcChannelConfigurer timeoutGrpcChannelConfigurer(final GrpcChannelsProperties props) {
requireNonNull(props, "properties");

return (channel, name) -> {
Duration timeout = props.getChannel(name).getDefaultRequestTimeout();
if (timeout != null && timeout.toMillis() > 0L) {
channel.intercept(new DefaultRequestTimeoutSetupClientInterceptor(timeout));
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

import io.grpc.CallOptions;
import io.grpc.LoadBalancerRegistry;
import io.grpc.ManagedChannelBuilder;
import io.grpc.NameResolverProvider;
Expand Down Expand Up @@ -118,6 +119,35 @@ public void setAddress(final String address) {
this.address = address == null ? null : URI.create(address);
}

// --------------------------------------------------
// defaultRequestTimeout
// --------------------------------------------------

private Duration defaultRequestTimeout = null;

/**
* Gets the default request timeout for each new call.
*
* @return The default request timeout or null
* @see #setDefaultRequestTimeout(Duration)
*/
public Duration getDefaultRequestTimeout() {
return this.defaultRequestTimeout;
}

/**
* Set the default request timeout duration for new calls (on a per call basis). By default and if zero value is
* configured, the timeout will not be used. The default request timeout will be ignored, if a deadline has been
* applied manually.
*
* @param defaultRequestTimeout the default request timeout or null.
*
* @see CallOptions#withDeadlineAfter(long, TimeUnit)
*/
public void setDefaultRequestTimeout(Duration defaultRequestTimeout) {
this.defaultRequestTimeout = defaultRequestTimeout;
}

// --------------------------------------------------
// defaultLoadBalancingPolicy
// --------------------------------------------------
Expand Down Expand Up @@ -480,6 +510,9 @@ public void copyDefaultsFrom(final GrpcChannelProperties config) {
if (this.address == null) {
this.address = config.address;
}
if (this.defaultRequestTimeout == null) {
this.defaultRequestTimeout = config.defaultRequestTimeout;
}
if (this.defaultLoadBalancingPolicy == null) {
this.defaultLoadBalancingPolicy = config.defaultLoadBalancingPolicy;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2016-2024 The gRPC-Spring Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.devh.boot.grpc.client.interceptor;

import static java.util.Objects.requireNonNull;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.MethodDescriptor;
import lombok.extern.slf4j.Slf4j;

/**
* A client interceptor configuring the default request timeout / deadline for each call.
*
* @author Sergei Batsura ([email protected])
*/
@Slf4j
public class DefaultRequestTimeoutSetupClientInterceptor implements ClientInterceptor {

private final Duration defaultRequestTimeout;

public DefaultRequestTimeoutSetupClientInterceptor(Duration defaultRequestTimeout) {
this.defaultRequestTimeout = requireNonNull(defaultRequestTimeout, "defaultRequestTimeout");
}

@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
final MethodDescriptor<ReqT, RespT> method,
final CallOptions callOptions,
final Channel next) {

if (callOptions.getDeadline() == null) {
return next.newCall(method,
callOptions.withDeadlineAfter(defaultRequestTimeout.toMillis(), TimeUnit.MILLISECONDS));
} else {
return next.newCall(method, callOptions);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@
"description": "Connection timeout at application startup. If set to a positive duration instructs a client to connect to GRPC-endpoint when GRPC stub is created.",
"defaultValue": 0
},
{
"name": "grpc.client.GLOBAL.defaultRequestTimeout",
"type": "java.time.Duration",
"sourceType": "net.devh.boot.grpc.client.config.GrpcChannelProperties",
"description": "The default timeout is applied to each new call. By default, and if a zero value is configured, the timeout will not be set. The default timeout will be ignored if a deadline has been set manually."
},
{
"name": "grpc.client.GLOBAL.security.authority-override",
"type": "java.lang.String",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ net.devh.boot.grpc.client.autoconfigure.GrpcClientHealthAutoConfiguration
net.devh.boot.grpc.client.autoconfigure.GrpcClientMicrometerTraceAutoConfiguration
net.devh.boot.grpc.client.autoconfigure.GrpcClientSecurityAutoConfiguration
net.devh.boot.grpc.client.autoconfigure.GrpcDiscoveryClientAutoConfiguration
net.devh.boot.grpc.client.autoconfigure.GrpcClientDefaultRequestTimeoutAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.springframework.context.annotation.Configuration;

import net.devh.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration;
import net.devh.boot.grpc.client.autoconfigure.GrpcClientDefaultRequestTimeoutAutoConfiguration;
import net.devh.boot.grpc.common.autoconfigure.GrpcCommonCodecAutoConfiguration;
import net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration;
import net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration;
Expand All @@ -28,7 +29,7 @@
@Configuration
@ImportAutoConfiguration({GrpcCommonCodecAutoConfiguration.class, GrpcServerAutoConfiguration.class,
GrpcServerFactoryAutoConfiguration.class, GrpcServerSecurityAutoConfiguration.class,
GrpcClientAutoConfiguration.class})
GrpcClientAutoConfiguration.class, GrpcClientDefaultRequestTimeoutAutoConfiguration.class})
public class BaseAutoConfiguration {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2016-2024 The gRPC-Spring Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.devh.boot.grpc.test.setup;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import io.grpc.internal.testing.StreamRecorder;
import io.grpc.stub.StreamObserver;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.config.GrpcChannelProperties;
import net.devh.boot.grpc.test.config.BaseAutoConfiguration;
import net.devh.boot.grpc.test.config.ServiceConfiguration;
import net.devh.boot.grpc.test.proto.SomeType;

/**
* These tests check the property {@link GrpcChannelProperties#getDefaultRequestTimeout()}}.
*/
public class DefaultRequestTimeoutSetupTests {

@Slf4j
@SpringBootTest(properties = {
"grpc.client.GLOBAL.address=localhost:9090",
"grpc.client.GLOBAL.defaultRequestTimeout=1s",
"grpc.client.GLOBAL.negotiationType=PLAINTEXT",
})
@SpringJUnitConfig(classes = {ServiceConfiguration.class, BaseAutoConfiguration.class})
static class DefaultRequestTimeoutSetupTest extends AbstractSimpleServerClientTest {

@Test
@SneakyThrows
@DirtiesContext
void testServiceStubTimeoutEnabledAndSuccessful() {
log.info("--- Starting test with unsuccessful and than successful call ---");
final StreamRecorder<SomeType> streamRecorder1 = StreamRecorder.create();
this.testServiceStub.echo(streamRecorder1);
assertThrows(ExecutionException.class, () -> streamRecorder1.firstValue().get());

final StreamRecorder<SomeType> streamRecorder2 = StreamRecorder.create();
StreamObserver<SomeType> echo2 = testServiceStub.echo(streamRecorder2);
echo2.onNext(SomeType.getDefaultInstance());
assertNull(streamRecorder2.getError());
assertNotNull(streamRecorder2.firstValue().get().getVersion());
log.info("--- Test completed --- ");
}

@Test
@SneakyThrows
@DirtiesContext
void testServiceStubManuallyConfiguredDeadlineTakesPrecedenceOfTheConfigOne() {
log.info(
"--- Starting test that manually configured deadline takes precedence of the config default request timeout ---");
final StreamRecorder<SomeType> streamRecorder = StreamRecorder.create();
StreamObserver<SomeType> echo =
this.testServiceStub.withDeadlineAfter(5L, TimeUnit.SECONDS).echo(streamRecorder);
TimeUnit.SECONDS.sleep(2);
echo.onNext(SomeType.getDefaultInstance());
assertNull(streamRecorder.getError());
assertNotNull(streamRecorder.firstValue().get().getVersion());
log.info("--- Test completed --- ");
}
}

@Slf4j
@SpringBootTest(properties = {
"grpc.client.GLOBAL.address=localhost:9090",
"grpc.client.GLOBAL.defaultRequestTimeout=0s",
"grpc.client.GLOBAL.negotiationType=PLAINTEXT",
})
@SpringJUnitConfig(classes = {ServiceConfiguration.class, BaseAutoConfiguration.class})
static class ZeroDefaultRequestTimeoutSetupTest extends AbstractSimpleServerClientTest {
}

}
Loading