-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #31703 from alesj/gstork1
- Loading branch information
Showing
34 changed files
with
856 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/StorkConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package io.quarkus.grpc.runtime.config; | ||
|
||
import io.quarkus.runtime.annotations.ConfigGroup; | ||
import io.quarkus.runtime.annotations.ConfigItem; | ||
|
||
/** | ||
* Stork config for new Vert.x gRPC | ||
*/ | ||
@ConfigGroup | ||
public class StorkConfig { | ||
/** | ||
* Number of threads on a delayed gRPC ClientCall | ||
*/ | ||
@ConfigItem(defaultValue = "10") | ||
public int threads; | ||
|
||
/** | ||
* Deadline in milliseconds of delayed gRPC call | ||
*/ | ||
@ConfigItem(defaultValue = "5000") | ||
public long deadline; | ||
|
||
/** | ||
* Number of retries on a gRPC ClientCall | ||
*/ | ||
@ConfigItem(defaultValue = "3") | ||
public int retries; | ||
|
||
/** | ||
* Initial delay in seconds on refresh check | ||
*/ | ||
@ConfigItem(defaultValue = "60") | ||
public long delay; | ||
|
||
/** | ||
* Refresh period in seconds | ||
*/ | ||
@ConfigItem(defaultValue = "120") | ||
public long period; | ||
} |
29 changes: 29 additions & 0 deletions
29
.../grpc/runtime/src/main/java/io/quarkus/grpc/runtime/stork/AbstractStorkMeasuringCall.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.quarkus.grpc.runtime.stork; | ||
|
||
import io.grpc.ClientCall; | ||
import io.grpc.ForwardingClientCall; | ||
import io.smallrye.stork.api.ServiceInstance; | ||
|
||
abstract class AbstractStorkMeasuringCall<ReqT, RespT> extends ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT> | ||
implements StorkMeasuringCollector { | ||
final boolean recordTime; | ||
|
||
protected AbstractStorkMeasuringCall(ClientCall<ReqT, RespT> delegate, boolean recordTime) { | ||
super(delegate); | ||
this.recordTime = recordTime; | ||
} | ||
|
||
protected abstract ServiceInstance serviceInstance(); | ||
|
||
public void recordReply() { | ||
if (serviceInstance() != null && recordTime) { | ||
serviceInstance().recordReply(); | ||
} | ||
} | ||
|
||
public void recordEnd(Throwable error) { | ||
if (serviceInstance() != null) { | ||
serviceInstance().recordEnd(error); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
212 changes: 212 additions & 0 deletions
212
extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/stork/StorkGrpcChannel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
package io.quarkus.grpc.runtime.stork; | ||
|
||
import static io.quarkus.grpc.runtime.stork.StorkMeasuringCollector.STORK_MEASURE_TIME; | ||
import static io.quarkus.grpc.runtime.stork.StorkMeasuringCollector.STORK_SERVICE_INSTANCE; | ||
|
||
import java.net.InetAddress; | ||
import java.net.InetSocketAddress; | ||
import java.net.UnknownHostException; | ||
import java.util.ArrayList; | ||
import java.util.Comparator; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.ScheduledExecutorService; | ||
import java.util.concurrent.ScheduledThreadPoolExecutor; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import io.grpc.CallOptions; | ||
import io.grpc.Channel; | ||
import io.grpc.ClientCall; | ||
import io.grpc.Deadline; | ||
import io.grpc.MethodDescriptor; | ||
import io.grpc.internal.DelayedClientCall; | ||
import io.quarkus.grpc.runtime.config.StorkConfig; | ||
import io.smallrye.mutiny.Uni; | ||
import io.smallrye.stork.Stork; | ||
import io.smallrye.stork.api.Service; | ||
import io.smallrye.stork.api.ServiceInstance; | ||
import io.vertx.core.net.SocketAddress; | ||
import io.vertx.grpc.client.GrpcClient; | ||
import io.vertx.grpc.client.GrpcClientChannel; | ||
|
||
public class StorkGrpcChannel extends Channel implements AutoCloseable { | ||
private static final Logger log = LoggerFactory.getLogger(StorkGrpcChannel.class); | ||
|
||
private final Map<Long, ServiceInstance> services = new ConcurrentHashMap<>(); | ||
private final Map<Long, Channel> channels = new ConcurrentHashMap<>(); | ||
private final ScheduledExecutorService scheduler; | ||
|
||
private final GrpcClient client; | ||
private final String serviceName; | ||
private final StorkConfig stork; | ||
private final Executor executor; | ||
|
||
private static class Context { | ||
Service service; | ||
boolean measureTime; | ||
ServiceInstance instance; | ||
InetSocketAddress address; | ||
Channel channel; | ||
AtomicReference<ServiceInstance> ref; | ||
} | ||
|
||
public StorkGrpcChannel(GrpcClient client, String serviceName, StorkConfig stork, Executor executor) { | ||
this.client = client; | ||
this.serviceName = serviceName; | ||
this.stork = stork; | ||
this.executor = executor; | ||
this.scheduler = new ScheduledThreadPoolExecutor(stork.threads); | ||
this.scheduler.scheduleAtFixedRate(this::refresh, stork.delay, stork.period, TimeUnit.SECONDS); | ||
} | ||
|
||
@Override | ||
public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(MethodDescriptor<RequestT, ResponseT> methodDescriptor, | ||
CallOptions callOptions) { | ||
Service service = Stork.getInstance().getService(serviceName); | ||
if (service == null) { | ||
throw new IllegalStateException("No service definition for serviceName " + serviceName + " found."); | ||
} | ||
|
||
Context context = new Context(); | ||
context.service = service; | ||
// handle this calls here | ||
Boolean measureTime = STORK_MEASURE_TIME.get(); | ||
context.measureTime = measureTime != null && measureTime; | ||
context.ref = STORK_SERVICE_INSTANCE.get(); | ||
|
||
DelayedClientCall<RequestT, ResponseT> delayed = new StorkDelayedClientCall<>(executor, scheduler, | ||
Deadline.after(stork.deadline, TimeUnit.MILLISECONDS)); | ||
|
||
asyncCall(methodDescriptor, callOptions, context) | ||
.onFailure() | ||
.retry() | ||
.atMost(stork.retries) | ||
.subscribe() | ||
.asCompletionStage() | ||
.thenApply(delayed::setCall) | ||
.thenAccept(Runnable::run) | ||
.exceptionally(t -> { | ||
delayed.cancel("Failed to create new Stork ClientCall", t); | ||
return null; | ||
}); | ||
|
||
return delayed; | ||
} | ||
|
||
private <RequestT, ResponseT> Uni<ClientCall<RequestT, ResponseT>> asyncCall( | ||
MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions, Context context) { | ||
Uni<Context> entry = pickServiceInstanceWithChannel(context); | ||
return entry.map(c -> { | ||
ServiceInstance instance = c.instance; | ||
long serviceId = instance.getId(); | ||
Channel channel = c.channel; | ||
try { | ||
services.put(serviceId, instance); | ||
channels.put(serviceId, channel); | ||
return channel.newCall(methodDescriptor, callOptions); | ||
} catch (Exception ex) { | ||
// remove, no good | ||
services.remove(serviceId); | ||
channels.remove(serviceId); | ||
throw new IllegalStateException(ex); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public String authority() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
scheduler.shutdown(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return super.toString() + String.format(" [%s]", serviceName); | ||
} | ||
|
||
private void refresh() { | ||
// any better way to know which are OK / bad? | ||
services.clear(); | ||
channels.clear(); | ||
} | ||
|
||
private Uni<Context> pickServiceInstanceWithChannel(Context context) { | ||
Uni<ServiceInstance> uni = pickServerInstance(context.service, context.measureTime); | ||
return uni | ||
.map(si -> { | ||
context.instance = si; | ||
if (si.gatherStatistics() && context.ref != null) { | ||
context.ref.set(si); | ||
} | ||
return context; | ||
}) | ||
.invoke(this::checkSocketAddress) | ||
.invoke(c -> { | ||
ServiceInstance instance = context.instance; | ||
InetSocketAddress isa = context.address; | ||
context.channel = channels.computeIfAbsent(instance.getId(), id -> { | ||
SocketAddress address = SocketAddress.inetSocketAddress(isa.getPort(), isa.getHostName()); | ||
return new GrpcClientChannel(client, address); | ||
}); | ||
}); | ||
} | ||
|
||
private Uni<ServiceInstance> pickServerInstance(Service service, boolean measureTime) { | ||
return Uni.createFrom() | ||
.deferred(() -> { | ||
if (services.isEmpty()) { | ||
return service.getInstances() | ||
.invoke(l -> l.forEach(s -> services.put(s.getId(), s))); | ||
} else { | ||
List<ServiceInstance> list = new ArrayList<>(services.values()); | ||
return Uni.createFrom().item(list); | ||
} | ||
}) | ||
.invoke(list -> { | ||
// list should not be empty + sort by id | ||
list.sort(Comparator.comparing(ServiceInstance::getId)); | ||
}) | ||
.map(list -> service.selectInstanceAndRecordStart(list, measureTime)); | ||
} | ||
|
||
private void checkSocketAddress(Context context) { | ||
ServiceInstance instance = context.instance; | ||
Set<InetSocketAddress> socketAddresses = new HashSet<>(); | ||
try { | ||
for (InetAddress inetAddress : InetAddress.getAllByName(instance.getHost())) { | ||
socketAddresses.add(new InetSocketAddress(inetAddress, instance.getPort())); | ||
} | ||
} catch (UnknownHostException e) { | ||
log.warn("Ignoring wrong host: '{}' for service name '{}'", instance.getHost(), serviceName, e); | ||
} | ||
|
||
if (!socketAddresses.isEmpty()) { | ||
context.address = socketAddresses.iterator().next(); // pick first | ||
} else { | ||
long serviceId = instance.getId(); | ||
services.remove(serviceId); | ||
channels.remove(serviceId); | ||
throw new IllegalStateException("Failed to determine working socket addresses for service-name: " + serviceName); | ||
} | ||
} | ||
|
||
private static class StorkDelayedClientCall<RequestT, ResponseT> extends DelayedClientCall<RequestT, ResponseT> { | ||
public StorkDelayedClientCall(Executor callExecutor, ScheduledExecutorService scheduler, @Nullable Deadline deadline) { | ||
super(callExecutor, scheduler, deadline); | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/stork/StorkMeasuringCall.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.quarkus.grpc.runtime.stork; | ||
|
||
import io.grpc.ClientCall; | ||
import io.grpc.ForwardingClientCall; | ||
import io.smallrye.stork.api.ServiceInstance; | ||
|
||
abstract class StorkMeasuringCall<ReqT, RespT> extends ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT> | ||
implements StorkMeasuringCollector { | ||
final boolean recordTime; | ||
|
||
protected StorkMeasuringCall(ClientCall<ReqT, RespT> delegate, boolean recordTime) { | ||
super(delegate); | ||
this.recordTime = recordTime; | ||
} | ||
|
||
protected abstract ServiceInstance serviceInstance(); | ||
|
||
public void recordReply() { | ||
if (serviceInstance() != null && recordTime) { | ||
serviceInstance().recordReply(); | ||
} | ||
} | ||
|
||
public void recordEnd(Throwable error) { | ||
if (serviceInstance() != null) { | ||
serviceInstance().recordEnd(error); | ||
} | ||
} | ||
} |
Oops, something went wrong.