Skip to content

Commit

Permalink
Merge pull request #1132 from batsura-sa/feature/deadline_property
Browse files Browse the repository at this point in the history
Feat(defaultRequestTimeout) add defaultRequestTimeout property for the client
  • Loading branch information
yidongnan authored Oct 27, 2024
2 parents abf7fb8 + c1b1ced commit ee810d1
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 1 deletion.
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 {
}

}

0 comments on commit ee810d1

Please sign in to comment.