diff --git a/.travis.yml b/.travis.yml index c81d28675a3..4a37defdafb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,6 @@ install: - pushd examples/example-hostname && ../gradlew build && popd - pushd examples/example-hostname && mvn verify && popd - pushd examples/example-tls && ../gradlew clean build && popd - - pushd examples/example-kotlin && ../gradlew build && popd - pushd examples/example-xds && ../gradlew build && popd before_script: diff --git a/COMPILING.md b/COMPILING.md index 46fe15c2d4a..02e55292c3b 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -47,7 +47,7 @@ The codegen plugin is C++ code and requires protobuf 3.0.0 or later. For Linux, Mac and MinGW: ``` -$ PROTOBUF_VERSION=3.11.0 +$ PROTOBUF_VERSION=3.12.0 $ curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-all-$PROTOBUF_VERSION.tar.gz $ tar xzf protobuf-all-$PROTOBUF_VERSION.tar.gz $ cd protobuf-$PROTOBUF_VERSION @@ -80,16 +80,16 @@ When building on Windows and VC++, you need to specify project properties for Gradle to find protobuf: ``` .\gradlew publishToMavenLocal ^ - -PvcProtobufInclude=C:\path\to\protobuf-3.11.0\src ^ - -PvcProtobufLibs=C:\path\to\protobuf-3.11.0\vsprojects\Release ^ + -PvcProtobufInclude=C:\path\to\protobuf-3.12.0\src ^ + -PvcProtobufLibs=C:\path\to\protobuf-3.12.0\vsprojects\Release ^ -PtargetArch=x86_32 ``` Since specifying those properties every build is bothersome, you can instead create ``\gradle.properties`` with contents like: ``` -vcProtobufInclude=C:\\path\\to\\protobuf-3.11.0\\src -vcProtobufLibs=C:\\path\\to\\protobuf-3.11.0\\vsprojects\\Release +vcProtobufInclude=C:\\path\\to\\protobuf-3.12.0\\src +vcProtobufLibs=C:\\path\\to\\protobuf-3.12.0\\vsprojects\\Release targetArch=x86_32 ``` diff --git a/README.md b/README.md index a652b14b9fe..1fc0b8e50ab 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/quickstart/java.html) or the more explanatory [gRPC basics](https://grpc.io/docs/tutorials/basic/java.html). -The [examples](https://github.com/grpc/grpc-java/tree/v1.28.1/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.28.1/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.30.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.30.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -42,37 +42,45 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.28.1 + 1.30.0 io.grpc grpc-protobuf - 1.28.1 + 1.30.0 io.grpc grpc-stub - 1.28.1 + 1.30.0 + + + org.apache.tomcat + annotations-api + 6.0.53 + provided ``` Or for Gradle with non-Android, add to your dependencies: ```gradle -implementation 'io.grpc:grpc-netty-shaded:1.28.1' -implementation 'io.grpc:grpc-protobuf:1.28.1' -implementation 'io.grpc:grpc-stub:1.28.1' +implementation 'io.grpc:grpc-netty-shaded:1.30.0' +implementation 'io.grpc:grpc-protobuf:1.30.0' +implementation 'io.grpc:grpc-stub:1.30.0' +compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.28.1' -implementation 'io.grpc:grpc-protobuf-lite:1.28.1' -implementation 'io.grpc:grpc-stub:1.28.1' +implementation 'io.grpc:grpc-okhttp:1.30.0' +implementation 'io.grpc:grpc-protobuf-lite:1.30.0' +implementation 'io.grpc:grpc-stub:1.30.0' +compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.28.1 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.30.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -102,9 +110,9 @@ For protobuf-based codegen integrated with the Maven build system, you can use protobuf-maven-plugin 0.6.1 - com.google.protobuf:protoc:3.11.0:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.28.1:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.30.0:exe:${os.detected.classifier} @@ -130,11 +138,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.11.0" + artifact = "com.google.protobuf:protoc:3.12.0" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.28.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.30.0' } } generateProtoTasks { diff --git a/RELEASING.md b/RELEASING.md index 9d45bd9f5fe..98ac3cc791e 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -46,8 +46,6 @@ $ VERSION_FILES=( examples/example-jwt-auth/pom.xml examples/example-hostname/build.gradle examples/example-hostname/pom.xml - examples/example-kotlin/build.gradle - examples/example-kotlin/android/helloworld/app/build.gradle examples/example-tls/build.gradle examples/example-tls/pom.xml examples/example-xds/build.gradle @@ -122,7 +120,6 @@ Tagging the Release $ ${EDITOR:-nano -w} README.md $ ${EDITOR:-nano -w} documentation/android-channel-builder.md $ ${EDITOR:-nano -w} cronet/README.md - $ ${EDITOR:-nano -w} examples/example-xds/README.md $ git commit -a -m "Update README etc to reference $MAJOR.$MINOR.$PATCH" ``` 3. Change root build files to remove "-SNAPSHOT" for the next release version diff --git a/SECURITY.md b/SECURITY.md index 5b4c75f485f..45d75542bb4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -232,9 +232,14 @@ import java.security.Security; ... // Somewhere in main() -Security.insertProviderAt(Conscrypt.newProvider(), 1); +Security.insertProviderAt( + Conscrypt.newProviderBuilder().provideTrustManager(false).build(), 1); ``` +Note: according to [Conscrypt Implementation Notes](https://github.com/google/conscrypt/blob/2.4.0/IMPLEMENTATION_NOTES.md#hostname-verification), +its default `HostnameVerifier` on OpenJDK always fails. This can be worked +around by disabling its default `TrustManager` implementation as shown above. + ### TLS with Jetty ALPN **Please do not use Jetty ALPN** diff --git a/all/build.gradle b/all/build.gradle index 9b7a9825666..0010ec4e31b 100644 --- a/all/build.gradle +++ b/all/build.gradle @@ -1,5 +1,5 @@ plugins { - id "java" + id "java-library" id "maven-publish" id "com.github.kt3k.coveralls" @@ -17,6 +17,7 @@ def subprojects = [ project(':grpc-okhttp'), project(':grpc-protobuf'), project(':grpc-protobuf-lite'), + project(':grpc-rls'), project(':grpc-services'), project(':grpc-stub'), project(':grpc-testing'), @@ -31,7 +32,7 @@ for (subproject in rootProject.subprojects) { } dependencies { - compile subprojects.minus(project(':grpc-protobuf-lite')) + api subprojects.minus(project(':grpc-protobuf-lite')) } javadoc { diff --git a/alts/build.gradle b/alts/build.gradle index 9796ada6abc..60f8fe1ed23 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -1,5 +1,5 @@ plugins { - id "java" + id "java-library" id "maven-publish" id "com.github.johnrengelman.shadow" @@ -13,34 +13,35 @@ sourceCompatibility = 1.7 targetCompatibility = 1.7 dependencies { - compile project(':grpc-auth'), - project(':grpc-core'), + api project(':grpc-core') + implementation project(':grpc-auth'), project(':grpc-grpclb'), project(':grpc-protobuf'), project(':grpc-stub'), libraries.lang, libraries.protobuf, libraries.conscrypt - def nettyDependency = compile project(':grpc-netty') - compile (libraries.google_auth_oauth2_http) { + def nettyDependency = implementation project(':grpc-netty') + implementation (libraries.google_auth_oauth2_http) { // prefer our own versions instead of google-auth-oauth2-http's dependency exclude group: 'com.google.guava', module: 'guava' // we'll always be more up-to-date exclude group: 'io.grpc', module: 'grpc-context' } + guavaDependency 'implementation' compileOnly libraries.javax_annotation - shadow configurations.compile.getDependencies().minus(nettyDependency) + shadow configurations.implementation.getDependencies().minus(nettyDependency) shadow project(path: ':grpc-netty-shaded', configuration: 'shadow') - testCompile project(':grpc-testing'), + testImplementation project(':grpc-testing'), project(':grpc-testing-proto'), libraries.guava, libraries.guava_testlib, libraries.junit, libraries.mockito, libraries.truth - testRuntime libraries.netty_tcnative, + testRuntimeOnly libraries.netty_tcnative, libraries.netty_epoll signature 'org.codehaus.mojo.signature:java17:1.0@signature' } diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsChannelCrypter.java b/alts/src/main/java/io/grpc/alts/internal/AltsChannelCrypter.java index 5e4c6fec301..e47433ff034 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsChannelCrypter.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsChannelCrypter.java @@ -17,10 +17,10 @@ package io.grpc.alts.internal; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Verify.verify; import com.google.common.annotations.VisibleForTesting; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.util.List; @@ -56,61 +56,72 @@ static int getCounterLength() { @Override public void encrypt(ByteBuf outBuf, List plainBufs) throws GeneralSecurityException { - checkArgument(outBuf.nioBufferCount() == 1); - // Copy plaintext buffers into outBuf for in-place encryption on single direct buffer. - ByteBuf plainBuf = outBuf.slice(outBuf.writerIndex(), outBuf.writableBytes()); - plainBuf.writerIndex(0); - for (ByteBuf inBuf : plainBufs) { - plainBuf.writeBytes(inBuf); + byte[] tempArr = new byte[outBuf.writableBytes()]; + + // Copy plaintext into tempArr. + { + ByteBuf tempBuf = Unpooled.wrappedBuffer(tempArr, 0, tempArr.length - TAG_LENGTH); + tempBuf.resetWriterIndex(); + for (ByteBuf plainBuf : plainBufs) { + tempBuf.writeBytes(plainBuf); + } } - verify(outBuf.writableBytes() == plainBuf.readableBytes() + TAG_LENGTH); - ByteBuffer out = outBuf.internalNioBuffer(outBuf.writerIndex(), outBuf.writableBytes()); - ByteBuffer plain = out.duplicate(); - plain.limit(out.limit() - TAG_LENGTH); - - byte[] counter = incrementOutCounter(); - int outPosition = out.position(); - aeadCrypter.encrypt(out, plain, counter); - int bytesWritten = out.position() - outPosition; - outBuf.writerIndex(outBuf.writerIndex() + bytesWritten); - verify(!outBuf.isWritable()); + // Encrypt into tempArr. + { + ByteBuffer out = ByteBuffer.wrap(tempArr); + ByteBuffer plain = ByteBuffer.wrap(tempArr, 0, tempArr.length - TAG_LENGTH); + + byte[] counter = incrementOutCounter(); + aeadCrypter.encrypt(out, plain, counter); + } + outBuf.writeBytes(tempArr); } @Override - public void decrypt(ByteBuf out, ByteBuf tag, List ciphertextBufs) + public void decrypt(ByteBuf outBuf, ByteBuf tagBuf, List ciphertextBufs) throws GeneralSecurityException { + // There is enough space for the ciphertext including the tag in outBuf. + byte[] tempArr = new byte[outBuf.writableBytes()]; + + // Copy ciphertext and tag into tempArr. + { + ByteBuf tempBuf = Unpooled.wrappedBuffer(tempArr); + tempBuf.resetWriterIndex(); + for (ByteBuf ciphertextBuf : ciphertextBufs) { + tempBuf.writeBytes(ciphertextBuf); + } + tempBuf.writeBytes(tagBuf); + } - ByteBuf cipherTextAndTag = out.slice(out.writerIndex(), out.writableBytes()); - cipherTextAndTag.writerIndex(0); + decryptInternal(outBuf, tempArr); + } - for (ByteBuf inBuf : ciphertextBufs) { - cipherTextAndTag.writeBytes(inBuf); + @Override + public void decrypt( + ByteBuf outBuf, ByteBuf ciphertextAndTagDirect) throws GeneralSecurityException { + byte[] tempArr = new byte[ciphertextAndTagDirect.readableBytes()]; + + // Copy ciphertext and tag into tempArr. + { + ByteBuf tempBuf = Unpooled.wrappedBuffer(tempArr); + tempBuf.resetWriterIndex(); + tempBuf.writeBytes(ciphertextAndTagDirect); } - cipherTextAndTag.writeBytes(tag); - decrypt(out, cipherTextAndTag); + decryptInternal(outBuf, tempArr); } - @Override - public void decrypt(ByteBuf out, ByteBuf ciphertextAndTag) throws GeneralSecurityException { - int bytesRead = ciphertextAndTag.readableBytes(); - checkArgument(bytesRead == out.writableBytes()); - - checkArgument(out.nioBufferCount() == 1); - ByteBuffer outBuffer = out.internalNioBuffer(out.writerIndex(), out.writableBytes()); - - checkArgument(ciphertextAndTag.nioBufferCount() == 1); - ByteBuffer ciphertextAndTagBuffer = - ciphertextAndTag.nioBuffer(ciphertextAndTag.readerIndex(), bytesRead); - - byte[] counter = incrementInCounter(); - int outPosition = outBuffer.position(); - aeadCrypter.decrypt(outBuffer, ciphertextAndTagBuffer, counter); - int bytesWritten = outBuffer.position() - outPosition; - out.writerIndex(out.writerIndex() + bytesWritten); - ciphertextAndTag.readerIndex(out.readerIndex() + bytesRead); - verify(out.writableBytes() == TAG_LENGTH); + private void decryptInternal(ByteBuf outBuf, byte[] tempArr) throws GeneralSecurityException { + // Perform in-place decryption on tempArr. + { + ByteBuffer ciphertextAndTag = ByteBuffer.wrap(tempArr); + ByteBuffer out = ByteBuffer.wrap(tempArr); + byte[] counter = incrementInCounter(); + aeadCrypter.decrypt(out, ciphertextAndTag, counter); + } + + outBuf.writeBytes(tempArr, 0, tempArr.length - TAG_LENGTH); } @Override diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java index e9103963780..083ad056789 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java @@ -82,6 +82,7 @@ private void setStartClientFields(HandshakerReq.Builder req) { startClientReq.addTargetIdentitiesBuilder().setServiceAccount(serviceAccount); } } + startClientReq.setMaxFrameSize(AltsTsiFrameProtector.getMaxFrameSize()); req.setClientStart(startClientReq); } @@ -97,6 +98,7 @@ private void setStartServerFields(HandshakerReq.Builder req, ByteBuffer inBytes) if (handshakerOptions.getRpcProtocolVersions() != null) { startServerReq.setRpcVersions(handshakerOptions.getRpcProtocolVersions()); } + startServerReq.setMaxFrameSize(AltsTsiFrameProtector.getMaxFrameSize()); req.setServerStart(startServerReq); } diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsTsiFrameProtector.java b/alts/src/main/java/io/grpc/alts/internal/AltsTsiFrameProtector.java index 23e1dc9e5ad..67d6637a130 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsTsiFrameProtector.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsTsiFrameProtector.java @@ -33,9 +33,10 @@ public final class AltsTsiFrameProtector implements TsiFrameProtector { private static final int HEADER_TYPE_FIELD_BYTES = 4; private static final int HEADER_BYTES = HEADER_LEN_FIELD_BYTES + HEADER_TYPE_FIELD_BYTES; private static final int HEADER_TYPE_DEFAULT = 6; - // Total frame size including full header and tag. - private static final int MAX_ALLOWED_FRAME_BYTES = 16 * 1024; - private static final int LIMIT_MAX_ALLOWED_FRAME_BYTES = 1024 * 1024; + private static final int LIMIT_MAX_ALLOWED_FRAME_SIZE = 1024 * 1024; + // Frame size negotiation extends frame size range to [MIN_FRAME_SIZE, MAX_FRAME_SIZE]. + private static final int MIN_FRAME_SIZE = 16 * 1024; + private static final int MAX_FRAME_SIZE = 128 * 1024; private final Protector protector; private final Unprotector unprotector; @@ -44,7 +45,7 @@ public final class AltsTsiFrameProtector implements TsiFrameProtector { public AltsTsiFrameProtector( int maxProtectedFrameBytes, ChannelCrypterNetty crypter, ByteBufAllocator alloc) { checkArgument(maxProtectedFrameBytes > HEADER_BYTES + crypter.getSuffixLength()); - maxProtectedFrameBytes = Math.min(LIMIT_MAX_ALLOWED_FRAME_BYTES, maxProtectedFrameBytes); + maxProtectedFrameBytes = Math.min(LIMIT_MAX_ALLOWED_FRAME_SIZE, maxProtectedFrameBytes); protector = new Protector(maxProtectedFrameBytes, crypter); unprotector = new Unprotector(crypter, alloc); } @@ -65,12 +66,16 @@ static int getHeaderTypeDefault() { return HEADER_TYPE_DEFAULT; } - public static int getMaxAllowedFrameBytes() { - return MAX_ALLOWED_FRAME_BYTES; + static int getLimitMaxAllowedFrameSize() { + return LIMIT_MAX_ALLOWED_FRAME_SIZE; } - static int getLimitMaxAllowedFrameBytes() { - return LIMIT_MAX_ALLOWED_FRAME_BYTES; + public static int getMinFrameSize() { + return MIN_FRAME_SIZE; + } + + public static int getMaxFrameSize() { + return MAX_FRAME_SIZE; } @Override @@ -262,7 +267,7 @@ private void handleHeader() { checkArgument( requiredProtectedBytes >= suffixBytes, "Invalid header field: frame size too small"); checkArgument( - requiredProtectedBytes <= LIMIT_MAX_ALLOWED_FRAME_BYTES - HEADER_BYTES, + requiredProtectedBytes <= LIMIT_MAX_ALLOWED_FRAME_SIZE - HEADER_BYTES, "Invalid header field: frame size too large"); int frameType = header.readIntLE(); checkArgument(frameType == HEADER_TYPE_DEFAULT, "Invalid header field: frame type"); diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java b/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java index 3cd639ad5ff..844e1038746 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java @@ -26,11 +26,15 @@ import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Negotiates a grpc channel key to be used by the TsiFrameProtector, using ALTs handshaker service. */ public final class AltsTsiHandshaker implements TsiHandshaker { + private static final Logger logger = Logger.getLogger(AltsTsiHandshaker.class.getName()); + public static final String TSI_SERVICE_ACCOUNT_PEER_PROPERTY = "service_account"; private final boolean isClient; @@ -178,6 +182,14 @@ public TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator byte[] key = handshaker.getKey(); Preconditions.checkState(key.length == AltsChannelCrypter.getKeyLength(), "Bad key length."); + // Frame size negotiation is not performed if the peer does not send max frame size (e.g. peer + // is gRPC Go or peer uses an old binary). + int peerMaxFrameSize = handshaker.getResult().getMaxFrameSize(); + if (peerMaxFrameSize != 0) { + maxFrameSize = Math.min(peerMaxFrameSize, AltsTsiFrameProtector.getMaxFrameSize()); + maxFrameSize = Math.max(AltsTsiFrameProtector.getMinFrameSize(), maxFrameSize); + } + logger.log(Level.FINE, "Maximum frame size value is {0}.", maxFrameSize); return new AltsTsiFrameProtector(maxFrameSize, new AltsChannelCrypter(key, isClient), alloc); } @@ -190,7 +202,7 @@ public TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator */ @Override public TsiFrameProtector createFrameProtector(ByteBufAllocator alloc) { - return createFrameProtector(AltsTsiFrameProtector.getMaxAllowedFrameBytes(), alloc); + return createFrameProtector(AltsTsiFrameProtector.getMinFrameSize(), alloc); } @Override diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java index 82c8a682bca..d1bd5ffea08 100644 --- a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java @@ -106,6 +106,7 @@ public void startClientHandshakeWithOptions() throws Exception { .setTargetName(TEST_TARGET_NAME) .addTargetIdentities( Identity.newBuilder().setServiceAccount(TEST_TARGET_SERVICE_ACCOUNT)) + .setMaxFrameSize(AltsTsiFrameProtector.getMaxFrameSize()) .build()) .build(); verify(mockStub).send(req); @@ -133,6 +134,22 @@ public void startServerHandshakeSuccess() throws Exception { ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE); ByteBuffer outFrame = handshaker.startServerHandshake(inBytes); + HandshakerReq req = + HandshakerReq.newBuilder() + .setServerStart( + StartServerHandshakeReq.newBuilder() + .addApplicationProtocols(AltsHandshakerClient.getApplicationProtocol()) + .putHandshakeParameters( + HandshakeProtocol.ALTS.getNumber(), + ServerHandshakeParameters.newBuilder() + .addRecordProtocols(AltsHandshakerClient.getRecordProtocol()) + .build()) + .setInBytes(ByteString.copyFrom(ByteBuffer.allocate(IN_BYTES_SIZE))) + .setMaxFrameSize(AltsTsiFrameProtector.getMaxFrameSize()) + .build()) + .build(); + verify(mockStub).send(req); + assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame()); assertFalse(handshaker.isFinished()); assertNull(handshaker.getResult()); diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsTsiFrameProtectorTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsTsiFrameProtectorTest.java index 4405a99eff0..cbda9dbcdc0 100644 --- a/alts/src/test/java/io/grpc/alts/internal/AltsTsiFrameProtectorTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/AltsTsiFrameProtectorTest.java @@ -125,7 +125,7 @@ public void parserHeader_frameTooLarge() throws GeneralSecurityException { getDirectBuffer( AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref); in.writeIntLE( - AltsTsiFrameProtector.getLimitMaxAllowedFrameBytes() + AltsTsiFrameProtector.getLimitMaxAllowedFrameSize() - AltsTsiFrameProtector.getHeaderLenFieldBytes() + 1); in.writeIntLE(6); @@ -206,7 +206,7 @@ public void parserHeader_frameMaxOk() throws GeneralSecurityException { getDirectBuffer( AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref); in.writeIntLE( - AltsTsiFrameProtector.getLimitMaxAllowedFrameBytes() + AltsTsiFrameProtector.getLimitMaxAllowedFrameSize() - AltsTsiFrameProtector.getHeaderLenFieldBytes()); in.writeIntLE(6); diff --git a/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java b/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java index a04bbfd07e8..1483e4b08e2 100644 --- a/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java +++ b/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java @@ -224,7 +224,7 @@ public TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator @Override public TsiFrameProtector createFrameProtector(ByteBufAllocator alloc) { - return createFrameProtector(AltsTsiFrameProtector.getMaxAllowedFrameBytes(), alloc); + return createFrameProtector(AltsTsiFrameProtector.getMinFrameSize(), alloc); } @Override diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle index 36d3d21d53b..ec144bda19a 100644 --- a/android-interop-testing/build.gradle +++ b/android-interop-testing/build.gradle @@ -65,6 +65,7 @@ dependencies { implementation (libraries.google_auth_oauth2_http) { exclude group: 'org.apache.httpcomponents' } + censusGrpcMetricDependency 'implementation' compileOnly libraries.javax_annotation diff --git a/android/build.gradle b/android/build.gradle index 83dfc39fb80..97f24cd92c1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,8 +26,8 @@ repositories { } dependencies { - implementation project(':grpc-core') - + api project(':grpc-core') + guavaDependency 'implementation' testImplementation project('::grpc-okhttp') testImplementation libraries.androidx_test testImplementation libraries.junit diff --git a/api/build.gradle b/api/build.gradle index 09361582df8..3eaab4894f4 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,5 +1,5 @@ plugins { - id "java" + id "java-library" id "maven-publish" id "me.champeau.gradle.jmh" @@ -11,18 +11,11 @@ description = 'gRPC: API' evaluationDependsOn(project(':grpc-context').path) dependencies { - compile project(':grpc-context'), - libraries.errorprone, - libraries.jsr305, - libraries.animalsniffer_annotations - compile (libraries.guava) { - // prefer our own versions from libraries instead of Guava's dependency - exclude group: 'com.google.errorprone', module: 'error_prone_annotations' - exclude group: 'com.google.code.findbugs', module: 'jsr305' - exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations' - } - - testCompile project(':grpc-context').sourceSets.test.output, + api project(':grpc-context'), + libraries.jsr305 + guavaDependency 'implementation' + + testImplementation project(':grpc-context').sourceSets.test.output, project(':grpc-testing'), project(':grpc-grpclb'), libraries.guava_testlib diff --git a/api/src/main/java/io/grpc/CallOptions.java b/api/src/main/java/io/grpc/CallOptions.java index 17b065ed22e..2c9b1c52084 100644 --- a/api/src/main/java/io/grpc/CallOptions.java +++ b/api/src/main/java/io/grpc/CallOptions.java @@ -150,8 +150,10 @@ public Deadline getDeadline() { /** * Enables - * 'wait for ready' feature for the call. 'Fail fast' is the default option for gRPC calls - * and 'wait for ready' is the opposite to it. + * 'wait for ready' for the call. Wait-for-ready queues the RPC until a connection is + * available. This may dramatically increase the latency of the RPC, but avoids failing + * "unnecessarily." The default queues the RPC until an attempt to connect has completed, but + * fails RPCs without sending them if unable to connect. */ public CallOptions withWaitForReady() { CallOptions newOptions = new CallOptions(this); diff --git a/api/src/main/java/io/grpc/ClientCall.java b/api/src/main/java/io/grpc/ClientCall.java index 234678618e0..b66924d4102 100644 --- a/api/src/main/java/io/grpc/ClientCall.java +++ b/api/src/main/java/io/grpc/ClientCall.java @@ -205,8 +205,8 @@ public void onReady() {} /** * Prevent any further processing for this {@code ClientCall}. No further messages may be sent or * will be received. The server is informed of cancellations, but may not stop processing the - * call. Cancellation is permitted if previously {@link #halfClose}d. Cancelling an already {@code - * cancel()}ed {@code ClientCall} has no effect. + * call. Cancellation is permitted even if previously {@link #halfClose}d. Cancelling an already + * {@code cancel()}ed {@code ClientCall} has no effect. * *

