From 6ccd4395228be849dbfdb94e4b782c993bd8708f Mon Sep 17 00:00:00 2001 From: Shashank Patidar Date: Wed, 14 Apr 2021 21:42:34 +0530 Subject: [PATCH 1/3] implementing to get dynamic config during runtime --- javaagent-core/build.gradle.kts | 20 +++++- .../agent/core/config/EnvironmentConfig.java | 1 + .../agent/core/config/HypertraceConfig.java | 70 +++++++++++++++++-- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/javaagent-core/build.gradle.kts b/javaagent-core/build.gradle.kts index 9de342656..8aba79c2a 100644 --- a/javaagent-core/build.gradle.kts +++ b/javaagent-core/build.gradle.kts @@ -3,7 +3,7 @@ import com.google.protobuf.gradle.* plugins { `java-library` idea - id("com.google.protobuf") version "0.8.13" + id("com.google.protobuf") version "0.8.15" id("org.hypertrace.publish-maven-central-plugin") } @@ -12,7 +12,18 @@ protobuf { // The artifact spec for the Protobuf Compiler artifact = "com.google.protobuf:protoc:3.11.4" } + plugins { + id("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:1.37.0" + } + } generateProtoTasks { + ofSourceSet("main").forEach { + it.plugins { + // Apply the "grpc" plugin whose spec is defined above, without options. + id("grpc") + } + } } } @@ -31,6 +42,13 @@ dependencies { api("com.google.protobuf:protobuf-java:3.11.4") api("com.google.protobuf:protobuf-java-util:3.11.4") + api("io.grpc:grpc-stub:1.37.0") + api("io.grpc:grpc-protobuf:1.37.0") + if (JavaVersion.current().isJava9Compatible) { + // Workaround for @javax.annotation.Generated + // see: https://github.com/grpc/grpc-java/issues/3633 + api("javax.annotation:javax.annotation-api:1.3.1") + } // convert yaml to json, since java protobuf impl supports only json implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.3") diff --git a/javaagent-core/src/main/java/org/hypertrace/agent/core/config/EnvironmentConfig.java b/javaagent-core/src/main/java/org/hypertrace/agent/core/config/EnvironmentConfig.java index c207193cf..c7917e918 100644 --- a/javaagent-core/src/main/java/org/hypertrace/agent/core/config/EnvironmentConfig.java +++ b/javaagent-core/src/main/java/org/hypertrace/agent/core/config/EnvironmentConfig.java @@ -36,6 +36,7 @@ private EnvironmentConfig() {} private static final String HT_PREFIX = "ht."; public static final String CONFIG_FILE_PROPERTY = HT_PREFIX + "config.file"; + public static final String DYNAMIC_CONFIG_SERVICE_URL = HT_PREFIX + "dynamic.config.service.url"; static final String SERVICE_NAME = HT_PREFIX + "service.name"; static final String ENABLED = HT_PREFIX + "enabled"; static final String RESOURCE_ATTRIBUTES = HT_PREFIX + ".resource.attributes"; diff --git a/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java b/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java index 55b3f3ca6..467a0d250 100644 --- a/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java +++ b/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java @@ -21,15 +21,20 @@ import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.BoolValue; import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; import com.google.protobuf.StringValue; import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat.Parser; +import io.grpc.Channel; +import io.grpc.ManagedChannelBuilder; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.hypertrace.agent.config.Config; import org.hypertrace.agent.config.Config.AgentConfig; import org.hypertrace.agent.config.Config.DataCapture; @@ -38,6 +43,8 @@ import org.hypertrace.agent.config.Config.Opa.Builder; import org.hypertrace.agent.config.Config.PropagationFormat; import org.hypertrace.agent.config.Config.Reporting; +import org.hypertrace.agent.config.ConfigurationServiceGrpc; +import org.hypertrace.agent.config.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,10 +71,18 @@ private HypertraceConfig() {} public static AgentConfig get() { if (agentConfig == null) { + String dynamicConfigServiceUrl = + EnvironmentConfig.getProperty(EnvironmentConfig.DYNAMIC_CONFIG_SERVICE_URL); + DynamicConfigFetcher dynamicConfigFetcher = null; + if (dynamicConfigServiceUrl != null) { + dynamicConfigFetcher = new DynamicConfigFetcher(dynamicConfigServiceUrl); + Executors.newScheduledThreadPool(1) + .scheduleAtFixedRate(dynamicConfigFetcher, 60, 30, TimeUnit.SECONDS); + } synchronized (HypertraceConfig.class) { if (agentConfig == null) { try { - agentConfig = load(); + agentConfig = load(dynamicConfigFetcher); log.info( "Config loaded: {}", JsonFormat.printer().omittingInsignificantWhitespace().print(agentConfig)); @@ -130,21 +145,28 @@ public static void reset() { } } - private static AgentConfig load() throws IOException { + private static AgentConfig load(DynamicConfigFetcher dynamicConfigFetcher) throws IOException { String configFile = EnvironmentConfig.getProperty(EnvironmentConfig.CONFIG_FILE_PROPERTY); if (configFile == null) { - return EnvironmentConfig.applyPropertiesAndEnvVars(applyDefaults(AgentConfig.newBuilder())) - .build(); + AgentConfig.Builder configBuilder = AgentConfig.newBuilder(); + if (dynamicConfigFetcher != null) { + configBuilder = dynamicConfigFetcher.initializeConfig().toBuilder(); + } + return EnvironmentConfig.applyPropertiesAndEnvVars(applyDefaults(configBuilder)).build(); } - return load(configFile); + return load(configFile, dynamicConfigFetcher); } @VisibleForTesting - static AgentConfig load(String filename) throws IOException { + static AgentConfig load(String filename, DynamicConfigFetcher dynamicConfigFetcher) + throws IOException { File configFile = new File(filename); if (!configFile.exists() || configFile.isDirectory() || !configFile.canRead()) { log.error("Config file {} does not exist", filename); AgentConfig.Builder configBuilder = AgentConfig.newBuilder(); + if (dynamicConfigFetcher != null) { + configBuilder = dynamicConfigFetcher.initializeConfig().toBuilder(); + } return EnvironmentConfig.applyPropertiesAndEnvVars(applyDefaults(configBuilder)).build(); } @@ -236,4 +258,40 @@ private static String convertYamlToJson(InputStream yaml) throws IOException { ObjectMapper jsonWriter = new ObjectMapper(); return jsonWriter.writeValueAsString(obj); } + + public static class DynamicConfigFetcher implements Runnable { + + private final ConfigurationServiceGrpc.ConfigurationServiceBlockingStub blockingStub; + + private static Int64Value configTimestamp; + + private DynamicConfigFetcher(String dynamicConfigServiceUrl) { + Channel channel = ManagedChannelBuilder.forTarget(dynamicConfigServiceUrl).build(); + blockingStub = ConfigurationServiceGrpc.newBlockingStub(channel); + configTimestamp = Int64Value.newBuilder().setValue(System.currentTimeMillis()).build(); + } + + @Override + public void run() { + Service.UpdateConfigurationResponse response = + blockingStub.updateConfiguration( + Service.UpdateConfigurationRequest.newBuilder() + .setTimestamp(configTimestamp) + .build()); + configTimestamp = response.getTimestamp(); + AgentConfig.Builder configBuilder = HypertraceConfig.get().toBuilder(); + configBuilder.setEnabled(response.getEnabled()); + configBuilder.setDataCapture(response.getDataCapture()); + configBuilder.setJavaagent(response.getJavaAgent()); + agentConfig = configBuilder.build(); + } + + private AgentConfig initializeConfig() { + Service.InitialConfigurationResponse response = + blockingStub.initialConfiguration( + Service.InitialConfigurationRequest.newBuilder().build()); + configTimestamp = response.getTimestamp(); + return response.getAgentConfig(); + } + } } From e8867743f6cedbf6f9f80bd0238b42a07c720df1 Mon Sep 17 00:00:00 2001 From: Shashank Patidar Date: Thu, 15 Apr 2021 18:36:02 +0530 Subject: [PATCH 2/3] adding test --- javaagent-core/build.gradle.kts | 2 + .../agent/core/config/HypertraceConfig.java | 30 +++-- .../core/config/DynamicConfigServer.java | 116 ++++++++++++++++++ .../core/config/HypertraceConfigTest.java | 84 ++++++++++++- 4 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 javaagent-core/src/test/java/org/hypertrace/agent/core/config/DynamicConfigServer.java diff --git a/javaagent-core/build.gradle.kts b/javaagent-core/build.gradle.kts index 8aba79c2a..d1a1d3547 100644 --- a/javaagent-core/build.gradle.kts +++ b/javaagent-core/build.gradle.kts @@ -53,4 +53,6 @@ dependencies { implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.3") api("com.blogspot.mydailyjava:weak-lock-free:0.17") + + runtimeOnly("io.grpc:grpc-netty-shaded:1.37.0") } diff --git a/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java b/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java index 467a0d250..2938930ad 100644 --- a/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java +++ b/javaagent-core/src/main/java/org/hypertrace/agent/core/config/HypertraceConfig.java @@ -48,7 +48,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** {@link HypertraceConfig} loads a yaml config from file. */ +/** + * {@link HypertraceConfig} loads a yaml config from file and gets dynamic config if dynamic config + * service url is set in env vars or system properties. + */ public class HypertraceConfig { private HypertraceConfig() {} @@ -76,7 +79,14 @@ public static AgentConfig get() { DynamicConfigFetcher dynamicConfigFetcher = null; if (dynamicConfigServiceUrl != null) { dynamicConfigFetcher = new DynamicConfigFetcher(dynamicConfigServiceUrl); - Executors.newScheduledThreadPool(1) + Executors.newScheduledThreadPool( + 1, + runnable -> { + Thread thread = new Thread(runnable, "dynamic_agent_config_fetcher"); + thread.setDaemon(true); + // thread.setContextClassLoader(null); + return thread; + }) .scheduleAtFixedRate(dynamicConfigFetcher, 60, 30, TimeUnit.SECONDS); } synchronized (HypertraceConfig.class) { @@ -145,7 +155,8 @@ public static void reset() { } } - private static AgentConfig load(DynamicConfigFetcher dynamicConfigFetcher) throws IOException { + @VisibleForTesting + static AgentConfig load(DynamicConfigFetcher dynamicConfigFetcher) throws IOException { String configFile = EnvironmentConfig.getProperty(EnvironmentConfig.CONFIG_FILE_PROPERTY); if (configFile == null) { AgentConfig.Builder configBuilder = AgentConfig.newBuilder(); @@ -263,12 +274,13 @@ public static class DynamicConfigFetcher implements Runnable { private final ConfigurationServiceGrpc.ConfigurationServiceBlockingStub blockingStub; - private static Int64Value configTimestamp; + private static Int64Value configUpdatedTimestamp; private DynamicConfigFetcher(String dynamicConfigServiceUrl) { - Channel channel = ManagedChannelBuilder.forTarget(dynamicConfigServiceUrl).build(); + Channel channel = + ManagedChannelBuilder.forTarget(dynamicConfigServiceUrl).usePlaintext().build(); blockingStub = ConfigurationServiceGrpc.newBlockingStub(channel); - configTimestamp = Int64Value.newBuilder().setValue(System.currentTimeMillis()).build(); + configUpdatedTimestamp = Int64Value.newBuilder().setValue(System.currentTimeMillis()).build(); } @Override @@ -276,9 +288,9 @@ public void run() { Service.UpdateConfigurationResponse response = blockingStub.updateConfiguration( Service.UpdateConfigurationRequest.newBuilder() - .setTimestamp(configTimestamp) + .setTimestamp(configUpdatedTimestamp) .build()); - configTimestamp = response.getTimestamp(); + configUpdatedTimestamp = response.getTimestamp(); AgentConfig.Builder configBuilder = HypertraceConfig.get().toBuilder(); configBuilder.setEnabled(response.getEnabled()); configBuilder.setDataCapture(response.getDataCapture()); @@ -290,7 +302,7 @@ private AgentConfig initializeConfig() { Service.InitialConfigurationResponse response = blockingStub.initialConfiguration( Service.InitialConfigurationRequest.newBuilder().build()); - configTimestamp = response.getTimestamp(); + configUpdatedTimestamp = response.getTimestamp(); return response.getAgentConfig(); } } diff --git a/javaagent-core/src/test/java/org/hypertrace/agent/core/config/DynamicConfigServer.java b/javaagent-core/src/test/java/org/hypertrace/agent/core/config/DynamicConfigServer.java new file mode 100644 index 000000000..7ec59bd53 --- /dev/null +++ b/javaagent-core/src/test/java/org/hypertrace/agent/core/config/DynamicConfigServer.java @@ -0,0 +1,116 @@ +/* + * Copyright The Hypertrace 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 org.hypertrace.agent.core.config; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import org.hypertrace.agent.config.ConfigurationServiceGrpc; +import org.hypertrace.agent.config.Service; + +public class DynamicConfigServer { + + private final Server server; + private Service.InitialConfigurationResponse initialConfigurationResponse; + private Service.UpdateConfigurationResponse updateConfigurationResponse; + + public DynamicConfigServer(int port) { + server = ServerBuilder.forPort(port).addService(new DynamicConfigService()).build(); + } + + /** Start serving requests. */ + public void start() throws IOException { + server.start(); + Runtime.getRuntime() + .addShutdownHook( + new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + DynamicConfigServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + /** Stop serving requests and shutdown resources. */ + public void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** Await termination on the main thread since the grpc library uses daemon threads. */ + public void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + public Service.InitialConfigurationResponse getInitialConfigurationResponse() { + return initialConfigurationResponse; + } + + public void setInitialConfigurationResponse( + Service.InitialConfigurationResponse initialConfigurationResponse) { + this.initialConfigurationResponse = initialConfigurationResponse; + } + + public Service.UpdateConfigurationResponse getUpdateConfigurationResponse() { + return updateConfigurationResponse; + } + + public void setUpdateConfigurationResponse( + Service.UpdateConfigurationResponse updateConfigurationResponse) { + this.updateConfigurationResponse = updateConfigurationResponse; + } + + /** Main method. This comment makes the linter happy. */ + public static void main(String[] args) throws Exception { + DynamicConfigServer server = new DynamicConfigServer(8980); + server.start(); + server.blockUntilShutdown(); + } + + private class DynamicConfigService extends ConfigurationServiceGrpc.ConfigurationServiceImplBase { + @Override + public void initialConfiguration( + org.hypertrace.agent.config.Service.InitialConfigurationRequest request, + io.grpc.stub.StreamObserver< + org.hypertrace.agent.config.Service.InitialConfigurationResponse> + responseObserver) { + + responseObserver.onNext(initialConfigurationResponse); + responseObserver.onCompleted(); + } + + @Override + public void updateConfiguration( + org.hypertrace.agent.config.Service.UpdateConfigurationRequest request, + io.grpc.stub.StreamObserver + responseObserver) { + responseObserver.onNext(updateConfigurationResponse); + responseObserver.onCompleted(); + } + } +} diff --git a/javaagent-core/src/test/java/org/hypertrace/agent/core/config/HypertraceConfigTest.java b/javaagent-core/src/test/java/org/hypertrace/agent/core/config/HypertraceConfigTest.java index 81dbee374..67bde90e0 100644 --- a/javaagent-core/src/test/java/org/hypertrace/agent/core/config/HypertraceConfigTest.java +++ b/javaagent-core/src/test/java/org/hypertrace/agent/core/config/HypertraceConfigTest.java @@ -16,6 +16,9 @@ package org.hypertrace.agent.core.config; +import com.google.protobuf.BoolValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; import com.google.protobuf.StringValue; import com.google.protobuf.util.JsonFormat; import java.io.File; @@ -23,20 +26,51 @@ import java.io.IOException; import java.net.URL; import java.util.Arrays; +import org.hypertrace.agent.config.Config; import org.hypertrace.agent.config.Config.AgentConfig; import org.hypertrace.agent.config.Config.PropagationFormat; import org.hypertrace.agent.config.Config.TraceReporterType; +import org.hypertrace.agent.config.Service; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junitpioneer.jupiter.ClearSystemProperty; public class HypertraceConfigTest { + private static DynamicConfigServer server; + + @BeforeAll + public static void setup() throws Exception { + server = new DynamicConfigServer(1234); + Thread thread = + new Thread( + () -> { + try { + server.start(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + server.blockUntilShutdown(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + thread.start(); + } + + @AfterAll + public static void cleanup() throws InterruptedException { + server.stop(); + } + @Test public void defaultValues() throws IOException { URL resource = getClass().getClassLoader().getResource("emptyconfig.yaml"); - AgentConfig agentConfig = HypertraceConfig.load(resource.getPath()); + AgentConfig agentConfig = HypertraceConfig.load(resource.getPath(), null); Assertions.assertTrue(agentConfig.getEnabled().getValue()); Assertions.assertEquals("unknown", agentConfig.getServiceName().getValue()); Assertions.assertEquals( @@ -79,14 +113,14 @@ public void defaultValues() throws IOException { @Test public void config() throws IOException { URL resource = getClass().getClassLoader().getResource("config.yaml"); - AgentConfig agentConfig = HypertraceConfig.load(resource.getPath()); + AgentConfig agentConfig = HypertraceConfig.load(resource.getPath(), null); assertConfig(agentConfig); } @Test public void jsonConfig(@TempDir File tempFolder) throws IOException { URL resource = getClass().getClassLoader().getResource("config.yaml"); - AgentConfig agentConfig = HypertraceConfig.load(resource.getPath()); + AgentConfig agentConfig = HypertraceConfig.load(resource.getPath(), null); String jsonConfig = JsonFormat.printer().print(agentConfig); Assertions.assertTrue(!jsonConfig.contains("value")); @@ -94,7 +128,27 @@ public void jsonConfig(@TempDir File tempFolder) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream(jsonFile); fileOutputStream.write(jsonConfig.getBytes()); - agentConfig = HypertraceConfig.load(jsonFile.getAbsolutePath()); + agentConfig = HypertraceConfig.load(jsonFile.getAbsolutePath(), null); + assertConfig(agentConfig); + } + + @Test + @ClearSystemProperty(key = EnvironmentConfig.DYNAMIC_CONFIG_SERVICE_URL) + @ClearSystemProperty(key = EnvironmentConfig.CONFIG_FILE_PROPERTY) + public void dynamicInitialConfig() throws IOException { + URL resource = getClass().getClassLoader().getResource("config.yaml"); + AgentConfig agentConfig = HypertraceConfig.load(resource.getPath(), null); + Service.InitialConfigurationResponse initialConfigurationResponse = + Service.InitialConfigurationResponse.newBuilder() + .setTimestamp(Int64Value.newBuilder().setValue(System.currentTimeMillis()).build()) + .setAgentConfig(agentConfig) + .build(); + + server.setInitialConfigurationResponse(initialConfigurationResponse); + server.setUpdateConfigurationResponse(getUpdateConfigurationResponse()); + + System.setProperty(EnvironmentConfig.DYNAMIC_CONFIG_SERVICE_URL, "localhost:1234"); + agentConfig = HypertraceConfig.get(); assertConfig(agentConfig); } @@ -136,9 +190,29 @@ public void configWithSystemProps() throws IOException { System.setProperty(EnvironmentConfig.REPORTING_ENDPOINT, "http://nowhere.here"); URL resource = getClass().getClassLoader().getResource("config.yaml"); - AgentConfig agentConfig = HypertraceConfig.load(resource.getPath()); + AgentConfig agentConfig = HypertraceConfig.load(resource.getPath(), null); Assertions.assertEquals( "http://nowhere.here", agentConfig.getReporting().getEndpoint().getValue()); Assertions.assertEquals("service", agentConfig.getServiceName().getValue()); } + + private Service.UpdateConfigurationResponse getUpdateConfigurationResponse() { + Config.DataCapture dataCapture = + Config.DataCapture.newBuilder() + .setBodyMaxSizeBytes(Int32Value.newBuilder().setValue(12).build()) + .setHttpHeaders( + Config.Message.newBuilder() + .setResponse(BoolValue.newBuilder().setValue(false).build()) + .build()) + .build(); + return Service.UpdateConfigurationResponse.newBuilder() + .setDataCapture(dataCapture) + .setEnabled(BoolValue.newBuilder().setValue(false).build()) + .setTimestamp(Int64Value.newBuilder().setValue(System.currentTimeMillis()).build()) + .setJavaAgent( + Config.JavaAgent.newBuilder() + .addFilterJarPaths(StringValue.newBuilder().setValue("random").build()) + .build()) + .build(); + } } From 1212cbff05545c7edaffa0335b2359ab003fd684 Mon Sep 17 00:00:00 2001 From: Shashank Patidar Date: Thu, 15 Apr 2021 19:33:45 +0530 Subject: [PATCH 3/3] updating dependency --- javaagent-core/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/javaagent-core/build.gradle.kts b/javaagent-core/build.gradle.kts index d1a1d3547..b85939824 100644 --- a/javaagent-core/build.gradle.kts +++ b/javaagent-core/build.gradle.kts @@ -44,6 +44,7 @@ dependencies { api("com.google.protobuf:protobuf-java-util:3.11.4") api("io.grpc:grpc-stub:1.37.0") api("io.grpc:grpc-protobuf:1.37.0") + api("io.grpc:grpc-netty:1.37.0") if (JavaVersion.current().isJava9Compatible) { // Workaround for @javax.annotation.Generated // see: https://github.com/grpc/grpc-java/issues/3633 @@ -53,6 +54,4 @@ dependencies { implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.3") api("com.blogspot.mydailyjava:weak-lock-free:0.17") - - runtimeOnly("io.grpc:grpc-netty-shaded:1.37.0") }