No other methods on this class can be called after this method has been called. * diff --git a/api/src/main/java/io/grpc/InternalNotifyOnServerBuild.java b/api/src/main/java/io/grpc/InternalServer.java similarity index 55% rename from api/src/main/java/io/grpc/InternalNotifyOnServerBuild.java rename to api/src/main/java/io/grpc/InternalServer.java index b52acfaa7cc..8a28c91fbec 100644 --- a/api/src/main/java/io/grpc/InternalNotifyOnServerBuild.java +++ b/api/src/main/java/io/grpc/InternalServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 The gRPC Authors + * Copyright 2020 The gRPC Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,15 @@ package io.grpc; /** - * Provides a callback method for a service to receive a reference to its server. The contract with - * {@link ServerBuilder} is that this method will be called on all registered services implementing - * the interface after build() has been called and before the {@link Server} instance is returned. + * Internal accessor for getting the {@link Server} instance inside server RPC {@link Context}. + * This is intended for usage internal to the gRPC team. If you think you need to use + * this, contact the gRPC team first. */ @Internal -public interface InternalNotifyOnServerBuild { - /** Notifies the service that the server has been built. */ - void notifyOnBuild(Server server); +public class InternalServer { + public static final Context.Key SERVER_CONTEXT_KEY = Server.SERVER_CONTEXT_KEY; + + // Prevent instantiation. + private InternalServer() { + } } diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 5682286c776..e97571c8c63 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -75,18 +75,19 @@ * synchronization primitives, blocking I/O, blocking RPCs, etc. * *

  • Avoid calling into other components with lock held. The Synchronization - * Context may be under a lock, e.g., the transport lock of OkHttp. If your LoadBalancer has a - * lock, holds the lock in a callback method (e.g., {@link #handleSubchannelState - * handleSubchannelState()}) while calling into another class that may involve locks, be cautious - * of deadlock. Generally you wouldn't need any locking in the LoadBalancer.
  • + * Context may be under a lock, e.g., the transport lock of OkHttp. If your LoadBalancer holds a + * lock in a callback method (e.g., {@link #handleResolvedAddresses handleResolvedAddresses()}) + * while calling into another method that also involves locks, be cautious of deadlock. Generally + * you wouldn't need any locking in the LoadBalancer if you follow the canonical implementation + * pattern below. * * * *

    The canonical implementation pattern

    * *

    A {@link LoadBalancer} keeps states like the latest addresses from NameResolver, the - * Subchannel(s) and their latest connectivity states. These states are mutated within the Channel - * Executor. + * Subchannel(s) and their latest connectivity states. These states are mutated within the + * Synchronization Context, * *

    A typical {@link SubchannelPicker SubchannelPicker} holds a snapshot of these states. It may * have its own states, e.g., a picker from a round-robin load-balancer may keep a pointer to the @@ -95,9 +96,10 @@ * picker only needs to synchronize its own states, which is typically trivial to implement. * *

    When the LoadBalancer states changes, e.g., Subchannels has become or stopped being READY, and - * we want subsequent RPCs to use the latest list of READY Subchannels, LoadBalancer would create - * a new picker, which holds a snapshot of the latest Subchannel list. Refer to the javadoc of - * {@link #handleSubchannelState handleSubchannelState()} how to do this properly. + * we want subsequent RPCs to use the latest list of READY Subchannels, LoadBalancer would create a + * new picker, which holds a snapshot of the latest Subchannel list. Refer to the javadoc of {@link + * io.grpc.LoadBalancer.SubchannelStateListener#onSubchannelState onSubchannelState()} how to do + * this properly. * *

    No synchronization should be necessary between LoadBalancer and its pickers if you follow * the pattern above. It may be possible to implement in a different way, but that would usually @@ -360,7 +362,7 @@ public boolean equals(Object obj) { @Deprecated public void handleSubchannelState( Subchannel subchannel, ConnectivityStateInfo stateInfo) { - // Do nothing. If the implemetation doesn't implement this, it will get subchannel states from + // Do nothing. If the implementation doesn't implement this, it will get subchannel states from // the new API. We don't throw because there may be forwarding LoadBalancers still plumb this. } @@ -507,7 +509,7 @@ private PickResult( * A decision to proceed the RPC on a Subchannel. * *

    The Subchannel should either be an original Subchannel returned by {@link - * Helper#createSubchannel Helper.createSubchannel()}, or a wrapper of it preferrably based on + * Helper#createSubchannel Helper.createSubchannel()}, or a wrapper of it preferably based on * {@code ForwardingSubchannel}. At the very least its {@link Subchannel#getInternalSubchannel * getInternalSubchannel()} must return the same object as the one returned by the original. * Otherwise the Channel cannot use it for the RPC. @@ -1024,7 +1026,7 @@ public void updateSubchannelAddresses( /** * Updates the addresses used for connections in the {@code Channel} that was created by {@link - * #createOobChannel(EquivalentAddressGroup, String)}. This is supperior to {@link + * #createOobChannel(EquivalentAddressGroup, String)}. This is superior to {@link * #createOobChannel(EquivalentAddressGroup, String)} when the old and new addresses overlap, * since the channel can continue using an existing connection. * @@ -1048,8 +1050,6 @@ public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressG *

    The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB * channels within {@link #shutdown}. * - *

    NOT IMPLEMENTED: this method is currently a stub and not yet implemented by gRPC. - * * @since 1.20.0 */ public ManagedChannel createResolvingOobChannel(String target) { diff --git a/api/src/main/java/io/grpc/Server.java b/api/src/main/java/io/grpc/Server.java index fc98fe242e0..781455b18ee 100644 --- a/api/src/main/java/io/grpc/Server.java +++ b/api/src/main/java/io/grpc/Server.java @@ -30,6 +30,14 @@ @ThreadSafe public abstract class Server { + /** + * Key for accessing the {@link Server} instance inside server RPC {@link Context}. It's + * unclear to us what users would need. If you think you need to use this, please file an + * issue for us to discuss a public API. + */ + static final Context.Key SERVER_CONTEXT_KEY = + Context.key("io.grpc.Server"); + /** * Bind and start the server. After this call returns, clients may begin connecting to the * listening socket(s). diff --git a/auth/build.gradle b/auth/build.gradle index 2dafc0f4fd9..b2e1620bb68 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -1,5 +1,5 @@ plugins { - id "java" + id "java-library" id "maven-publish" id "me.champeau.gradle.japicmp" @@ -8,9 +8,10 @@ plugins { description = "gRPC: Auth" dependencies { - compile project(':grpc-api'), + api project(':grpc-api'), libraries.google_auth_credentials - testCompile project(':grpc-testing'), + guavaDependency 'implementation' + testImplementation project(':grpc-testing'), libraries.google_auth_oauth2_http signature "org.codehaus.mojo.signature:java17:1.0@signature" signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" diff --git a/benchmarks/README.md b/benchmarks/README.md index 29c107d7281..48933fff88c 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -17,6 +17,46 @@ You can now find the client and the server executables in `benchmarks/build/inst The `C++` counterpart can be found at https://github.com/grpc/grpc/tree/master/test/cpp/qps +The [netty benchmark](src/jmh/java/io/grpc/benchmarks/netty) directory contains +the standard benchmarks used to assess the performance of GRPC. Since these +benchmarks run on localhost over loopback the performance of the underlying network is considerably +different to real networks and their behavior. To address this issue we recommend the use of +a network emulator to make loopback behave more like a real network. To this end the benchmark +code looks for a loopback interface with 'benchmark' in its name and attempts to use the address +bound to that interface when creating the client and server. If it cannot find such an interface it +will print a warning and continue with the default localhost address. + +To attempt to standardize benchmark behavior across machines we attempt to emulate a 10gbit +ethernet interface with a packet delay of 0.1ms. + +### Linux + +On Linux we can use [netem](https://www.linuxfoundation.org/collaborate/workgroups/networking/netem) to shape the traffic appropriately. + +```sh +# Remove all traffic shaping from loopback +sudo tc qdisc del dev lo root +# Add a priority traffic class to the root of loopback +sudo tc qdisc add dev lo root handle 1: prio +# Add a qdisc under the new class with the appropriate shaping +sudo tc qdisc add dev lo parent 1:1 handle 2: netem delay 0.1ms rate 10gbit +# Add a filter which selects the new qdisc class for traffic to 127.127.127.127 +sudo tc filter add dev lo parent 1:0 protocol ip prio 1 u32 match ip dst 127.127.127.127 flowid 2:1 +# Create an interface alias call 'lo:benchmark' that maps 127.127.127.127 to loopback +sudo ip addr add dev lo 127.127.127.127/32 label lo:benchmark +``` + +to remove this configuration + +```sh +sudo tc qdisc del dev lo root +sudo ip addr del dev lo 127.127.127.127/32 label lo:benchmark +``` + +### Other Platforms + +Contributions are welcome! + ## Visualizing the Latency Distribution The QPS client comes with the option `--save_histogram=FILE`, if set it serializes the histogram to `FILE` which can then be used with a plotter to visualize the latency distribution. The histogram is stored in the file format of [HdrHistogram](https://hdrhistogram.org/). That way it can be plotted very easily using a browser based tool like https://hdrhistogram.github.io/HdrHistogram/plotFiles.html. Simply upload the generated file and it will generate a beautiful graph for you. It also allows you to plot two or more histograms on the same surface in order two easily compare latency distributions. diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 7bf6336325b..c59ef8e7039 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -21,7 +21,7 @@ configurations { } dependencies { - compile project(':grpc-core'), + implementation project(':grpc-core'), project(':grpc-netty'), project(':grpc-okhttp'), project(':grpc-stub'), @@ -34,7 +34,7 @@ dependencies { compileOnly libraries.javax_annotation alpnagent libraries.jetty_alpn_agent - testCompile libraries.junit, + testImplementation libraries.junit, libraries.mockito } diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/README.md b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/README.md deleted file mode 100644 index 64943e0540f..00000000000 --- a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/README.md +++ /dev/null @@ -1,45 +0,0 @@ -Benchmarks -========== - -This directory contains the standard benchmarks used to assess the performance of GRPC. Since these -benchmarks run on localhost over loopback the performance of the underlying network is considerably -different to real networks and their behavior. To address this issue we recommend the use of -a network emulator to make loopback behave more like a real network. To this end the benchmark -code looks for a loopback interface with 'benchmark' in its name and attempts to use the address -bound to that interface when creating the client and server. If it cannot find such an interface it -will print a warning and continue with the default localhost address. - -To attempt to standardize benchmark behavior across machines we attempt to emulate a 10gbit -ethernet interface with a packet delay of 0.1ms. - - -Linux -===== - -On Linux we can use [netem](https://www.linuxfoundation.org/collaborate/workgroups/networking/netem) to shape the traffic appropriately. - -```sh -# Remove all traffic shaping from loopback -sudo tc qdisc del dev lo root -# Add a priority traffic class to the root of loopback -sudo tc qdisc add dev lo root handle 1: prio -# Add a qdisc under the new class with the appropriate shaping -sudo tc qdisc add dev lo parent 1:1 handle 2: netem delay 0.1ms rate 10gbit -# Add a filter which selects the new qdisc class for traffic to 127.127.127.127 -sudo tc filter add dev lo parent 1:0 protocol ip prio 1 u32 match ip dst 127.127.127.127 flowid 2:1 -# Create an interface alias call 'lo:benchmark' that maps 127.127.127.127 to loopback -sudo ip addr add dev lo 127.127.127.127/32 label lo:benchmark -``` - -to remove this configuration - -```sh -sudo tc qdisc del dev lo root -sudo ip addr del dev lo 127.127.127.127/32 label lo:benchmark -``` - -Other Platforms -=============== - -Contributions welcome! - diff --git a/build.gradle b/build.gradle index 4c8c919ebdc..a16592adc04 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.29.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.30.0" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() @@ -41,6 +41,9 @@ subprojects { } } + def isAndroid = project.name in [ + 'grpc-android', 'grpc-android-interop-testing', 'grpc-cronet'] + ext { def exeSuffix = osdetector.os == 'windows' ? ".exe" : "" protocPluginBaseName = 'protoc-gen-grpc-java' @@ -49,12 +52,11 @@ subprojects { nettyVersion = '4.1.48.Final' guavaVersion = '28.2-android' googleauthVersion = '0.20.0' - protobufVersion = '3.11.0' + protobufVersion = '3.12.0' protocVersion = protobufVersion opencensusVersion = '0.24.0' configureProtoCompilation = { - boolean isAndroid = project.getName().contains('android') String generatedSourcePath = "${projectDir}/src/generated" project.protobuf { protoc { @@ -138,7 +140,7 @@ subprojects { gson: "com.google.code.gson:gson:2.8.6", guava: "com.google.guava:guava:${guavaVersion}", hpack: 'com.twitter:hpack:0.10.1', - javax_annotation: 'javax.annotation:javax.annotation-api:1.2', + javax_annotation: 'org.apache.tomcat:annotations-api:6.0.53', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', google_api_protos: 'com.google.api.grpc:proto-google-common-protos:1.17.0', google_auth_credentials: "com.google.auth:google-auth-library-credentials:${googleauthVersion}", @@ -185,18 +187,73 @@ subprojects { // Jetty ALPN dependencies jetty_alpn_agent: 'org.mortbay.jetty.alpn:jetty-alpn-agent:2.0.9' ] + + // A util function to config guava dependency with transitive dependencies + // properly resolved for the failOnVersionConflict strategy. + guavaDependency = { configurationName -> + dependencies."$configurationName"(libraries.guava) { + exclude group: 'com.google.code.findbugs', module: 'jsr305' + exclude group: 'com.google.errorprone', module: 'error_prone_annotations' + exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations' + } + dependencies."$configurationName" libraries.errorprone + dependencies.runtimeOnly libraries.animalsniffer_annotations + dependencies.runtimeOnly libraries.jsr305 + } + + // A util function to config opencensus_api dependency with transitive + // dependencies properly resolved for the failOnVersionConflict strategy. + censusApiDependency = { configurationName -> + dependencies."$configurationName"(libraries.opencensus_api) { + exclude group: 'com.google.code.findbugs', module: 'jsr305' + exclude group: 'com.google.guava', module: 'guava' + // we'll always be more up-to-date + exclude group: 'io.grpc', module: 'grpc-context' + } + dependencies.runtimeOnly project(':grpc-context') + dependencies.runtimeOnly libraries.jsr305 + guavaDependency 'runtimeOnly' + } + + // A util function to config opencensus_contrib_grpc_metrics dependency + // with transitive dependencies properly resolved for the failOnVersionConflict + // strategy. + censusGrpcMetricDependency = { configurationName -> + dependencies."$configurationName"(libraries.opencensus_contrib_grpc_metrics) { + exclude group: 'com.google.code.findbugs', module: 'jsr305' + exclude group: 'com.google.guava', module: 'guava' + // we'll always be more up-to-date + exclude group: 'io.grpc', module: 'grpc-context' + } + dependencies.runtimeOnly project(':grpc-context') + dependencies.runtimeOnly libraries.jsr305 + guavaDependency 'runtimeOnly' + } + + // A util function to config perfmark dependency with transitive + // dependencies properly resolved for the failOnVersionConflict strategy. + perfmarkDependency = { configurationName -> + dependencies."$configurationName"(libraries.perfmark) { + exclude group: 'com.google.errorprone', module: 'error_prone_annotations' + } + dependencies.runtimeOnly libraries.errorprone + } } - // Define a separate configuration for managing the dependency on Jetty ALPN agent. configurations { - compile { - // Detect Maven Enforcer's dependencyConvergence failures. We only - // care for artifacts used as libraries by others. - if (!(project.name in [ + // Detect Maven Enforcer's dependencyConvergence failures. We only + // care for artifacts used as libraries by others. + if (isAndroid && !(project.name in ['grpc-android-interop-testing'])) { + releaseRuntimeClasspath { + resolutionStrategy.failOnVersionConflict() + } + } + if (!isAndroid && !(project.name in [ 'grpc-benchmarks', 'grpc-interop-testing', 'grpc-gae-interop-testing-jdk8', - ])) { + ])) { + runtimeClasspath { resolutionStrategy.failOnVersionConflict() } } diff --git a/buildscripts/build_docker.sh b/buildscripts/build_docker.sh index 6c405519571..7ba8b6e6973 100755 --- a/buildscripts/build_docker.sh +++ b/buildscripts/build_docker.sh @@ -2,7 +2,7 @@ set -eu -o pipefail readonly proto_dir="$(mktemp -d --tmpdir protobuf.XXXXXX)" -wget -O - https://github.com/google/protobuf/archive/v3.11.0.tar.gz | tar xz -C "$proto_dir" +wget -O - https://github.com/google/protobuf/archive/v3.12.0.tar.gz | tar xz -C "$proto_dir" -docker build -t protoc-artifacts "$proto_dir"/protobuf-3.11.0/protoc-artifacts +docker build -t protoc-artifacts "$proto_dir"/protobuf-3.12.0/protoc-artifacts rm -r "$proto_dir" diff --git a/buildscripts/kokoro/android.sh b/buildscripts/kokoro/android.sh index 9751c9aa433..951488f747d 100755 --- a/buildscripts/kokoro/android.sh +++ b/buildscripts/kokoro/android.sh @@ -56,9 +56,6 @@ cd ../routeguide cd ../helloworld ../../gradlew build -cd "$BASE_DIR/github/grpc-java/examples/example-kotlin/android/helloworld/" -../../../gradlew build - # Skip APK size and dex count comparisons for non-PR builds if [[ -z "${KOKORO_GITHUB_PULL_REQUEST_COMMIT:-}" ]]; then diff --git a/buildscripts/kokoro/linux_artifacts.cfg b/buildscripts/kokoro/linux_artifacts.cfg index 46263cf29f6..0938e4bff2e 100644 --- a/buildscripts/kokoro/linux_artifacts.cfg +++ b/buildscripts/kokoro/linux_artifacts.cfg @@ -6,6 +6,7 @@ timeout_mins: 60 action { define_artifacts { - regex: ["**/mvn-artifacts/**"] + regex: "github/grpc-java/mvn-artifacts/**" + regex: "github/grpc-java/artifacts/**" } } diff --git a/buildscripts/kokoro/macos.cfg b/buildscripts/kokoro/macos.cfg index 50c0da6cdc3..310e1130416 100644 --- a/buildscripts/kokoro/macos.cfg +++ b/buildscripts/kokoro/macos.cfg @@ -15,6 +15,6 @@ env_vars { # We always build mvn artifacts. action { define_artifacts { - regex: ["**/mvn-artifacts/**"] + regex: "github/grpc-java/mvn-artifacts/**" } } diff --git a/buildscripts/kokoro/unix.sh b/buildscripts/kokoro/unix.sh index a559aa42ee7..353717e336e 100755 --- a/buildscripts/kokoro/unix.sh +++ b/buildscripts/kokoro/unix.sh @@ -88,8 +88,16 @@ if [[ -z "${ALL_ARTIFACTS:-}" ]]; then ./gradlew grpc-compiler:build grpc-compiler:publish $GRADLE_FLAGS \ -Dorg.gradle.parallel=false -PrepositoryDir=$LOCAL_MVN_TEMP else - ./gradlew publish $GRADLE_FLAGS \ + ./gradlew publish :grpc-core:versionFile $GRADLE_FLAGS \ -Dorg.gradle.parallel=false -PrepositoryDir=$LOCAL_MVN_TEMP + pushd examples/example-hostname + ../gradlew jibBuildTar $GRADLE_FLAGS + popd + + readonly OTHER_ARTIFACT_DIR="${OTHER_ARTIFACT_DIR:-$GRPC_JAVA_DIR/artifacts}" + mkdir -p "$OTHER_ARTIFACT_DIR" + cp core/build/version "$OTHER_ARTIFACT_DIR"/ + cp examples/example-hostname/build/example-hostname.* "$OTHER_ARTIFACT_DIR"/ fi readonly MVN_ARTIFACT_DIR="${MVN_ARTIFACT_DIR:-$GRPC_JAVA_DIR/mvn-artifacts}" diff --git a/buildscripts/kokoro/upload_artifacts.sh b/buildscripts/kokoro/upload_artifacts.sh index c3c97444dc4..c56cddf8f8c 100644 --- a/buildscripts/kokoro/upload_artifacts.sh +++ b/buildscripts/kokoro/upload_artifacts.sh @@ -12,6 +12,7 @@ find "$KOKORO_GFILE_DIR" # The output from all the jobs are coalesced into a single dir LOCAL_MVN_ARTIFACTS="$KOKORO_GFILE_DIR"/github/grpc-java/mvn-artifacts/ +LOCAL_OTHER_ARTIFACTS="$KOKORO_GFILE_DIR"/github/grpc-java/artifacts/ # verify that files from all 3 grouped jobs are present. # platform independent artifacts, from linux job: @@ -45,6 +46,9 @@ mkdir -p ~/java_signing/ gsutil cp -r gs://grpc-testing-secrets/java_signing/ ~/ gpg --batch --import ~/java_signing/grpc-java-team-sonatype.asc +gsutil cat gs://grpc-testing-secrets/dockerhub_credentials/grpcpackages.password \ + | docker login --username grpcpackages --password-stdin + # gpg commands changed between v1 and v2 are different. gpg --version @@ -65,5 +69,19 @@ if gpg --version | grep 'gpg (GnuPG) 2.'; then --detach-sign -a {} \; fi +# Just the numbers; does not include leading 'v' +VERSION="$(cat $LOCAL_OTHER_ARTIFACTS/version)" +EXAMPLE_HOSTNAME_ID="$(cat "$LOCAL_OTHER_ARTIFACTS/example-hostname.id")" +docker load --input "$LOCAL_OTHER_ARTIFACTS/example-hostname.tar" +LATEST_VERSION="$((echo "v$VERSION"; git ls-remote -t https://github.com/grpc/grpc-java.git | cut -f 2 | sed s#refs/tags/##) | sort -V | tail -n 1)" + + STAGING_REPO=a93898609ef848 "$GRPC_JAVA_DIR"/buildscripts/sonatype-upload.sh "$STAGING_REPO" "$LOCAL_MVN_ARTIFACTS" + +docker tag "$EXAMPLE_HOSTNAME_ID" "grpc/java-example-hostname:${VERSION}" +docker push "grpc/java-example-hostname:${VERSION}" +if [[ "$VERSION" = "$LATEST_VERSION" ]]; then + docker tag "$EXAMPLE_HOSTNAME_ID" grpc/java-example-hostname:latest + docker push grpc/java-example-hostname:latest +fi diff --git a/buildscripts/kokoro/windows.cfg b/buildscripts/kokoro/windows.cfg index 0002fd1571d..6b2703f99c9 100644 --- a/buildscripts/kokoro/windows.cfg +++ b/buildscripts/kokoro/windows.cfg @@ -7,6 +7,7 @@ timeout_mins: 45 # We always build mvn artifacts. action { define_artifacts { - regex: ["**/build/test-results/**/*.xml", "**/mvn-artifacts/**"] + regex: "**/build/test-results/**/*.xml" + regex: "github/grpc-java/mvn-artifacts/**" } } diff --git a/buildscripts/kokoro/xds.sh b/buildscripts/kokoro/xds.sh index e1b99376766..9d662451a2a 100755 --- a/buildscripts/kokoro/xds.sh +++ b/buildscripts/kokoro/xds.sh @@ -24,6 +24,8 @@ JAVA_OPTS=-Djava.util.logging.config.file=grpc-java/buildscripts/xds_logging.pro python3 grpc/tools/run_tests/run_xds_tests.py \ --test_case=all \ --project_id=grpc-testing \ + --source_image=projects/grpc-testing/global/images/xds-test-server \ + --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ --client_cmd="grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-client \ diff --git a/buildscripts/make_dependencies.bat b/buildscripts/make_dependencies.bat index 7c13d8159f3..c9ff2a63a43 100644 --- a/buildscripts/make_dependencies.bat +++ b/buildscripts/make_dependencies.bat @@ -1,4 +1,4 @@ -set PROTOBUF_VER=3.11.0 +set PROTOBUF_VER=3.12.0 set CMAKE_NAME=cmake-3.3.2-win32-x86 if not exist "protobuf-%PROTOBUF_VER%\cmake\build\Release\" ( diff --git a/buildscripts/make_dependencies.sh b/buildscripts/make_dependencies.sh index 92ef9d7f72d..d6332f303d6 100755 --- a/buildscripts/make_dependencies.sh +++ b/buildscripts/make_dependencies.sh @@ -3,7 +3,7 @@ # Build protoc set -evux -o pipefail -PROTOBUF_VERSION=3.11.0 +PROTOBUF_VERSION=3.12.0 # ARCH is x86_64 bit unless otherwise specified. ARCH="${ARCH:-x86_64}" diff --git a/census/build.gradle b/census/build.gradle index 1c08390d185..7925262ce3c 100644 --- a/census/build.gradle +++ b/census/build.gradle @@ -1,5 +1,5 @@ plugins { - id "java" + id "java-library" id "maven-publish" } @@ -8,27 +8,12 @@ description = 'gRPC: Census' evaluationDependsOn(project(':grpc-api').path) dependencies { - compile project(':grpc-api') - - compile (libraries.opencensus_api) { - // prefer 3.0.2 from libraries instead of 3.0.1 - exclude group: 'com.google.code.findbugs', module: 'jsr305' - // prefer 20.0 from libraries instead of 19.0 - exclude group: 'com.google.guava', module: 'guava' - // we'll always be more up-to-date - exclude group: 'io.grpc', module: 'grpc-context' - } - - compile (libraries.opencensus_contrib_grpc_metrics) { - // prefer 3.0.2 from libraries instead of 3.0.1 - exclude group: 'com.google.code.findbugs', module: 'jsr305' - // we'll always be more up-to-date - exclude group: 'io.grpc', module: 'grpc-context' - // prefer 20.0 from libraries instead of 19.0 - exclude group: 'com.google.guava', module: 'guava' - } + api project(':grpc-api') + guavaDependency 'implementation' + censusApiDependency 'implementation' + censusGrpcMetricDependency 'implementation' - testCompile project(':grpc-api').sourceSets.test.output, + testImplementation project(':grpc-api').sourceSets.test.output, project(':grpc-context').sourceSets.test.output, project(':grpc-core').sourceSets.test.output, project(':grpc-testing'), diff --git a/compiler/build.gradle b/compiler/build.gradle index 098f48c3080..60d3a436f6c 100644 --- a/compiler/build.gradle +++ b/compiler/build.gradle @@ -126,14 +126,14 @@ model { } configurations { - testLiteCompile + testLiteImplementation } dependencies { - testCompile project(':grpc-protobuf'), + testImplementation project(':grpc-protobuf'), project(':grpc-stub'), libraries.javax_annotation - testLiteCompile project(':grpc-protobuf-lite'), + testLiteImplementation project(':grpc-protobuf-lite'), project(':grpc-stub'), libraries.javax_annotation } diff --git a/compiler/src/java_plugin/cpp/java_generator.cpp b/compiler/src/java_plugin/cpp/java_generator.cpp index db914ce12e1..695ef8581ba 100644 --- a/compiler/src/java_plugin/cpp/java_generator.cpp +++ b/compiler/src/java_plugin/cpp/java_generator.cpp @@ -161,10 +161,6 @@ static inline std::string MethodIdFieldName(const MethodDescriptor* method) { return "METHODID_" + ToAllUpperCase(method->name()); } -static inline bool ShouldGenerateAsLite(const Descriptor* desc) { - return false; -} - static inline std::string MessageFullJavaName(const Descriptor* desc) { return google::protobuf::compiler::java::ClassName(desc); } diff --git a/compiler/src/java_plugin/cpp/java_plugin.cpp b/compiler/src/java_plugin/cpp/java_plugin.cpp index a62fb8677d6..d8b10c52422 100644 --- a/compiler/src/java_plugin/cpp/java_plugin.cpp +++ b/compiler/src/java_plugin/cpp/java_plugin.cpp @@ -43,10 +43,14 @@ class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator { JavaGrpcGenerator() {} virtual ~JavaGrpcGenerator() {} + uint64_t GetSupportedFeatures() const override { + return FEATURE_PROTO3_OPTIONAL; + } + virtual bool Generate(const google::protobuf::FileDescriptor* file, const std::string& parameter, google::protobuf::compiler::GeneratorContext* context, - std::string* error) const { + std::string* error) const override { std::vector > options; google::protobuf::compiler::ParseGeneratorParameter(parameter, &options); diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index cafb4dbe5f8..3b2f9d94e29 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.29.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.30.0)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index bf4c3079c5a..55d598d9199 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.29.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.30.0)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index e2eb97adacb..d4c35a401cc 100644 --- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt +++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.29.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.30.0)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index 1d0e4305469..12459208bce 100644 --- a/compiler/src/testLite/golden/TestService.java.txt +++ b/compiler/src/testLite/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.29.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.30.0)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/context/build.gradle b/context/build.gradle index 832e22accba..1028ad2c628 100644 --- a/context/build.gradle +++ b/context/build.gradle @@ -10,7 +10,7 @@ plugins { description = 'gRPC: Context' dependencies { - testCompile libraries.jsr305, libraries.guava_testlib + testImplementation libraries.jsr305, libraries.guava_testlib signature "org.codehaus.mojo.signature:java17:1.0@signature" signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } diff --git a/context/src/main/java/io/grpc/Context.java b/context/src/main/java/io/grpc/Context.java index bd37ba7fadd..da540215bef 100644 --- a/context/src/main/java/io/grpc/Context.java +++ b/context/src/main/java/io/grpc/Context.java @@ -17,6 +17,7 @@ package io.grpc; import io.grpc.Context.CheckReturnValue; +import io.grpc.PersistentHashArrayMappedTrie.Node; import java.io.Closeable; import java.util.ArrayList; import java.util.concurrent.Callable; @@ -99,9 +100,6 @@ public class Context { static final Logger log = Logger.getLogger(Context.class.getName()); - private static final PersistentHashArrayMappedTrie, Object> EMPTY_ENTRIES = - new PersistentHashArrayMappedTrie<>(); - // Long chains of contexts are suspicious and usually indicate a misuse of Context. // The threshold is arbitrarily chosen. // VisibleForTesting @@ -114,7 +112,7 @@ public class Context { *

    Never assume this is the default context for new threads, because {@link Storage} may define * a default context that is different from ROOT. */ - public static final Context ROOT = new Context(null, EMPTY_ENTRIES); + public static final Context ROOT = new Context(); // Visible For testing static Storage storage() { @@ -155,19 +153,25 @@ private static Storage createStorage( } /** - * Create a {@link Key} with the given debug name. Multiple different keys may have the same name; - * the name is intended for debugging purposes and does not impact behavior. + * Create a {@link Key} with the given debug name. + * + * @param debugString a name intended for debugging purposes and does not impact behavior. + * Multiple different keys may have the same debugString. + * The value should be not null. */ - public static Key key(String name) { - return new Key<>(name); + public static Key key(String debugString) { + return new Key<>(debugString); } /** - * Create a {@link Key} with the given debug name and default value. Multiple different keys may - * have the same name; the name is intended for debugging purposes and does not impact behavior. + * Create a {@link Key} with the given debug name and default value. + * + * @param debugString a name intended for debugging purposes and does not impact behavior. + * Multiple different keys may have the same debugString. + * The value should be not null. */ - public static Key keyWithDefault(String name, T defaultValue) { - return new Key<>(name, defaultValue); + public static Key keyWithDefault(String debugString, T defaultValue) { + return new Key<>(debugString, defaultValue); } /** @@ -185,18 +189,16 @@ public static Context current() { return current; } - private ArrayList listeners; - private CancellationListener parentListener = new ParentListener(); final CancellableContext cancellableAncestor; - final PersistentHashArrayMappedTrie, Object> keyValueEntries; + final Node, Object> keyValueEntries; // The number parents between this context and the root context. final int generation; /** * Construct a context that cannot be cancelled and will not cascade cancellation from its parent. */ - private Context(PersistentHashArrayMappedTrie, Object> keyValueEntries, int generation) { - cancellableAncestor = null; + private Context(Node, Object> keyValueEntries, int generation) { + this.cancellableAncestor = null; this.keyValueEntries = keyValueEntries; this.generation = generation; validateGeneration(generation); @@ -206,10 +208,20 @@ private Context(PersistentHashArrayMappedTrie, Object> keyValueEntries, i * Construct a context that cannot be cancelled but will cascade cancellation from its parent if * it is cancellable. */ - private Context(Context parent, PersistentHashArrayMappedTrie, Object> keyValueEntries) { - cancellableAncestor = cancellableAncestor(parent); + private Context(Context parent, Node, Object> keyValueEntries) { + this.cancellableAncestor = cancellableAncestor(parent); this.keyValueEntries = keyValueEntries; - this.generation = parent == null ? 0 : parent.generation + 1; + this.generation = parent.generation + 1; + validateGeneration(generation); + } + + /** + * Construct for {@link #ROOT}. + */ + private Context() { + this.cancellableAncestor = null; + this.keyValueEntries = null; + this.generation = 0; validateGeneration(generation); } @@ -341,7 +353,8 @@ public CancellableContext withDeadline(Deadline newDeadline, ScheduledExecutorSe * are unrelated, have separate keys for them. */ public Context withValue(Key k1, V v1) { - PersistentHashArrayMappedTrie, Object> newKeyValueEntries = keyValueEntries.put(k1, v1); + Node, Object> newKeyValueEntries = + PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1); return new Context(this, newKeyValueEntries); } @@ -350,8 +363,9 @@ public Context withValue(Key k1, V v1) { * from its parent. */ public Context withValues(Key k1, V1 v1, Key k2, V2 v2) { - PersistentHashArrayMappedTrie, Object> newKeyValueEntries = - keyValueEntries.put(k1, v1).put(k2, v2); + Node, Object> newKeyValueEntries = + PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1); + newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k2, v2); return new Context(this, newKeyValueEntries); } @@ -360,8 +374,10 @@ public Context withValues(Key k1, V1 v1, Key k2, V2 v2) { * from its parent. */ public Context withValues(Key k1, V1 v1, Key k2, V2 v2, Key k3, V3 v3) { - PersistentHashArrayMappedTrie, Object> newKeyValueEntries = - keyValueEntries.put(k1, v1).put(k2, v2).put(k3, v3); + Node, Object> newKeyValueEntries = + PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1); + newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k2, v2); + newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k3, v3); return new Context(this, newKeyValueEntries); } @@ -385,8 +401,11 @@ public Context withValues(Key k1, V1 v1, Key k2, V2 v2, Key */ public Context withValues(Key k1, V1 v1, Key k2, V2 v2, Key k3, V3 v3, Key k4, V4 v4) { - PersistentHashArrayMappedTrie, Object> newKeyValueEntries = - keyValueEntries.put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4); + Node, Object> newKeyValueEntries = + PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1); + newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k2, v2); + newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k3, v3); + newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k4, v4); return new Context(this, newKeyValueEntries); } @@ -398,10 +417,6 @@ public Context fork() { return new Context(keyValueEntries, generation + 1); } - boolean canBeCancelled() { - return cancellableAncestor != null; - } - /** * Attach this context, thus enter a new scope within which this context is {@link #current}. The * previously current context is returned. It is allowed to attach contexts where {@link @@ -498,98 +513,30 @@ public void addListener(final CancellationListener cancellationListener, final Executor executor) { checkNotNull(cancellationListener, "cancellationListener"); checkNotNull(executor, "executor"); - if (canBeCancelled()) { - ExecutableListener executableListener = - new ExecutableListener(executor, cancellationListener); - synchronized (this) { - if (isCancelled()) { - executableListener.deliver(); - } else { - if (listeners == null) { - // Now that we have a listener we need to listen to our parent so - // we can cascade listener notification. - listeners = new ArrayList<>(); - listeners.add(executableListener); - if (cancellableAncestor != null) { - cancellableAncestor.addListener(parentListener, DirectExecutor.INSTANCE); - } - } else { - listeners.add(executableListener); - } - } - } + if (cancellableAncestor == null) { + return; } + cancellableAncestor.addListenerInternal( + new ExecutableListener(executor, cancellationListener, this)); } /** * Remove a {@link CancellationListener}. */ public void removeListener(CancellationListener cancellationListener) { - if (!canBeCancelled()) { - return; - } - synchronized (this) { - if (listeners != null) { - for (int i = listeners.size() - 1; i >= 0; i--) { - if (listeners.get(i).listener == cancellationListener) { - listeners.remove(i); - // Just remove the first matching listener, given that we allow duplicate - // adds we should allow for duplicates after remove. - break; - } - } - // We have no listeners so no need to listen to our parent - if (listeners.isEmpty()) { - if (cancellableAncestor != null) { - cancellableAncestor.removeListener(parentListener); - } - listeners = null; - } - } - } - } - - /** - * Notify all listeners that this context has been cancelled and immediately release - * any reference to them so that they may be garbage collected. - */ - void notifyAndClearListeners() { - if (!canBeCancelled()) { + if (cancellableAncestor == null) { return; } - ArrayList tmpListeners; - synchronized (this) { - if (listeners == null) { - return; - } - tmpListeners = listeners; - listeners = null; - } - // Deliver events to non-child context listeners before we notify child contexts. We do this - // to cancel higher level units of work before child units. This allows for a better error - // handling paradigm where the higher level unit of work knows it is cancelled and so can - // ignore errors that bubble up as a result of cancellation of lower level units. - for (int i = 0; i < tmpListeners.size(); i++) { - if (!(tmpListeners.get(i).listener instanceof ParentListener)) { - tmpListeners.get(i).deliver(); - } - } - for (int i = 0; i < tmpListeners.size(); i++) { - if (tmpListeners.get(i).listener instanceof ParentListener) { - tmpListeners.get(i).deliver(); - } - } - if (cancellableAncestor != null) { - cancellableAncestor.removeListener(parentListener); - } + cancellableAncestor.removeListenerInternal(cancellationListener, this); } // Used in tests to ensure that listeners are defined and released when cancellation cascades. // It's very important to ensure that we do not accidentally retain listeners. int listenerCount() { - synchronized (this) { - return listeners == null ? 0 : listeners.size(); + if (cancellableAncestor == null) { + return 0; } + return cancellableAncestor.listenerCount(); } /** @@ -693,13 +640,6 @@ public void execute(Runnable r) { return new CurrentContextExecutor(); } - /** - * Lookup the value for a key in the context inheritance chain. - */ - Object lookup(Key key) { - return keyValueEntries.get(key); - } - /** * A context which inherits cancellation from its parent but which can also be independently * cancelled and which will propagate cancellation to its descendants. To avoid leaking memory, @@ -732,9 +672,13 @@ public static final class CancellableContext extends Context implements Closeabl private final Deadline deadline; private final Context uncancellableSurrogate; - private boolean cancelled; + private ArrayList listeners; + // parentListener is initialized when listeners is initialized (only if there is a + // cancellable ancestor), and uninitialized when listeners is uninitialized. + private CancellationListener parentListener; private Throwable cancellationCause; private ScheduledFuture pendingDeadline; + private boolean cancelled; /** * Create a cancellable context that does not have a deadline. @@ -788,6 +732,73 @@ public void detach(Context toAttach) { uncancellableSurrogate.detach(toAttach); } + @Override + public void addListener( + final CancellationListener cancellationListener, final Executor executor) { + checkNotNull(cancellationListener, "cancellationListener"); + checkNotNull(executor, "executor"); + addListenerInternal(new ExecutableListener(executor, cancellationListener, this)); + } + + private void addListenerInternal(ExecutableListener executableListener) { + synchronized (this) { + if (isCancelled()) { + executableListener.deliver(); + } else { + if (listeners == null) { + // Now that we have a listener we need to listen to our parent so + // we can cascade listener notification. + listeners = new ArrayList<>(); + listeners.add(executableListener); + if (cancellableAncestor != null) { + parentListener = + new CancellationListener() { + @Override + public void cancelled(Context context) { + CancellableContext.this.cancel(context.cancellationCause()); + } + }; + cancellableAncestor.addListenerInternal( + new ExecutableListener(DirectExecutor.INSTANCE, parentListener, this)); + } + } else { + listeners.add(executableListener); + } + } + } + } + + @Override + public void removeListener(CancellationListener cancellationListener) { + removeListenerInternal(cancellationListener, this); + } + + private void removeListenerInternal(CancellationListener cancellationListener, + Context context) { + synchronized (this) { + if (listeners != null) { + for (int i = listeners.size() - 1; i >= 0; i--) { + ExecutableListener executableListener = listeners.get(i); + if (executableListener.listener == cancellationListener + && executableListener.context == context) { + listeners.remove(i); + // Just remove the first matching listener, given that we allow duplicate + // adds we should allow for duplicates after remove. + break; + } + } + // We have no listeners so no need to listen to our parent + if (listeners.isEmpty()) { + if (cancellableAncestor != null) { + cancellableAncestor.removeListener(parentListener); + } + parentListener = null; + listeners = null; + } + } + } + } + /** * Returns true if the Context is the current context. * @@ -833,6 +844,48 @@ public boolean cancel(Throwable cause) { return triggeredCancel; } + /** + * Notify all listeners that this context has been cancelled and immediately release + * any reference to them so that they may be garbage collected. + */ + private void notifyAndClearListeners() { + ArrayList tmpListeners; + CancellationListener tmpParentListener; + synchronized (this) { + if (listeners == null) { + return; + } + tmpParentListener = parentListener; + parentListener = null; + tmpListeners = listeners; + listeners = null; + } + // Deliver events to this context listeners before we notify child contexts. We do this + // to cancel higher level units of work before child units. This allows for a better error + // handling paradigm where the higher level unit of work knows it is cancelled and so can + // ignore errors that bubble up as a result of cancellation of lower level units. + for (ExecutableListener tmpListener : tmpListeners) { + if (tmpListener.context == this) { + tmpListener.deliver(); + } + } + for (ExecutableListener tmpListener : tmpListeners) { + if (!(tmpListener.context == this)) { + tmpListener.deliver(); + } + } + if (cancellableAncestor != null) { + cancellableAncestor.removeListener(tmpParentListener); + } + } + + @Override + int listenerCount() { + synchronized (this) { + return listeners == null ? 0 : listeners.size(); + } + } + /** * Cancel this context and detach it as the current context. * @@ -876,11 +929,6 @@ public Deadline getDeadline() { return deadline; } - @Override - boolean canBeCancelled() { - return true; - } - /** * Cleans up this object by calling {@code cancel(null)}. */ @@ -929,7 +977,7 @@ public T get() { */ @SuppressWarnings("unchecked") public T get(Context context) { - T value = (T) context.lookup(this); + T value = (T) PersistentHashArrayMappedTrie.get(context.keyValueEntries, this); return value == null ? defaultValue : value; } @@ -1010,13 +1058,15 @@ public Context doAttach(Context toAttach) { /** * Stores listener and executor pair. */ - private final class ExecutableListener implements Runnable { + private static final class ExecutableListener implements Runnable { private final Executor executor; final CancellationListener listener; + private final Context context; - ExecutableListener(Executor executor, CancellationListener listener) { + ExecutableListener(Executor executor, CancellationListener listener, Context context) { this.executor = executor; this.listener = listener; + this.context = context; } void deliver() { @@ -1029,19 +1079,7 @@ void deliver() { @Override public void run() { - listener.cancelled(Context.this); - } - } - - private final class ParentListener implements CancellationListener { - @Override - public void cancelled(Context context) { - if (Context.this instanceof CancellableContext) { - // Record cancellation with its cancellationCause. - ((CancellableContext) Context.this).cancel(context.cancellationCause()); - } else { - notifyAndClearListeners(); - } + listener.cancelled(context); } } @@ -1072,9 +1110,6 @@ public String toString() { * {@link #cancellableAncestor}. */ static CancellableContext cancellableAncestor(Context parent) { - if (parent == null) { - return null; - } if (parent instanceof CancellableContext) { return (CancellableContext) parent; } diff --git a/context/src/main/java/io/grpc/PersistentHashArrayMappedTrie.java b/context/src/main/java/io/grpc/PersistentHashArrayMappedTrie.java index 72ef37bf914..66ee8697226 100644 --- a/context/src/main/java/io/grpc/PersistentHashArrayMappedTrie.java +++ b/context/src/main/java/io/grpc/PersistentHashArrayMappedTrie.java @@ -29,28 +29,14 @@ * Bagwell (2000). The rest of the implementation is ignorant of/ignores the * paper. */ -final class PersistentHashArrayMappedTrie { - private final Node root; +final class PersistentHashArrayMappedTrie { - PersistentHashArrayMappedTrie() { - this(null); - } - - private PersistentHashArrayMappedTrie(Node root) { - this.root = root; - } - - public int size() { - if (root == null) { - return 0; - } - return root.size(); - } + private PersistentHashArrayMappedTrie() {} /** * Returns the value with the specified key, or {@code null} if it does not exist. */ - public V get(K key) { + static V get(Node root, K key) { if (root == null) { return null; } @@ -60,12 +46,11 @@ public V get(K key) { /** * Returns a new trie where the key is set to the specified value. */ - public PersistentHashArrayMappedTrie put(K key, V value) { + static Node put(Node root, K key, V value) { if (root == null) { - return new PersistentHashArrayMappedTrie<>(new Leaf<>(key, value)); - } else { - return new PersistentHashArrayMappedTrie<>(root.put(key, value, key.hashCode(), 0)); + return new Leaf<>(key, value); } + return root.put(key, value, key.hashCode(), 0); } // Not actually annotated to avoid depending on guava diff --git a/context/src/test/java/io/grpc/ContextTest.java b/context/src/test/java/io/grpc/ContextTest.java index 47b9dff0a5e..bf078fcff92 100644 --- a/context/src/test/java/io/grpc/ContextTest.java +++ b/context/src/test/java/io/grpc/ContextTest.java @@ -33,6 +33,9 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -71,30 +74,31 @@ public class ContextTest { private Context listenerNotifedContext; private CountDownLatch deadlineLatch = new CountDownLatch(1); - private Context.CancellationListener cancellationListener = new Context.CancellationListener() { - @Override - public void cancelled(Context context) { - listenerNotifedContext = context; - deadlineLatch.countDown(); - } - }; + private final Context.CancellationListener cancellationListener = + new Context.CancellationListener() { + @Override + public void cancelled(Context context) { + listenerNotifedContext = context; + deadlineLatch.countDown(); + } + }; private Context observed; - private Runnable runner = new Runnable() { + private final Runnable runner = new Runnable() { @Override public void run() { observed = Context.current(); } }; - private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); @Before - public void setUp() throws Exception { + public void setUp() { Context.ROOT.attach(); } @After - public void tearDown() throws Exception { + public void tearDown() { scheduler.shutdown(); assertEquals(Context.ROOT, Context.current()); } @@ -299,6 +303,35 @@ public void cancelled(Context context) { assertSame(base, observed3.get()); } + @Test + public void removeListenersFromContextAndChildContext() { + class SetContextCancellationListener implements Context.CancellationListener { + private final List observedContexts; + + SetContextCancellationListener() { + this.observedContexts = Collections.synchronizedList(new ArrayList()); + } + + @Override + public void cancelled(Context context) { + observedContexts.add(context); + } + } + + Context.CancellableContext base = Context.current().withCancellation(); + Context child = base.withValue(PET, "tiger"); + Context childOfChild = base.withValue(PET, "lion"); + final SetContextCancellationListener listener = new SetContextCancellationListener(); + base.addListener(listener, MoreExecutors.directExecutor()); + child.addListener(listener, MoreExecutors.directExecutor()); + childOfChild.addListener(listener, MoreExecutors.directExecutor()); + base.removeListener(listener); + childOfChild.removeListener(listener); + base.cancel(null); + assertEquals(1, listener.observedContexts.size()); + assertSame(child, listener.observedContexts.get(0)); + } + @Test public void exceptionOfExecutorDoesntThrow() { final AtomicReference loggedThrowable = new AtomicReference<>(); @@ -403,7 +436,6 @@ public void cancellableContextIsAttached() { assertSame("fish", FOOD.get()); assertFalse(attached.isCancelled()); assertNull(attached.cancellationCause()); - assertTrue(attached.canBeCancelled()); assertTrue(attached.isCurrent()); assertTrue(base.isCurrent()); @@ -450,7 +482,7 @@ public void nonCascadingCancellationDoesNotNotifyForked() { } @Test - public void testWrapRunnable() throws Exception { + public void testWrapRunnable() { Context base = Context.current().withValue(PET, "cat"); Context current = Context.current().withValue(PET, "fish"); current.attach(); @@ -521,7 +553,7 @@ public Object call() { } @Test - public void currentContextExecutor() throws Exception { + public void currentContextExecutor() { QueuedExecutor queuedExecutor = new QueuedExecutor(); Executor executor = Context.currentContextExecutor(queuedExecutor); Context base = Context.current().withValue(PET, "cat"); @@ -537,7 +569,7 @@ public void currentContextExecutor() throws Exception { } @Test - public void fixedContextExecutor() throws Exception { + public void fixedContextExecutor() { Context base = Context.current().withValue(PET, "cat"); QueuedExecutor queuedExecutor = new QueuedExecutor(); base.fixedContextExecutor(queuedExecutor).execute(runner); @@ -547,7 +579,7 @@ public void fixedContextExecutor() throws Exception { } @Test - public void typicalTryFinallyHandling() throws Exception { + public void typicalTryFinallyHandling() { Context base = Context.current().withValue(COLOR, "blue"); Context previous = base.attach(); try { @@ -560,7 +592,7 @@ public void typicalTryFinallyHandling() throws Exception { } @Test - public void typicalCancellableTryCatchFinallyHandling() throws Exception { + public void typicalCancellableTryCatchFinallyHandling() { Context.CancellableContext base = Context.current().withCancellation(); Context previous = base.attach(); try { @@ -830,7 +862,7 @@ public String call() { return COLOR.get(); } }); - assertEquals(null, workerThreadVal.get()); + assertNull(workerThreadVal.get()); assertEquals("blue", COLOR.get()); return null; @@ -890,11 +922,8 @@ public Context current() { @Test public void cancellableAncestorTest() { - assertEquals(null, cancellableAncestor(null)); - Context c = Context.current(); - assertFalse(c.canBeCancelled()); - assertEquals(null, cancellableAncestor(c)); + assertNull(cancellableAncestor(c)); Context.CancellableContext withCancellation = c.withCancellation(); assertEquals(withCancellation, cancellableAncestor(withCancellation)); @@ -935,7 +964,7 @@ public void cancellableAncestorFork() { } @Test - public void cancellableContext_closeCancelsWithNullCause() throws Exception { + public void cancellableContext_closeCancelsWithNullCause() { Context.CancellableContext cancellable = Context.current().withCancellation(); cancellable.close(); assertTrue(cancellable.isCancelled()); diff --git a/core/build.gradle b/core/build.gradle index e4a218cf3bb..8ae917db506 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,5 +1,5 @@ plugins { - id "java" + id "java-library" id "maven-publish" id "me.champeau.gradle.japicmp" @@ -13,15 +13,14 @@ evaluationDependsOn(project(':grpc-context').path) evaluationDependsOn(project(':grpc-api').path) dependencies { - compile project(':grpc-api'), - libraries.gson, + api project(':grpc-api') + implementation libraries.gson, libraries.android_annotations, - libraries.errorprone // prefer our version to perfmark's 2.3.3 - compile (libraries.perfmark) { - exclude group: 'com.google.errorprone', module: 'error_prone_annotations' - } - - testCompile project(':grpc-context').sourceSets.test.output, + libraries.animalsniffer_annotations, + libraries.errorprone + guavaDependency 'implementation' + perfmarkDependency 'implementation' + testImplementation project(':grpc-context').sourceSets.test.output, project(':grpc-api').sourceSets.test.output, project(':grpc-testing'), project(':grpc-grpclb'), @@ -57,3 +56,9 @@ plugins.withId("java") { options.errorprone.check("UnnecessaryAnonymousClass", CheckSeverity.OFF) } } + +task versionFile() { + doLast { + new File(buildDir, "version").write("${project.version}\n") + } +} diff --git a/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java b/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java index 792257758b0..3546e33357c 100644 --- a/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java +++ b/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java @@ -70,6 +70,7 @@ public static InProcessChannelBuilder forAddress(String name, int port) { private final String name; private ScheduledExecutorService scheduledExecutorService; private int maxInboundMetadataSize = Integer.MAX_VALUE; + private boolean transportIncludeStatusCause = false; private InProcessChannelBuilder(String name) { super(new InProcessSocketAddress(name), "localhost"); @@ -157,11 +158,30 @@ public InProcessChannelBuilder maxInboundMetadataSize(int bytes) { return this; } + /** + * Sets whether to include the cause with the status that is propagated + * forward from the InProcessTransport. This was added to make debugging failing + * tests easier by showing the cause of the status. + * + *

    By default, this is set to false. + * A default value of false maintains consistency with other transports which strip causal + * information from the status to avoid leaking information to untrusted clients, and + * to avoid sharing language-specific information with the client. + * For the in-process implementation, this is not a concern. + * + * @param enable whether to include cause in status + * @return this + */ + public InProcessChannelBuilder propagateCauseWithStatus(boolean enable) { + this.transportIncludeStatusCause = enable; + return this; + } + @Override @Internal protected ClientTransportFactory buildTransportFactory() { return new InProcessClientTransportFactory( - name, scheduledExecutorService, maxInboundMetadataSize); + name, scheduledExecutorService, maxInboundMetadataSize, transportIncludeStatusCause); } /** @@ -173,16 +193,18 @@ static final class InProcessClientTransportFactory implements ClientTransportFac private final boolean useSharedTimer; private final int maxInboundMetadataSize; private boolean closed; + private final boolean includeCauseWithStatus; private InProcessClientTransportFactory( String name, @Nullable ScheduledExecutorService scheduledExecutorService, - int maxInboundMetadataSize) { + int maxInboundMetadataSize, boolean includeCauseWithStatus) { this.name = name; useSharedTimer = scheduledExecutorService == null; timerService = useSharedTimer ? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : scheduledExecutorService; this.maxInboundMetadataSize = maxInboundMetadataSize; + this.includeCauseWithStatus = includeCauseWithStatus; } @Override @@ -194,7 +216,7 @@ public ConnectionClientTransport newClientTransport( // TODO(carl-mastrangelo): Pass channelLogger in. return new InProcessTransport( name, maxInboundMetadataSize, options.getAuthority(), options.getUserAgent(), - options.getEagAttributes()); + options.getEagAttributes(), includeCauseWithStatus); } @Override diff --git a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java index 448d6913066..3461eeebc0f 100644 --- a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java +++ b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java @@ -83,6 +83,7 @@ final class InProcessTransport implements ServerTransport, ConnectionClientTrans private final String userAgent; private final Optional optionalServerListener; private int serverMaxInboundMetadataSize; + private final boolean includeCauseWithStatus; private ObjectPool serverSchedulerPool; private ScheduledExecutorService serverScheduler; private ServerTransportListener serverTransportListener; @@ -115,7 +116,8 @@ protected void handleNotInUse() { }; private InProcessTransport(String name, int maxInboundMetadataSize, String authority, - String userAgent, Attributes eagAttrs, Optional optionalServerListener) { + String userAgent, Attributes eagAttrs, + Optional optionalServerListener, boolean includeCauseWithStatus) { this.name = name; this.clientMaxInboundMetadataSize = maxInboundMetadataSize; this.authority = authority; @@ -129,13 +131,14 @@ private InProcessTransport(String name, int maxInboundMetadataSize, String autho .build(); this.optionalServerListener = optionalServerListener; logId = InternalLogId.allocate(getClass(), name); + this.includeCauseWithStatus = includeCauseWithStatus; } public InProcessTransport( String name, int maxInboundMetadataSize, String authority, String userAgent, - Attributes eagAttrs) { + Attributes eagAttrs, boolean includeCauseWithStatus) { this(name, maxInboundMetadataSize, authority, userAgent, eagAttrs, - Optional.absent()); + Optional.absent(), includeCauseWithStatus); } InProcessTransport( @@ -143,7 +146,8 @@ public InProcessTransport( Attributes eagAttrs, ObjectPool serverSchedulerPool, List serverStreamTracerFactories, ServerListener serverListener) { - this(name, maxInboundMetadataSize, authority, userAgent, eagAttrs, Optional.of(serverListener)); + this(name, maxInboundMetadataSize, authority, userAgent, eagAttrs, + Optional.of(serverListener), false); this.serverMaxInboundMetadataSize = maxInboundMetadataSize; this.serverSchedulerPool = serverSchedulerPool; this.serverStreamTracerFactories = serverStreamTracerFactories; @@ -564,7 +568,7 @@ public void close(Status status, Metadata trailers) { /** clientStream.serverClosed() must be called before this method */ private void notifyClientClose(Status status, Metadata trailers) { - Status clientStatus = stripCause(status); + Status clientStatus = cleanStatus(status, includeCauseWithStatus); synchronized (this) { if (closed) { return; @@ -744,7 +748,7 @@ public synchronized boolean isReady() { // Must be thread-safe for shutdownNow() @Override public void cancel(Status reason) { - Status serverStatus = stripCause(reason); + Status serverStatus = cleanStatus(reason, includeCauseWithStatus); if (!internalCancel(serverStatus, serverStatus)) { return; } @@ -843,19 +847,25 @@ public void appendTimeoutInsight(InsightBuilder insight) { } /** - * Returns a new status with the same code and description, but stripped of any other information - * (i.e. cause). + * Returns a new status with the same code and description. + * If includeCauseWithStatus is true, cause is also included. * - *

    This is, so that the InProcess transport behaves in the same way as the other transports, - * when exchanging statuses between client and server and vice versa. + *

    For InProcess transport to behave in the same way as the other transports, + * when exchanging statuses between client and server and vice versa, + * the cause should be excluded from the status. + * For easier debugging, the status may be optionally included. */ - private static Status stripCause(Status status) { + private static Status cleanStatus(Status status, boolean includeCauseWithStatus) { if (status == null) { return null; } - return Status + Status clientStatus = Status .fromCodeValue(status.getCode().value()) .withDescription(status.getDescription()); + if (includeCauseWithStatus) { + clientStatus = clientStatus.withCause(status.getCause()); + } + return clientStatus; } private static class SingleMessageProducer implements StreamListener.MessageProducer { diff --git a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java index 6bc54d62d05..cf47a4d9f92 100644 --- a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java @@ -511,7 +511,6 @@ public ManagedChannel build() { return new ManagedChannelOrphanWrapper(new ManagedChannelImpl( this, buildTransportFactory(), - // TODO(carl-mastrangelo): Allow clients to pass this in new ExponentialBackoffPolicy.Provider(), SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR), GrpcUtil.STOPWATCH_SUPPLIER, diff --git a/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java index 6928434df3c..21a1becc284 100644 --- a/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java @@ -29,7 +29,6 @@ import io.grpc.DecompressorRegistry; import io.grpc.HandlerRegistry; import io.grpc.InternalChannelz; -import io.grpc.InternalNotifyOnServerBuild; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerInterceptor; @@ -77,7 +76,6 @@ public static ServerBuilder forPort(int port) { new InternalHandlerRegistry.Builder(); final List transportFilters = new ArrayList<>(); final List interceptors = new ArrayList<>(); - private final List notifyOnBuildList = new ArrayList<>(); private final List streamTracerFactories = new ArrayList<>(); HandlerRegistry fallbackRegistry = DEFAULT_FALLBACK_REGISTRY; ObjectPool executorPool = DEFAULT_EXECUTOR_POOL; @@ -114,9 +112,6 @@ public final T addService(ServerServiceDefinition service) { @Override public final T addService(BindableService bindableService) { - if (bindableService instanceof InternalNotifyOnServerBuild) { - notifyOnBuildList.add((InternalNotifyOnServerBuild) bindableService); - } return addService(checkNotNull(bindableService, "bindableService").bindService()); } @@ -222,14 +217,7 @@ protected void setDeadlineTicker(Deadline.Ticker ticker) { @Override public final Server build() { - ServerImpl server = new ServerImpl( - this, - buildTransportServers(getTracerFactories()), - Context.ROOT); - for (InternalNotifyOnServerBuild notifyTarget : notifyOnBuildList) { - notifyTarget.notifyOnBuild(server); - } - return server; + return new ServerImpl(this, buildTransportServers(getTracerFactories()), Context.ROOT); } @VisibleForTesting diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 517cc267d1b..40bd17d2d38 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -196,7 +196,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.29.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.30.0"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java index d84c7710384..97be3f26e0e 100644 --- a/core/src/main/java/io/grpc/internal/InternalSubchannel.java +++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java @@ -111,6 +111,10 @@ final class InternalSubchannel implements InternalInstrumented, Tr @Nullable private ScheduledHandle reconnectTask; + @Nullable + private ScheduledHandle shutdownDueToUpdateTask; + @Nullable + private ManagedClientTransport shutdownDueToUpdateTransport; /** * All transports that are not terminated. At the very least the value of {@link #activeTransport} @@ -354,7 +358,9 @@ public void run() { addressIndex.reset(); gotoNonErrorState(IDLE); } else { - savedTransport = pendingTransport; + pendingTransport.shutdown( + Status.UNAVAILABLE.withDescription( + "InternalSubchannel closed pending transport due to address change")); pendingTransport = null; addressIndex.reset(); startNewTransport(); @@ -362,9 +368,33 @@ public void run() { } } if (savedTransport != null) { - savedTransport.shutdown( - Status.UNAVAILABLE.withDescription( - "InternalSubchannel closed transport due to address change")); + if (shutdownDueToUpdateTask != null) { + // Keeping track of multiple shutdown tasks adds complexity, and shouldn't generally be + // necessary. This transport has probably already had plenty of time. + shutdownDueToUpdateTransport.shutdown( + Status.UNAVAILABLE.withDescription( + "InternalSubchannel closed transport early due to address change")); + shutdownDueToUpdateTask.cancel(); + shutdownDueToUpdateTask = null; + shutdownDueToUpdateTransport = null; + } + // Avoid needless RPC failures by delaying the shutdown. See + // https://github.com/grpc/grpc-java/issues/2562 + shutdownDueToUpdateTransport = savedTransport; + shutdownDueToUpdateTask = syncContext.schedule( + new Runnable() { + @Override public void run() { + ManagedClientTransport transport = shutdownDueToUpdateTransport; + shutdownDueToUpdateTask = null; + shutdownDueToUpdateTransport = null; + transport.shutdown( + Status.UNAVAILABLE.withDescription( + "InternalSubchannel closed transport due to address change")); + } + }, + ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, + TimeUnit.SECONDS, + scheduledExecutor); } } }); @@ -390,6 +420,12 @@ public void run() { handleTermination(); } // else: the callback will be run once all transports have been terminated cancelReconnectTask(); + if (shutdownDueToUpdateTask != null) { + shutdownDueToUpdateTask.cancel(); + shutdownDueToUpdateTransport.shutdown(reason); + shutdownDueToUpdateTask = null; + shutdownDueToUpdateTransport = null; + } if (savedActiveTransport != null) { savedActiveTransport.shutdown(reason); } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 94473a9bf5a..05dee80c6ea 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -114,7 +114,6 @@ final class ManagedChannelImpl extends ManagedChannel implements static final long IDLE_TIMEOUT_MILLIS_DISABLE = -1; - @VisibleForTesting static final long SUBCHANNEL_SHUTDOWN_DELAY_SECONDS = 5; @VisibleForTesting @@ -1260,6 +1259,49 @@ public void run() { return oobChannel; } + @Override + public ManagedChannel createResolvingOobChannel(String target) { + final class ResolvingOobChannelBuilder + extends AbstractManagedChannelImplBuilder { + int defaultPort = -1; + + ResolvingOobChannelBuilder(String target) { + super(target); + } + + @Override + public int getDefaultPort() { + return defaultPort; + } + + @Override + protected ClientTransportFactory buildTransportFactory() { + throw new UnsupportedOperationException(); + } + } + + checkState(!terminated, "Channel is terminated"); + + ResolvingOobChannelBuilder builder = new ResolvingOobChannelBuilder(target); + builder.offloadExecutorPool = offloadExecutorHolder.pool; + builder.overrideAuthority(getAuthority()); + builder.nameResolverFactory(nameResolverFactory); + builder.executorPool = executorPool; + builder.maxTraceEvents = maxTraceEvents; + builder.proxyDetector = nameResolverArgs.getProxyDetector(); + builder.defaultPort = nameResolverArgs.getDefaultPort(); + builder.userAgent = userAgent; + return + new ManagedChannelImpl( + builder, + transportFactory, + backoffPolicyProvider, + balancerRpcExecutorPool, + stopwatchSupplier, + Collections.emptyList(), + timeProvider); + } + @Override public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressGroup eag) { checkArgument(channel instanceof OobChannel, @@ -1327,7 +1369,6 @@ public void run() { "Resolved address: {0}, config={1}", servers, resolutionResult.getAttributes()); - ResolutionState lastResolutionStateCopy = lastResolutionState; if (lastResolutionState != ResolutionState.SUCCESS) { channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); @@ -1415,14 +1456,7 @@ public void run() { .build()); if (!handleResult.isOk()) { - if (servers.isEmpty() && lastResolutionStateCopy == ResolutionState.SUCCESS) { - // lb doesn't expose that it needs address or not, because for some LB it is not - // deterministic. Assuming lb needs address if LB returns error when the address is - // empty and it is not the first resolution. - scheduleExponentialBackOffInSyncContext(); - } else { - handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used")); - } + handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used")); } } } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java b/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java index a5d4accd02f..fc78d2231a0 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelServiceConfig.java @@ -37,6 +37,8 @@ */ final class ManagedChannelServiceConfig { + @Nullable + private final MethodInfo defaultMethodConfig; private final Map serviceMethodMap; private final Map serviceMap; @Nullable @@ -47,11 +49,13 @@ final class ManagedChannelServiceConfig { private final Map healthCheckingConfig; ManagedChannelServiceConfig( + @Nullable MethodInfo defaultMethodConfig, Map serviceMethodMap, Map serviceMap, @Nullable Throttle retryThrottling, @Nullable Object loadBalancingConfig, @Nullable Map healthCheckingConfig) { + this.defaultMethodConfig = defaultMethodConfig; this.serviceMethodMap = Collections.unmodifiableMap(new HashMap<>(serviceMethodMap)); this.serviceMap = Collections.unmodifiableMap(new HashMap<>(serviceMap)); this.retryThrottling = retryThrottling; @@ -66,6 +70,7 @@ final class ManagedChannelServiceConfig { static ManagedChannelServiceConfig empty() { return new ManagedChannelServiceConfig( + null, new HashMap(), new HashMap(), /* retryThrottling= */ null, @@ -100,6 +105,7 @@ static ManagedChannelServiceConfig fromServiceConfig( // this is surprising, but possible. return new ManagedChannelServiceConfig( + null, serviceMethodMap, serviceMap, retryThrottling, @@ -107,6 +113,7 @@ static ManagedChannelServiceConfig fromServiceConfig( healthCheckingConfig); } + MethodInfo defaultMethodConfig = null; for (Map methodConfig : methodConfigs) { MethodInfo info = new MethodInfo( methodConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit); @@ -114,13 +121,21 @@ static ManagedChannelServiceConfig fromServiceConfig( List> nameList = ServiceConfigUtil.getNameListFromMethodConfig(methodConfig); - checkArgument( - nameList != null && !nameList.isEmpty(), "no names in method config %s", methodConfig); + if (nameList == null || nameList.isEmpty()) { + continue; + } for (Map name : nameList) { String serviceName = ServiceConfigUtil.getServiceFromName(name); - checkArgument(!Strings.isNullOrEmpty(serviceName), "missing service name"); String methodName = ServiceConfigUtil.getMethodFromName(name); - if (Strings.isNullOrEmpty(methodName)) { + if (Strings.isNullOrEmpty(serviceName)) { + checkArgument( + Strings.isNullOrEmpty(methodName), "missing service name for method %s", methodName); + checkArgument( + defaultMethodConfig == null, + "Duplicate default method config in service config %s", + serviceConfig); + defaultMethodConfig = info; + } else if (Strings.isNullOrEmpty(methodName)) { // Service scoped config checkArgument( !serviceMap.containsKey(serviceName), "Duplicate service %s", serviceName); @@ -139,6 +154,7 @@ static ManagedChannelServiceConfig fromServiceConfig( return new ManagedChannelServiceConfig( + defaultMethodConfig, serviceMethodMap, serviceMap, retryThrottling, @@ -165,6 +181,11 @@ Map getServiceMethodMap() { return serviceMethodMap; } + @Nullable + MethodInfo getDefaultMethodConfig() { + return defaultMethodConfig; + } + @VisibleForTesting @Nullable Object getLoadBalancingConfig() { diff --git a/core/src/main/java/io/grpc/internal/PickSubchannelArgsImpl.java b/core/src/main/java/io/grpc/internal/PickSubchannelArgsImpl.java index b6ec5bfac5d..dd938303dee 100644 --- a/core/src/main/java/io/grpc/internal/PickSubchannelArgsImpl.java +++ b/core/src/main/java/io/grpc/internal/PickSubchannelArgsImpl.java @@ -24,7 +24,8 @@ import io.grpc.Metadata; import io.grpc.MethodDescriptor; -final class PickSubchannelArgsImpl extends PickSubchannelArgs { +/** Implementation of {@link PickSubchannelArgs}. */ +public final class PickSubchannelArgsImpl extends PickSubchannelArgs { private final CallOptions callOptions; private final Metadata headers; private final MethodDescriptor method; @@ -32,7 +33,8 @@ final class PickSubchannelArgsImpl extends PickSubchannelArgs { /** * Creates call args object for given method with its call options, metadata. */ - PickSubchannelArgsImpl(MethodDescriptor method, Metadata headers, CallOptions callOptions) { + public PickSubchannelArgsImpl( + MethodDescriptor method, Metadata headers, CallOptions callOptions) { this.method = checkNotNull(method, "method"); this.headers = checkNotNull(headers, "headers"); this.callOptions = checkNotNull(callOptions, "callOptions"); diff --git a/core/src/main/java/io/grpc/internal/ServerImpl.java b/core/src/main/java/io/grpc/internal/ServerImpl.java index 6e9cb9bf5ec..05c16e45bfc 100644 --- a/core/src/main/java/io/grpc/internal/ServerImpl.java +++ b/core/src/main/java/io/grpc/internal/ServerImpl.java @@ -161,7 +161,6 @@ public final class ServerImpl extends io.grpc.Server implements InternalInstrume this.channelz = builder.channelz; this.serverCallTracer = builder.callTracerFactory.create(); this.ticker = checkNotNull(builder.ticker, "ticker"); - channelz.addServer(this); } @@ -593,7 +592,10 @@ private Context.CancellableContext createContext( Metadata headers, StatsTraceContext statsTraceCtx) { Long timeoutNanos = headers.get(TIMEOUT_KEY); - Context baseContext = statsTraceCtx.serverFilterContext(rootContext); + Context baseContext = + statsTraceCtx + .serverFilterContext(rootContext) + .withValue(io.grpc.InternalServer.SERVER_CONTEXT_KEY, ServerImpl.this); if (timeoutNanos == null) { return baseContext.withCancellation(); @@ -759,9 +761,9 @@ void setListener(ServerStreamListener listener) { /** * Like {@link ServerCall#close(Status, Metadata)}, but thread-safe for internal use. */ - private void internalClose() { + private void internalClose(Throwable t) { // TODO(ejona86): this is not thread-safe :) - stream.close(Status.UNKNOWN, new Metadata()); + stream.close(Status.UNKNOWN.withCause(t), new Metadata()); } @Override @@ -782,10 +784,10 @@ public void runInContext() { try { getListener().messagesAvailable(producer); } catch (RuntimeException e) { - internalClose(); + internalClose(e); throw e; } catch (Error e) { - internalClose(); + internalClose(e); throw e; } finally { PerfMark.stopTask("ServerCallListener(app).messagesAvailable", tag); @@ -817,10 +819,10 @@ public void runInContext() { try { getListener().halfClosed(); } catch (RuntimeException e) { - internalClose(); + internalClose(e); throw e; } catch (Error e) { - internalClose(); + internalClose(e); throw e; } finally { PerfMark.stopTask("ServerCallListener(app).halfClosed", tag); @@ -891,10 +893,10 @@ public void runInContext() { try { getListener().onReady(); } catch (RuntimeException e) { - internalClose(); + internalClose(e); throw e; } catch (Error e) { - internalClose(); + internalClose(e); throw e; } finally { PerfMark.stopTask("ServerCallListener(app).onReady", tag); diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java index f27f9efa788..9fb91db5ea7 100644 --- a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java +++ b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java @@ -174,14 +174,18 @@ public HedgingPolicy get() { @CheckForNull private MethodInfo getMethodInfo(MethodDescriptor method) { ManagedChannelServiceConfig mcsc = managedChannelServiceConfig.get(); - MethodInfo info = null; - if (mcsc != null) { - info = mcsc.getServiceMethodMap().get(method.getFullMethodName()); + if (mcsc == null) { + return null; } - if (info == null && mcsc != null) { + MethodInfo info; + info = mcsc.getServiceMethodMap().get(method.getFullMethodName()); + if (info == null) { String serviceName = method.getServiceName(); info = mcsc.getServiceMap().get(serviceName); } + if (info == null) { + info = mcsc.getDefaultMethodConfig(); + } return info; } diff --git a/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java b/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java index 3bd46cd384d..f7e325ad5a9 100644 --- a/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java +++ b/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java @@ -16,14 +16,30 @@ package io.grpc.inprocess; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + import com.google.common.collect.ImmutableList; +import io.grpc.CallOptions; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.Server; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerServiceDefinition; import io.grpc.ServerStreamTracer; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import io.grpc.internal.AbstractTransportTest; import io.grpc.internal.GrpcUtil; import io.grpc.internal.InternalServer; import io.grpc.internal.ManagedClientTransport; +import io.grpc.stub.ClientCalls; +import io.grpc.testing.GrpcCleanupRule; +import io.grpc.testing.TestMethodDescriptors; import java.util.List; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -35,6 +51,9 @@ public class InProcessTransportTest extends AbstractTransportTest { private static final String AUTHORITY = "a-testing-authority"; private static final String USER_AGENT = "a-testing-user-agent"; + @Rule + public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); + @Override protected List newServer( List streamTracerFactories) { @@ -59,7 +78,7 @@ protected String testAuthority(InternalServer server) { protected ManagedClientTransport newClientTransport(InternalServer server) { return new InProcessTransport( TRANSPORT_NAME, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, testAuthority(server), USER_AGENT, - eagAttrs()); + eagAttrs(), false); } @Override @@ -75,4 +94,42 @@ protected boolean sizesReported() { public void socketStats() throws Exception { // test does not apply to in-process } + + @Test + public void causeShouldBePropagatedWithStatus() throws Exception { + server = null; + String failingServerName = "server_foo"; + String serviceFoo = "service_foo"; + final Status s = Status.INTERNAL.withCause(new Throwable("failing server exception")); + ServerServiceDefinition definition = ServerServiceDefinition.builder(serviceFoo) + .addMethod(TestMethodDescriptors.voidMethod(), new ServerCallHandler() { + @Override + public ServerCall.Listener startCall( + ServerCall call, Metadata headers) { + call.close(s, new Metadata()); + return new ServerCall.Listener() {}; + } + }) + .build(); + Server failingServer = InProcessServerBuilder + .forName(failingServerName) + .addService(definition) + .directExecutor() + .build() + .start(); + grpcCleanupRule.register(failingServer); + ManagedChannel channel = InProcessChannelBuilder + .forName(failingServerName) + .propagateCauseWithStatus(true) + .build(); + grpcCleanupRule.register(channel); + try { + ClientCalls.blockingUnaryCall(channel, TestMethodDescriptors.voidMethod(), + CallOptions.DEFAULT, null); + fail("exception should have been thrown"); + } catch (StatusRuntimeException e) { + // When propagateCauseWithStatus is true, the cause should be sent forward + assertEquals(s.getCause(), e.getCause()); + } + } } diff --git a/core/src/test/java/io/grpc/internal/AbstractTransportTest.java b/core/src/test/java/io/grpc/internal/AbstractTransportTest.java index adbefbedef4..cca994c105a 100644 --- a/core/src/test/java/io/grpc/internal/AbstractTransportTest.java +++ b/core/src/test/java/io/grpc/internal/AbstractTransportTest.java @@ -157,7 +157,7 @@ public void log(ChannelLogLevel level, String messageFormat, Object... args) {} * {@code serverListener}, otherwise tearDown() can't wait for shutdown which can put following * tests in an indeterminate state. */ - private InternalServer server; + protected InternalServer server; private ServerTransport serverTransport; private ManagedClientTransport client; private MethodDescriptor methodDescriptor = @@ -1058,9 +1058,7 @@ public void earlyServerClose_withServerHeaders() throws Exception { Metadata clientStreamTrailers = clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertNotNull(clientStreamTrailers); - assertEquals(status.getCode(), clientStreamStatus.getCode()); - assertEquals("Hello. Goodbye.", clientStreamStatus.getDescription()); - assertNull(clientStreamStatus.getCause()); + checkClientStatus(status, clientStreamStatus); assertTrue(clientStreamTracer1.getOutboundHeaders()); assertTrue(clientStreamTracer1.getInboundHeaders()); assertSame(clientStreamTrailers, clientStreamTracer1.getInboundTrailers()); @@ -1097,10 +1095,7 @@ public void earlyServerClose_noServerHeaders() throws Exception { Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); Metadata clientStreamTrailers = clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); - assertEquals(status.getCode(), clientStreamStatus.getCode()); - assertEquals("Hellogoodbye", clientStreamStatus.getDescription()); - // Cause should not be transmitted to the client. - assertNull(clientStreamStatus.getCause()); + checkClientStatus(status, clientStreamStatus); assertEquals( Lists.newArrayList(trailers.getAll(asciiKey)), Lists.newArrayList(clientStreamTrailers.getAll(asciiKey))); @@ -1138,9 +1133,7 @@ public void earlyServerClose_serverFailure() throws Exception { Metadata clientStreamTrailers = clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertNotNull(clientStreamTrailers); - assertEquals(status.getCode(), clientStreamStatus.getCode()); - assertEquals(status.getDescription(), clientStreamStatus.getDescription()); - assertNull(clientStreamStatus.getCause()); + checkClientStatus(status, clientStreamStatus); assertTrue(clientStreamTracer1.getOutboundHeaders()); assertSame(clientStreamTrailers, clientStreamTracer1.getInboundTrailers()); assertSame(clientStreamStatus, clientStreamTracer1.getStatus()); @@ -1188,9 +1181,7 @@ public void closed( Metadata clientStreamTrailers = clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertNotNull(clientStreamTrailers); - assertEquals(status.getCode(), clientStreamStatus.getCode()); - assertEquals(status.getDescription(), clientStreamStatus.getDescription()); - assertNull(clientStreamStatus.getCause()); + checkClientStatus(status, clientStreamStatus); assertTrue(clientStreamTracer1.getOutboundHeaders()); assertSame(clientStreamTrailers, clientStreamTracer1.getInboundTrailers()); assertSame(clientStreamStatus, clientStreamTracer1.getStatus()); @@ -1219,7 +1210,7 @@ public void clientCancel() throws Exception { assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)); Status serverStatus = serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); assertNotEquals(Status.Code.OK, serverStatus.getCode()); - // Cause should not be transmitted between client and server + // Cause should not be transmitted between client and server by default assertNull(serverStatus.getCause()); clientStream.cancel(status); @@ -2072,6 +2063,16 @@ private static void assertStatusEquals(Status expected, Status actual) { } } + /** + * Verifies that the client status is as expected. By default, the code and description should + * be present, and the cause should be stripped away. + */ + private static void checkClientStatus(Status expectedStatus, Status clientStreamStatus) { + assertEquals(expectedStatus.getCode(), clientStreamStatus.getCode()); + assertEquals(expectedStatus.getDescription(), clientStreamStatus.getDescription()); + assertNull(clientStreamStatus.getCause()); + } + private static boolean waitForFuture(Future future, long timeout, TimeUnit unit) throws InterruptedException { try { diff --git a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java index 985be0f68a9..37099995835 100644 --- a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java +++ b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java @@ -57,6 +57,7 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Before; @@ -700,6 +701,9 @@ public void updateAddresses_eagListWithNull_throws() { Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr3, addr4)))); assertExactCallbackInvokes("onStateChange:IDLE"); assertEquals(IDLE, internalSubchannel.getState()); + verify(transports.peek().transport, never()).shutdown(any(Status.class)); + fakeClock.forwardNanos( + TimeUnit.SECONDS.toNanos(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS)); verify(transports.peek().transport).shutdown(any(Status.class)); // And new addresses chosen when re-connecting @@ -784,6 +788,59 @@ public void updateAddresses_eagListWithNull_throws() { fakeClock.forwardNanos(10); // Drain retry, but don't care about result } + @Test public void updateAddresses_disjoint_readyTwice() { + SocketAddress addr1 = mock(SocketAddress.class); + createInternalSubchannel(addr1); + assertEquals(IDLE, internalSubchannel.getState()); + + // Address connects + assertNull(internalSubchannel.obtainActiveTransport()); + assertExactCallbackInvokes("onStateChange:CONNECTING"); + verify(mockTransportFactory) + .newClientTransport( + eq(addr1), + eq(createClientTransportOptions()), + isA(TransportLogger.class)); + transports.peek().listener.transportReady(); + assertExactCallbackInvokes("onStateChange:READY"); + assertEquals(READY, internalSubchannel.getState()); + + // Update addresses + SocketAddress addr2 = mock(SocketAddress.class); + internalSubchannel.updateAddresses( + Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr2)))); + assertExactCallbackInvokes("onStateChange:IDLE"); + assertEquals(IDLE, internalSubchannel.getState()); + ConnectionClientTransport firstTransport = transports.poll().transport; + verify(firstTransport, never()).shutdown(any(Status.class)); + + // Address connects + assertNull(internalSubchannel.obtainActiveTransport()); + assertExactCallbackInvokes("onStateChange:CONNECTING"); + verify(mockTransportFactory) + .newClientTransport( + eq(addr2), + eq(createClientTransportOptions()), + isA(TransportLogger.class)); + transports.peek().listener.transportReady(); + assertExactCallbackInvokes("onStateChange:READY"); + assertEquals(READY, internalSubchannel.getState()); + + // Update addresses + SocketAddress addr3 = mock(SocketAddress.class); + internalSubchannel.updateAddresses( + Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr3)))); + assertExactCallbackInvokes("onStateChange:IDLE"); + assertEquals(IDLE, internalSubchannel.getState()); + // Earlier transport is shutdown eagerly + verify(firstTransport).shutdown(any(Status.class)); + ConnectionClientTransport secondTransport = transports.peek().transport; + verify(secondTransport, never()).shutdown(any(Status.class)); + + internalSubchannel.shutdown(SHUTDOWN_REASON); + verify(secondTransport).shutdown(any(Status.class)); + } + @Test public void connectIsLazy() { SocketAddress addr = mock(SocketAddress.class); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java index 22cd9879e85..4439383c0e0 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java @@ -367,6 +367,9 @@ public void updateSubchannelAddresses_newAddressConnects() { requestConnectionSafely(helper, subchannel); MockClientTransportInfo t1 = newTransports.poll(); t1.listener.transportReady(); + + // Drain InternalSubchannel's delayed shutdown on updateAddresses + timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS); } @Test @@ -459,6 +462,9 @@ public void updateOobChannelAddresses_newAddressConnects() { oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata()); MockClientTransportInfo t1 = newTransports.poll(); t1.listener.transportReady(); + + // Drain InternalSubchannel's delayed shutdown on updateAddresses + timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS); } @Test diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 89e5be89a4e..fb65dc45d92 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static io.grpc.ConnectivityState.CONNECTING; import static io.grpc.ConnectivityState.IDLE; import static io.grpc.ConnectivityState.READY; @@ -3948,6 +3949,28 @@ public void healthCheckingConfigPropagated() throws Exception { } } + @Test + public void createResolvingOobChannel() throws Exception { + String oobTarget = "fake://second.example.com"; + URI oobUri = new URI(oobTarget); + channelBuilder + .nameResolverFactory(new FakeNameResolverFactory.Builder(expectedUri, oobUri).build()); + createChannel(); + + ManagedChannel resolvedOobChannel = null; + try { + resolvedOobChannel = helper.createResolvingOobChannel(oobTarget); + + assertWithMessage("resolving oob channel should have same authority") + .that(resolvedOobChannel.authority()) + .isEqualTo(channel.authority()); + } finally { + if (resolvedOobChannel != null) { + resolvedOobChannel.shutdownNow(); + } + } + } + private static final class ChannelBuilder extends AbstractManagedChannelImplBuilder { @@ -3979,7 +4002,7 @@ public long nextBackoffNanos() { } private static final class FakeNameResolverFactory extends NameResolver.Factory { - final URI expectedUri; + final List expectedUris; final List servers; final boolean resolvedAtStart; final Status error; @@ -3987,11 +4010,11 @@ private static final class FakeNameResolverFactory extends NameResolver.Factory final AtomicReference nextConfigOrError = new AtomicReference<>(); FakeNameResolverFactory( - URI expectedUri, + List expectedUris, List servers, boolean resolvedAtStart, Status error) { - this.expectedUri = expectedUri; + this.expectedUris = expectedUris; this.servers = servers; this.resolvedAtStart = resolvedAtStart; this.error = error; @@ -3999,12 +4022,12 @@ private static final class FakeNameResolverFactory extends NameResolver.Factory @Override public NameResolver newNameResolver(final URI targetUri, NameResolver.Args args) { - if (!expectedUri.equals(targetUri)) { + if (!expectedUris.contains(targetUri)) { return null; } assertEquals(DEFAULT_PORT, args.getDefaultPort()); FakeNameResolverFactory.FakeNameResolver resolver = - new FakeNameResolverFactory.FakeNameResolver(error); + new FakeNameResolverFactory.FakeNameResolver(targetUri, error); resolvers.add(resolver); return resolver; } @@ -4021,17 +4044,19 @@ void allResolved() { } final class FakeNameResolver extends NameResolver { + final URI targetUri; Listener2 listener; boolean shutdown; int refreshCalled; Status error; - FakeNameResolver(Status error) { + FakeNameResolver(URI targetUri, Status error) { + this.targetUri = targetUri; this.error = error; } @Override public String getServiceAuthority() { - return expectedUri.getAuthority(); + return targetUri.getAuthority(); } @Override public void start(Listener2 listener) { @@ -4072,13 +4097,13 @@ public String toString() { } static final class Builder { - final URI expectedUri; + List expectedUris; List servers = ImmutableList.of(); boolean resolvedAtStart = true; Status error = null; - Builder(URI expectedUri) { - this.expectedUri = expectedUri; + Builder(URI... expectedUris) { + this.expectedUris = Collections.unmodifiableList(Arrays.asList(expectedUris)); } FakeNameResolverFactory.Builder setServers(List servers) { @@ -4097,7 +4122,7 @@ FakeNameResolverFactory.Builder setError(Status error) { } FakeNameResolverFactory build() { - return new FakeNameResolverFactory(expectedUri, servers, resolvedAtStart, error); + return new FakeNameResolverFactory(expectedUris, servers, resolvedAtStart, error); } } } diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelServiceConfigTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelServiceConfigTest.java index ad6c73de8ff..5a7bad5a1b7 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelServiceConfigTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelServiceConfigTest.java @@ -18,15 +18,22 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Map; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ManagedChannelServiceConfigTest { + @Rule + public final ExpectedException thrown = ExpectedException.none(); + @Test public void managedChannelServiceConfig_shouldParseHealthCheckingConfig() throws Exception { Map rawServiceConfig = @@ -52,6 +59,83 @@ public void managedChannelServiceConfig_shouldHandleNoHealthCheckingConfig() thr assertThat(mcsc.getHealthCheckingConfig()).isNull(); } + @Test + public void createManagedChannelServiceConfig_failsOnDuplicateMethod() { + Map name1 = ImmutableMap.of("service", "service", "method", "method"); + Map name2 = ImmutableMap.of("service", "service", "method", "method"); + Map methodConfig = ImmutableMap.of("name", ImmutableList.of(name1, name2)); + Map serviceConfig = ImmutableMap.of("methodConfig", ImmutableList.of(methodConfig)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Duplicate method"); + + ManagedChannelServiceConfig.fromServiceConfig(serviceConfig, true, 3, 4, null); + } + + @Test + public void createManagedChannelServiceConfig_failsOnDuplicateService() { + Map name1 = ImmutableMap.of("service", "service"); + Map name2 = ImmutableMap.of("service", "service"); + Map methodConfig = ImmutableMap.of("name", ImmutableList.of(name1, name2)); + Map serviceConfig = ImmutableMap.of("methodConfig", ImmutableList.of(methodConfig)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Duplicate service"); + + ManagedChannelServiceConfig.fromServiceConfig(serviceConfig, true, 3, 4, null); + } + + @Test + public void createManagedChannelServiceConfig_failsOnDuplicateServiceMultipleConfig() { + Map name1 = ImmutableMap.of("service", "service"); + Map name2 = ImmutableMap.of("service", "service"); + Map methodConfig1 = ImmutableMap.of("name", ImmutableList.of(name1)); + Map methodConfig2 = ImmutableMap.of("name", ImmutableList.of(name2)); + Map serviceConfig = + ImmutableMap.of("methodConfig", ImmutableList.of(methodConfig1, methodConfig2)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Duplicate service"); + + ManagedChannelServiceConfig.fromServiceConfig(serviceConfig, true, 3, 4, null); + } + + @Test + public void createManagedChannelServiceConfig_failsOnMethodNameWithEmptyServiceName() { + Map name = ImmutableMap.of("service", "", "method", "method1"); + Map methodConfig = ImmutableMap.of("name", ImmutableList.of(name)); + Map serviceConfig = ImmutableMap.of("methodConfig", ImmutableList.of(methodConfig)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("missing service name for method method1"); + + ManagedChannelServiceConfig.fromServiceConfig(serviceConfig, true, 3, 4, null); + } + + @Test + public void createManagedChannelServiceConfig_failsOnMethodNameWithoutServiceName() { + Map name = ImmutableMap.of("method", "method1"); + Map methodConfig = ImmutableMap.of("name", ImmutableList.of(name)); + Map serviceConfig = ImmutableMap.of("methodConfig", ImmutableList.of(methodConfig)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("missing service name for method method1"); + + ManagedChannelServiceConfig.fromServiceConfig(serviceConfig, true, 3, 4, null); + } + + @Test + public void createManagedChannelServiceConfig_failsOnMissingServiceName() { + Map name = ImmutableMap.of("method", "method"); + Map methodConfig = ImmutableMap.of("name", ImmutableList.of(name)); + Map serviceConfig = ImmutableMap.of("methodConfig", ImmutableList.of(methodConfig)); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("missing service"); + + ManagedChannelServiceConfig.fromServiceConfig(serviceConfig, true, 3, 4, null); + } + @SuppressWarnings("unchecked") private static Map parseConfig(String json) throws Exception { return (Map) JsonParser.parse(json); diff --git a/core/src/test/java/io/grpc/internal/ServerImplTest.java b/core/src/test/java/io/grpc/internal/ServerImplTest.java index 2fe2692bcdb..b32833f3439 100644 --- a/core/src/test/java/io/grpc/internal/ServerImplTest.java +++ b/core/src/test/java/io/grpc/internal/ServerImplTest.java @@ -561,6 +561,7 @@ public ServerCall.Listener startCall( Context callContext = callContextReference.get(); assertNotNull(callContext); assertEquals("context added by tracer", SERVER_TRACER_ADDED_KEY.get(callContext)); + assertEquals(server, io.grpc.InternalServer.SERVER_CONTEXT_KEY.get(callContext)); streamListener.messagesAvailable(new SingleMessageProducer(STRING_MARSHALLER.stream(request))); assertEquals(1, executor.runDueTasks()); @@ -1440,8 +1441,9 @@ private void verifyExecutorsReturned() { private void ensureServerStateNotLeaked() { verify(stream).close(statusCaptor.capture(), metadataCaptor.capture()); - assertEquals(Status.UNKNOWN, statusCaptor.getValue()); - assertNull(statusCaptor.getValue().getCause()); + assertEquals(Status.UNKNOWN.getCode(), statusCaptor.getValue().getCode()); + // Used in InProcessTransport when set to include the cause with the status + assertNotNull(statusCaptor.getValue().getCause()); assertTrue(metadataCaptor.getValue().keys().isEmpty()); } diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java index f81a4c5a73d..7e61213f861 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java @@ -274,17 +274,19 @@ public void emptyAddresses_validConfig_2ndResolution_lbNeedsAddress() throws Exc assertThat(channel.getState(true)).isEqualTo(ConnectivityState.IDLE); reset(mockLoadBalancer); - Map ignoredServiceConfig = - parseJson("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); - nameResolverFactory.nextRawServiceConfig.set(ignoredServiceConfig); nameResolverFactory.servers.clear(); // 2nd resolution nameResolverFactory.allResolved(); - // 2nd service config without address should be ignored + // 2nd service config without addresses + ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); verify(mockLoadBalancer, never()).handleResolvedAddresses(any(ResolvedAddresses.class)); - verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); + verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.UNAVAILABLE); + assertThat(statusCaptor.getValue().getDescription()) + .contains("NameResolver returned no usable address."); + assertThat(channel.getState(true)).isEqualTo(ConnectivityState.TRANSIENT_FAILURE); assertWithMessage("Empty address should schedule NameResolver retry") .that(getNameResolverRefresh()) .isNotNull(); diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java index d339e4423d6..eaa67480318 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java @@ -304,81 +304,61 @@ public void nearerDeadlineKept_new() { } @Test - public void handleUpdate_failsOnMissingServiceName() { - JsonObj name = new JsonObj("method", "method"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name)); + public void handleUpdate_onEmptyName() { + JsonObj methodConfig = new JsonObj(); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("missing service"); - ManagedChannelServiceConfig parsedServiceConfig = createManagedChannelServiceConfig(serviceConfig); interceptor.handleUpdate(parsedServiceConfig); - } - @Test - public void handleUpdate_failsOnDuplicateMethod() { - JsonObj name1 = new JsonObj("service", "service", "method", "method"); - JsonObj name2 = new JsonObj("service", "service", "method", "method"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Duplicate method"); - - ManagedChannelServiceConfig parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); - - interceptor.handleUpdate(parsedServiceConfig); + assertThat(interceptor.managedChannelServiceConfig.get().getDefaultMethodConfig()).isNull(); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); } @Test - public void handleUpdate_failsOnEmptyName() { - JsonObj methodConfig = new JsonObj(); + public void handleUpdate_onDefaultMethodConfig() { + JsonObj name = new JsonObj(); + JsonObj methodConfig = new JsonObj("name", new JsonList(name)); JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("no names in method config"); - ManagedChannelServiceConfig parsedServiceConfig = createManagedChannelServiceConfig(serviceConfig); - interceptor.handleUpdate(parsedServiceConfig); - } - - @Test - public void handleUpdate_failsOnDuplicateService() { - JsonObj name1 = new JsonObj("service", "service"); - JsonObj name2 = new JsonObj("service", "service"); - JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Duplicate service"); - - ManagedChannelServiceConfig parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); + assertThat(interceptor.managedChannelServiceConfig.get().getDefaultMethodConfig()) + .isEqualTo(new MethodInfo(methodConfig, false, 1, 1)); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); + name = new JsonObj("method", ""); + methodConfig = new JsonObj("name", new JsonList(name)); + serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + parsedServiceConfig = createManagedChannelServiceConfig(serviceConfig); interceptor.handleUpdate(parsedServiceConfig); - } - - @Test - public void handleUpdate_failsOnDuplicateServiceMultipleConfig() { - JsonObj name1 = new JsonObj("service", "service"); - JsonObj name2 = new JsonObj("service", "service"); - JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1)); - JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2)); - JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Duplicate service"); + assertThat(interceptor.managedChannelServiceConfig.get().getDefaultMethodConfig()) + .isEqualTo(new MethodInfo(methodConfig, false, 1, 1)); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); - ManagedChannelServiceConfig parsedServiceConfig = - createManagedChannelServiceConfig(serviceConfig); + name = new JsonObj("service", ""); + methodConfig = new JsonObj("name", new JsonList(name)); + serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + parsedServiceConfig = createManagedChannelServiceConfig(serviceConfig); + interceptor.handleUpdate(parsedServiceConfig); + assertThat(interceptor.managedChannelServiceConfig.get().getDefaultMethodConfig()) + .isEqualTo(new MethodInfo(methodConfig, false, 1, 1)); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); + name = new JsonObj("service", "", "method", ""); + methodConfig = new JsonObj("name", new JsonList(name)); + serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig)); + parsedServiceConfig = createManagedChannelServiceConfig(serviceConfig); interceptor.handleUpdate(parsedServiceConfig); + assertThat(interceptor.managedChannelServiceConfig.get().getDefaultMethodConfig()) + .isEqualTo(new MethodInfo(methodConfig, false, 1, 1)); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMap()).isEmpty(); + assertThat(interceptor.managedChannelServiceConfig.get().getServiceMethodMap()).isEmpty(); } @Test @@ -425,6 +405,61 @@ public void handleUpdate_matchNames() { "service2", new MethodInfo(methodConfig, false, 1, 1)); } + @Test + public void interceptCall_matchNames() { + JsonObj name0 = new JsonObj(); + JsonObj name1 = new JsonObj("service", "service"); + JsonObj name2 = new JsonObj("service", "service", "method", "method"); + JsonObj methodConfig0 = new JsonObj( + "name", new JsonList(name0), "maxRequestMessageBytes", 5d); + JsonObj methodConfig1 = new JsonObj( + "name", new JsonList(name1), "maxRequestMessageBytes", 6d); + JsonObj methodConfig2 = new JsonObj( + "name", new JsonList(name2), "maxRequestMessageBytes", 7d); + JsonObj serviceConfig = + new JsonObj("methodConfig", new JsonList(methodConfig0, methodConfig1, methodConfig2)); + ManagedChannelServiceConfig parsedServiceConfig = + createManagedChannelServiceConfig(serviceConfig); + + interceptor.handleUpdate(parsedServiceConfig); + + String fullMethodName = + MethodDescriptor.generateFullMethodName("service", "method"); + MethodDescriptor methodDescriptor = + MethodDescriptor.newBuilder(new NoopMarshaller(), new NoopMarshaller()) + .setType(MethodType.UNARY) + .setFullMethodName(fullMethodName) + .build(); + interceptor.interceptCall( + methodDescriptor, CallOptions.DEFAULT, channel); + verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); + assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(7); + + fullMethodName = + MethodDescriptor.generateFullMethodName("service", "method2"); + methodDescriptor = + MethodDescriptor.newBuilder(new NoopMarshaller(), new NoopMarshaller()) + .setType(MethodType.UNARY) + .setFullMethodName(fullMethodName) + .build(); + interceptor.interceptCall( + methodDescriptor, CallOptions.DEFAULT, channel); + verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); + assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(6); + + fullMethodName = + MethodDescriptor.generateFullMethodName("service2", "method"); + methodDescriptor = + MethodDescriptor.newBuilder(new NoopMarshaller(), new NoopMarshaller()) + .setType(MethodType.UNARY) + .setFullMethodName(fullMethodName) + .build(); + interceptor.interceptCall( + methodDescriptor, CallOptions.DEFAULT, channel); + verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture()); + assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(5); + } + @Test public void methodInfo_validateDeadline() { JsonObj name = new JsonObj("service", "service"); diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigStateTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigStateTest.java index 868fb15b47b..4390ff44ea3 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigStateTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigStateTest.java @@ -39,12 +39,14 @@ public class ServiceConfigStateTest { private final ManagedChannelServiceConfig serviceConfig1 = new ManagedChannelServiceConfig( + null, Collections.emptyMap(), Collections.emptyMap(), null, null, null); private final ManagedChannelServiceConfig serviceConfig2 = new ManagedChannelServiceConfig( + null, Collections.emptyMap(), Collections.emptyMap(), null, @@ -428,6 +430,7 @@ public void lookup_default_onPresent_onAbsent() { public void lookup_default_onPresent_onPresent() { ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true); ManagedChannelServiceConfig serviceConfig3 = new ManagedChannelServiceConfig( + null, Collections.emptyMap(), Collections.emptyMap(), null, diff --git a/cronet/README.md b/cronet/README.md index 5787f836113..dde8ba51d38 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.28.1' +implementation 'io.grpc:grpc-cronet:1.30.0' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/cronet/build.gradle b/cronet/build.gradle index aac89f07614..b1b65685bb2 100644 --- a/cronet/build.gradle +++ b/cronet/build.gradle @@ -33,10 +33,11 @@ android { } dependencies { - implementation project(':grpc-core') + api project(':grpc-core'), + libraries.cronet_api + guavaDependency 'implementation' testImplementation project(':grpc-testing') - implementation libraries.cronet_api testImplementation libraries.cronet_embedded testImplementation libraries.junit diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index 4e50ef49298..043689cd92d 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.28.1' -implementation 'io.grpc:grpc-okhttp:1.28.1' +implementation 'io.grpc:grpc-android:1.30.0' +implementation 'io.grpc:grpc-okhttp:1.30.0' ``` You also need permission to access the device's network state in your diff --git a/examples/README.md b/examples/README.md index fdcec2d636b..9f74895a45c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -158,10 +158,6 @@ $ bazel-bin/hello-world-client - [JWT-based Authentication](example-jwt-auth) -- [Kotlin examples](example-kotlin) - -- [Kotlin Android examples](example-kotlin/android) - ## Unit test examples Examples for unit testing gRPC clients and servers are located in [examples/src/test](src/test). @@ -174,7 +170,7 @@ examples to write unit tests. `InProcessTransport` is light-weight and runs the and client in the same process without any socket/TCP connection. Mocking the client stub provides a false sense of security when writing tests. Mocking stubs and responses -allows for tests that don't map to reality, causing the tests to pass, but the system-under-test to fail. +allows for tests that don't map to reality, causing the tests to pass, but the system-under-test to fail. The gRPC client library is complicated, and accurately reproducing that complexity with mocks is very hard. You will be better off and write less code by using `InProcessTransport` instead. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 477cbaa66b9..50f28c1a205 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -28,9 +28,9 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } + protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.30.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -50,12 +50,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'javax.annotation:javax.annotation-api:1.2' + implementation 'io.grpc:grpc-okhttp:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.30.0' // CURRENT_GRPC_VERSION + implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.30.0' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index e5a00edcf06..19ca2ec62e8 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -27,9 +27,9 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } + protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.30.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -49,8 +49,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'javax.annotation:javax.annotation-api:1.2' + implementation 'io.grpc:grpc-okhttp:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.30.0' // CURRENT_GRPC_VERSION + implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index bdce8397c5c..3a95b8cfb26 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -26,9 +26,9 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } + protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.30.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'javax.annotation:javax.annotation-api:1.2' + implementation 'io.grpc:grpc-okhttp:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.30.0' // CURRENT_GRPC_VERSION + implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index 643e25afdfc..9382aa34016 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -27,9 +27,9 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } + protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.30.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -49,8 +49,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'javax.annotation:javax.annotation-api:1.2' + implementation 'io.grpc:grpc-okhttp:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.30.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.30.0' // CURRENT_GRPC_VERSION + implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 435ab7ec583..6cf38f967a4 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,14 +22,14 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.11.0' +def grpcVersion = '1.30.0' // CURRENT_GRPC_VERSION +def protobufVersion = '3.12.0' def protocVersion = protobufVersion dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" - compileOnly "javax.annotation:javax.annotation-api:1.2" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" // examples/advanced need this for JsonFormat implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}" diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 361a44ff150..bf0c5dd4eb3 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,13 +23,13 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.11.0' +def grpcVersion = '1.30.0' // CURRENT_GRPC_VERSION +def protocVersion = '3.12.0' dependencies { // grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub implementation "io.grpc:grpc-alts:${grpcVersion}" - compileOnly "javax.annotation:javax.annotation-api:1.2" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" } protobuf { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index ec74b652bde..c08b22bacd3 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,8 +23,8 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.11.0' +def grpcVersion = '1.30.0' // CURRENT_GRPC_VERSION +def protobufVersion = '3.12.0' def protocVersion = protobufVersion @@ -32,7 +32,7 @@ dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-auth:${grpcVersion}" - compileOnly "javax.annotation:javax.annotation-api:1.2" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" implementation "com.google.auth:google-auth-library-oauth2-http:0.9.0" implementation "com.google.api.grpc:grpc-google-cloud-pubsub-v1:0.1.24" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index b1f2ef2e1df..db3c2c4fb42 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,14 +6,14 @@ jar - 1.29.0-SNAPSHOT + 1.30.0 example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.29.0-SNAPSHOT - 3.11.0 + 1.30.0 + 3.12.0 1.7 1.7 @@ -32,6 +32,11 @@ + + com.google.protobuf + protobuf-java-util + ${protobuf.version} + io.grpc grpc-netty-shaded @@ -50,9 +55,9 @@ grpc-auth - javax.annotation - javax.annotation-api - 1.2 + org.apache.tomcat + annotations-api + 6.0.53 provided @@ -60,11 +65,6 @@ grpc-testing test - - com.google.protobuf - protobuf-java-util - ${protobuf.version} - com.google.auth google-auth-library-oauth2-http diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index 1a20ee19260..49a120055cc 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -3,6 +3,7 @@ plugins { id 'java' id "com.google.protobuf" version "0.8.10" + id 'com.google.cloud.tools.jib' version '2.1.0' // For releasing to Docker Hub } repositories { @@ -20,14 +21,14 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.11.0' +def grpcVersion = '1.30.0' // CURRENT_GRPC_VERSION +def protobufVersion = '3.12.0' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-services:${grpcVersion}" - compileOnly "javax.annotation:javax.annotation-api:1.2" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" testImplementation 'junit:junit:4.12' @@ -52,3 +53,13 @@ protobuf { applicationName = 'hostname-server' mainClassName = 'io.grpc.examples.hostname.HostnameServer' + +// For releasing to Docker Hub +jib { + container.ports = ['50051'] + outputPaths { + tar = 'build/example-hostname.tar' + digest = 'build/example-hostname.digest' + imageId = 'build/example-hostname.id' + } +} diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 1aa64e88938..529cd9c323c 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,14 +6,14 @@ jar - 1.29.0-SNAPSHOT + 1.30.0 example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.29.0-SNAPSHOT - 3.11.0 + 1.30.0 + 3.12.0 1.7 1.7 @@ -45,9 +45,9 @@ grpc-services - javax.annotation - javax.annotation-api - 1.2 + org.apache.tomcat + annotations-api + 6.0.53 provided diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 8b2b5760e86..d8facc5acc3 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,8 +22,8 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.11.0' +def grpcVersion = '1.30.0' // CURRENT_GRPC_VERSION +def protobufVersion = '3.12.0' def protocVersion = protobufVersion dependencies { @@ -32,7 +32,7 @@ dependencies { implementation "io.jsonwebtoken:jjwt:0.9.1" implementation "javax.xml.bind:jaxb-api:2.3.1" - compileOnly "javax.annotation:javax.annotation-api:1.2" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index a40481b5087..41d32528110 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,15 +7,15 @@ jar - 1.29.0-SNAPSHOT + 1.30.0 example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.29.0-SNAPSHOT - 3.11.0 - 3.11.0 + 1.30.0 + 3.12.0 + 3.12.0 1.7 1.7 @@ -58,9 +58,9 @@ 2.3.1 - javax.annotation - javax.annotation-api - 1.2 + org.apache.tomcat + annotations-api + 6.0.53 provided diff --git a/examples/example-kotlin/README.md b/examples/example-kotlin/README.md deleted file mode 100644 index 0f396a925f2..00000000000 --- a/examples/example-kotlin/README.md +++ /dev/null @@ -1,62 +0,0 @@ -grpc Kotlin example -============================================== - -The examples require grpc-java to already be built. You are strongly encouraged -to check out a git release tag, since there will already be a build of grpc -available. Otherwise you must follow COMPILING.md. - -You may want to read through the -[Quick Start Guide](https://grpc.io/docs/quickstart/java.html) -before trying out the examples. - -To build the examples, - -1. **[Install gRPC Java library SNAPSHOT locally, including code generation plugin](../../COMPILING.md) (Only need this step for non-released versions, e.g. master HEAD).** - -2. Run in this directory: -``` -$ ../gradlew installDist -``` - -This creates the scripts `hello-world-server`, `hello-world-client`, -`route-guide-server`, and `route-guide-client` in the -`build/install/examples/bin/` directory that run the examples. Each -example requires the server to be running before starting the client. - -For example, to try the hello world example first run: - -``` -$ ./build/install/examples/bin/hello-world-server -``` - -And in a different terminal window run: - -``` -$ ./build/install/examples/bin/hello-world-client -``` - -That's it! - -Please refer to gRPC Java's [README](../README.md) and -[tutorial](https://grpc.io/docs/tutorials/basic/java.html) for more -information. - -Unit test examples -============================================== - -Examples for unit testing gRPC clients and servers are located in [./src/test](./src/test). - -In general, we DO NOT allow overriding the client stub. -We encourage users to leverage `InProcessTransport` as demonstrated in the examples to -write unit tests. `InProcessTransport` is light-weight and runs the server -and client in the same process without any socket/TCP connection. - -For testing a gRPC client, create the client with a real stub -using an InProcessChannelBuilder.java and test it against an InProcessServer.java -with a mock/fake service implementation. - -For testing a gRPC server, create the server as an InProcessServer, -and test it against a real client stub with an InProcessChannel. - -The gRPC-java library also provides a JUnit rule, GrpcCleanupRule.java, to do the graceful shutdown -boilerplate for you. diff --git a/examples/example-kotlin/android/helloworld/app/build.gradle b/examples/example-kotlin/android/helloworld/app/build.gradle deleted file mode 100644 index 9f32d958623..00000000000 --- a/examples/example-kotlin/android/helloworld/app/build.gradle +++ /dev/null @@ -1,81 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'com.google.protobuf' - -android { - compileSdkVersion 27 - - defaultConfig { - applicationId "io.grpc.helloworldexample" - // API level 14+ is required for TLS since Google Play Services v10.2 - minSdkVersion 14 - targetSdkVersion 27 - versionCode 1 - versionName "1.0" - } - buildTypes { - debug { minifyEnabled false } - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - lintOptions { - disable 'GoogleAppIndexingWarning', 'HardcodedText', 'InvalidPackage' - textReport true - textOutput "stdout" - } - // Android Studio 3.1 does not automatically pick up '/kotlin' as source input - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - test.java.srcDirs += 'src/test/kotlin' - androidTest.java.srcDirs += 'src/androidTest/kotlin' - } - - lintOptions { - // Do not complain about outdated deps, so that this can javax.annotation-api can be same - // as other projects in this repo. Your project is not required to do this, and can - // upgrade the dep. - disable 'GradleDependency' - // The Android linter does not correctly detect resources used in Kotlin. - // See: - // - https://youtrack.jetbrains.com/issue/KT-7729 - // - https://youtrack.jetbrains.com/issue/KT-12499 - disable 'UnusedResources' - textReport true - textOutput "stdout" - } -} - -protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } - plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - } - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { option 'lite' } - } - task.plugins { - grpc { // Options added to --grpc_out - option 'lite' } - } - } - } -} - -dependencies { - implementation 'com.android.support:appcompat-v7:27.0.2' - implementation 'javax.annotation:javax.annotation-api:1.2' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.29.0-SNAPSHOT' // CURRENT_GRPC_VERSION -} - -repositories { mavenCentral() } diff --git a/examples/example-kotlin/android/helloworld/app/proguard-rules.pro b/examples/example-kotlin/android/helloworld/app/proguard-rules.pro deleted file mode 100644 index 1507a526787..00000000000 --- a/examples/example-kotlin/android/helloworld/app/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in $ANDROID_HOME/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - --dontwarn com.google.common.** -# Ignores: can't find referenced class javax.lang.model.element.Modifier --dontwarn com.google.errorprone.annotations.** --dontwarn javax.naming.** --dontwarn okio.** --dontwarn sun.misc.Unsafe diff --git a/examples/example-kotlin/android/helloworld/app/src/main/AndroidManifest.xml b/examples/example-kotlin/android/helloworld/app/src/main/AndroidManifest.xml deleted file mode 100644 index eee4057cd0c..00000000000 --- a/examples/example-kotlin/android/helloworld/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/examples/example-kotlin/android/helloworld/app/src/main/kotlin/io/grpc/helloworldexample/HelloworldActivity.kt b/examples/example-kotlin/android/helloworld/app/src/main/kotlin/io/grpc/helloworldexample/HelloworldActivity.kt deleted file mode 100644 index 66c96b9bc61..00000000000 --- a/examples/example-kotlin/android/helloworld/app/src/main/kotlin/io/grpc/helloworldexample/HelloworldActivity.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2015 The gRPC 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 io.grpc.helloworldexample - -import android.app.Activity -import android.content.Context -import android.os.AsyncTask -import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import android.text.TextUtils -import android.text.method.ScrollingMovementMethod -import android.view.View -import android.view.inputmethod.InputMethodManager -import android.widget.Button -import android.widget.TextView -import io.grpc.ManagedChannel -import io.grpc.ManagedChannelBuilder -import io.grpc.examples.helloworld.GreeterGrpc -import io.grpc.examples.helloworld.HelloRequest -import java.io.PrintWriter -import java.io.StringWriter -import java.lang.ref.WeakReference -import java.util.concurrent.TimeUnit -import kotlinx.android.synthetic.main.activity_helloworld.* - -class HelloworldActivity : AppCompatActivity(), View.OnClickListener { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_helloworld) - grpc_response_text!!.movementMethod = ScrollingMovementMethod() - send_button!!.setOnClickListener(this) - } - - override fun onClick(view: View) { - (getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .hideSoftInputFromWindow(host_edit_text!!.windowToken, 0) - send_button!!.isEnabled = false - grpc_response_text!!.text = "" - GrpcTask(this) - .execute( - host_edit_text!!.text.toString(), - message_edit_text!!.text.toString(), - port_edit_text!!.text.toString()) - } - - private class GrpcTask constructor(activity: Activity) : AsyncTask() { - private val activityReference: WeakReference = WeakReference(activity) - private var channel: ManagedChannel? = null - - override fun doInBackground(vararg params: String): String { - val host = params[0] - val message = params[1] - val portStr = params[2] - val port = if (TextUtils.isEmpty(portStr)) 0 else Integer.valueOf(portStr) - return try { - channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() - val stub = GreeterGrpc.newBlockingStub(channel) - val request = HelloRequest.newBuilder().setName(message).build() - val reply = stub.sayHello(request) - reply.message - } catch (e: Exception) { - val sw = StringWriter() - val pw = PrintWriter(sw) - e.printStackTrace(pw) - pw.flush() - - "Failed... : %s".format(sw) - } - } - - override fun onPostExecute(result: String) { - try { - channel?.shutdown()?.awaitTermination(1, TimeUnit.SECONDS) - } catch (e: InterruptedException) { - Thread.currentThread().interrupt() - } - - val activity = activityReference.get() ?: return - val resultText: TextView = activity.findViewById(R.id.grpc_response_text) - val sendButton: Button = activity.findViewById(R.id.send_button) - - resultText.text = result - sendButton.isEnabled = true - } - } -} diff --git a/examples/example-kotlin/android/helloworld/app/src/main/proto/helloworld.proto b/examples/example-kotlin/android/helloworld/app/src/main/proto/helloworld.proto deleted file mode 100644 index c60d9416f1f..00000000000 --- a/examples/example-kotlin/android/helloworld/app/src/main/proto/helloworld.proto +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2015 The gRPC 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. -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.grpc.examples.helloworld"; -option java_outer_classname = "HelloWorldProto"; -option objc_class_prefix = "HLW"; - -package helloworld; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; -} diff --git a/examples/example-kotlin/android/helloworld/app/src/main/res/layout/activity_helloworld.xml b/examples/example-kotlin/android/helloworld/app/src/main/res/layout/activity_helloworld.xml deleted file mode 100644 index e9f41f46696..00000000000 --- a/examples/example-kotlin/android/helloworld/app/src/main/res/layout/activity_helloworld.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - -