Make sure forceFlush / shutdown can have callers wait for them to be done by returning CompletableResultCode. (#1571)
* Make sure forceFlush / shutdown can have callers wait for them to be done by returning CompletableResultCode. * Merge
This commit is contained in:
parent
f680094039
commit
c6c179c267
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.exporters.inmemory;
|
package io.opentelemetry.exporters.inmemory;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import io.opentelemetry.sdk.metrics.export.MetricExporter;
|
import io.opentelemetry.sdk.metrics.export.MetricExporter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.exporters.inmemory;
|
package io.opentelemetry.exporters.inmemory;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -108,11 +108,12 @@ public final class InMemorySpanExporter implements SpanExporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
finishedSpanItems.clear();
|
finishedSpanItems.clear();
|
||||||
isStopped = true;
|
isStopped = true;
|
||||||
}
|
}
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private InMemorySpanExporter() {}
|
private InMemorySpanExporter() {}
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,14 @@ package io.opentelemetry.exporters.jaeger;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import io.grpc.ConnectivityState;
|
||||||
import io.grpc.ManagedChannel;
|
import io.grpc.ManagedChannel;
|
||||||
import io.grpc.ManagedChannelBuilder;
|
import io.grpc.ManagedChannelBuilder;
|
||||||
import io.opentelemetry.exporters.jaeger.proto.api_v2.Collector;
|
import io.opentelemetry.exporters.jaeger.proto.api_v2.Collector;
|
||||||
import io.opentelemetry.exporters.jaeger.proto.api_v2.Collector.PostSpansResponse;
|
import io.opentelemetry.exporters.jaeger.proto.api_v2.Collector.PostSpansResponse;
|
||||||
import io.opentelemetry.exporters.jaeger.proto.api_v2.CollectorServiceGrpc;
|
import io.opentelemetry.exporters.jaeger.proto.api_v2.CollectorServiceGrpc;
|
||||||
import io.opentelemetry.exporters.jaeger.proto.api_v2.Model;
|
import io.opentelemetry.exporters.jaeger.proto.api_v2.Model;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||||
|
|
@ -168,15 +169,21 @@ public final class JaegerGrpcSpanExporter implements SpanExporter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
|
* Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
|
||||||
* cancelled. The channel is forcefully closed after a timeout.
|
* cancelled.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
try {
|
final CompletableResultCode result = new CompletableResultCode();
|
||||||
managedChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
|
managedChannel.notifyWhenStateChanged(
|
||||||
} catch (InterruptedException e) {
|
ConnectivityState.SHUTDOWN,
|
||||||
logger.log(Level.WARNING, "Failed to shutdown the gRPC channel", e);
|
new Runnable() {
|
||||||
}
|
@Override
|
||||||
|
public void run() {
|
||||||
|
result.succeed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
managedChannel.shutdown();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Builder utility for this exporter. */
|
/** Builder utility for this exporter. */
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.exporters.logging;
|
package io.opentelemetry.exporters.logging;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import io.opentelemetry.sdk.metrics.export.MetricExporter;
|
import io.opentelemetry.sdk.metrics.export.MetricExporter;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.exporters.logging;
|
package io.opentelemetry.exporters.logging;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
@ -55,7 +55,7 @@ public class LoggingSpanExporter implements SpanExporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
this.flush();
|
return flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
import io.opentelemetry.common.AttributeValue;
|
import io.opentelemetry.common.AttributeValue;
|
||||||
import io.opentelemetry.common.Attributes;
|
import io.opentelemetry.common.Attributes;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.TestSpanData;
|
import io.opentelemetry.sdk.trace.TestSpanData;
|
||||||
import io.opentelemetry.sdk.trace.data.EventImpl;
|
import io.opentelemetry.sdk.trace.data.EventImpl;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;
|
||||||
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse;
|
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse;
|
||||||
import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc;
|
import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc;
|
||||||
import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc.MetricsServiceFutureStub;
|
import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc.MetricsServiceFutureStub;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import io.opentelemetry.sdk.metrics.export.MetricExporter;
|
import io.opentelemetry.sdk.metrics.export.MetricExporter;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import com.google.common.base.Splitter;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import io.grpc.ConnectivityState;
|
||||||
import io.grpc.ManagedChannel;
|
import io.grpc.ManagedChannel;
|
||||||
import io.grpc.ManagedChannelBuilder;
|
import io.grpc.ManagedChannelBuilder;
|
||||||
import io.grpc.Metadata;
|
import io.grpc.Metadata;
|
||||||
|
|
@ -30,7 +31,7 @@ import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
|
||||||
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse;
|
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse;
|
||||||
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc;
|
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc;
|
||||||
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc.TraceServiceFutureStub;
|
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc.TraceServiceFutureStub;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||||
|
|
@ -169,15 +170,21 @@ public final class OtlpGrpcSpanExporter implements SpanExporter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
|
* Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
|
||||||
* cancelled. The channel is forcefully closed after a timeout.
|
* cancelled.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
try {
|
final CompletableResultCode result = new CompletableResultCode();
|
||||||
managedChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
|
managedChannel.notifyWhenStateChanged(
|
||||||
} catch (InterruptedException e) {
|
ConnectivityState.SHUTDOWN,
|
||||||
logger.log(Level.WARNING, "Failed to shutdown the gRPC channel", e);
|
new Runnable() {
|
||||||
}
|
@Override
|
||||||
|
public void run() {
|
||||||
|
result.succeed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
managedChannel.shutdown();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Builder utility for this exporter. */
|
/** Builder utility for this exporter. */
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
import io.opentelemetry.common.AttributeValue;
|
import io.opentelemetry.common.AttributeValue;
|
||||||
import io.opentelemetry.common.ReadableAttributes;
|
import io.opentelemetry.common.ReadableAttributes;
|
||||||
import io.opentelemetry.common.ReadableKeyValuePairs.KeyValueConsumer;
|
import io.opentelemetry.common.ReadableKeyValuePairs.KeyValueConsumer;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.resources.ResourceAttributes;
|
import io.opentelemetry.sdk.resources.ResourceAttributes;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
|
|
@ -289,12 +289,13 @@ public final class ZipkinSpanExporter implements SpanExporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
try {
|
try {
|
||||||
sender.close();
|
sender.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.WARNING, "Exception while closing the Zipkin Sender instance", e);
|
logger.log(Level.WARNING, "Exception while closing the Zipkin Sender instance", e);
|
||||||
}
|
}
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import io.opentelemetry.common.Attributes;
|
import io.opentelemetry.common.Attributes;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.TestSpanData;
|
import io.opentelemetry.sdk.trace.TestSpanData;
|
||||||
import io.opentelemetry.sdk.trace.data.EventImpl;
|
import io.opentelemetry.sdk.trace.data.EventImpl;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ import static org.mockito.Mockito.when;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import io.opentelemetry.common.AttributeValue;
|
import io.opentelemetry.common.AttributeValue;
|
||||||
import io.opentelemetry.common.Attributes;
|
import io.opentelemetry.common.Attributes;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.resources.Resource;
|
import io.opentelemetry.sdk.resources.Resource;
|
||||||
import io.opentelemetry.sdk.resources.ResourceAttributes;
|
import io.opentelemetry.sdk.resources.ResourceAttributes;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import static io.opentelemetry.common.AttributeValue.stringAttributeValue;
|
||||||
|
|
||||||
import io.opentelemetry.common.Attributes;
|
import io.opentelemetry.common.Attributes;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
|
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
|
||||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||||
|
|
@ -98,8 +98,9 @@ public class SpanPipelineBenchmark {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
// no-op
|
// no-op
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020, OpenTelemetry 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.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import io.opentelemetry.OpenTelemetry;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
|
import io.opentelemetry.trace.Span;
|
||||||
|
import io.opentelemetry.trace.Tracer;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Level;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Threads;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
public class BatchSpanProcessorBenchmark {
|
||||||
|
|
||||||
|
private static class DelayingSpanExporter implements SpanExporter {
|
||||||
|
|
||||||
|
private final ScheduledExecutorService executor;
|
||||||
|
|
||||||
|
private final int delayMs;
|
||||||
|
|
||||||
|
private DelayingSpanExporter(int delayMs) {
|
||||||
|
executor = Executors.newScheduledThreadPool(5);
|
||||||
|
this.delayMs = delayMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("FutureReturnValueIgnored")
|
||||||
|
@Override
|
||||||
|
public CompletableResultCode export(Collection<SpanData> spans) {
|
||||||
|
final CompletableResultCode result = new CompletableResultCode();
|
||||||
|
executor.schedule(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
result.succeed();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delayMs,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableResultCode flush() {
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableResultCode shutdown() {
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Param({"0", "1", "5"})
|
||||||
|
private int delayMs;
|
||||||
|
|
||||||
|
@Param({"1000", "2000", "5000"})
|
||||||
|
private int spanCount;
|
||||||
|
|
||||||
|
private List<Span> spans;
|
||||||
|
|
||||||
|
private BatchSpanProcessor processor;
|
||||||
|
|
||||||
|
@Setup(Level.Trial)
|
||||||
|
public final void setup() {
|
||||||
|
SpanExporter exporter = new DelayingSpanExporter(delayMs);
|
||||||
|
processor = BatchSpanProcessor.newBuilder(exporter).build();
|
||||||
|
|
||||||
|
ImmutableList.Builder<Span> spans = ImmutableList.builderWithExpectedSize(spanCount);
|
||||||
|
Tracer tracer = OpenTelemetry.getTracerProvider().get("benchmarkTracer");
|
||||||
|
for (int i = 0; i < spanCount; i++) {
|
||||||
|
spans.add(tracer.spanBuilder("span").startSpan());
|
||||||
|
}
|
||||||
|
this.spans = spans.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Export spans through {@link io.opentelemetry.sdk.trace.export.BatchSpanProcessor}. */
|
||||||
|
@Benchmark
|
||||||
|
@Fork(1)
|
||||||
|
@Threads(5)
|
||||||
|
@Warmup(iterations = 5, time = 1)
|
||||||
|
@Measurement(iterations = 10, time = 1)
|
||||||
|
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||||
|
public void export() {
|
||||||
|
for (Span span : spans) {
|
||||||
|
processor.onEnd((ReadableSpan) span);
|
||||||
|
}
|
||||||
|
processor.forceFlush().join(10, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ package io.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
import io.opentelemetry.OpenTelemetry;
|
import io.opentelemetry.OpenTelemetry;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData.LongPoint;
|
import io.opentelemetry.sdk.metrics.data.MetricData.LongPoint;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData.Point;
|
import io.opentelemetry.sdk.metrics.data.MetricData.Point;
|
||||||
|
|
@ -58,7 +58,9 @@ public class BatchSpanProcessorDroppedSpansBenchmark {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {}
|
public CompletableResultCode shutdown() {
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@State(Scope.Benchmark)
|
@State(Scope.Benchmark)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ package io.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import io.opentelemetry.OpenTelemetry;
|
import io.opentelemetry.OpenTelemetry;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.trace.Span;
|
import io.opentelemetry.trace.Span;
|
||||||
|
|
@ -76,7 +76,9 @@ public class BatchSpanProcessorFlushBenchmark {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {}
|
public CompletableResultCode shutdown() {
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Param({"0", "1", "5"})
|
@Param({"0", "1", "5"})
|
||||||
|
|
@ -113,6 +115,6 @@ public class BatchSpanProcessorFlushBenchmark {
|
||||||
for (Span span : spans) {
|
for (Span span : spans) {
|
||||||
processor.onEnd((ReadableSpan) span);
|
processor.onEnd((ReadableSpan) span);
|
||||||
}
|
}
|
||||||
processor.flush().join(10, TimeUnit.MINUTES);
|
processor.forceFlush().join(10, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.trace.export;
|
package io.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.TestSpanData;
|
import io.opentelemetry.sdk.trace.TestSpanData;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.trace.Span;
|
import io.opentelemetry.trace.Span;
|
||||||
|
|
@ -54,7 +54,9 @@ public class MultiSpanExporterBenchmark {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {}
|
public CompletableResultCode shutdown() {
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Param({"1", "3"})
|
@Param({"1", "3"})
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,15 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.sdk.common.export;
|
package io.opentelemetry.sdk.common;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
||||||
|
|
@ -41,6 +44,35 @@ public class CompletableResultCode {
|
||||||
return FAILURE;
|
return FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link CompletableResultCode} that completes after all the provided {@link
|
||||||
|
* CompletableResultCode}s complete. If any of the results fail, the result will be failed.
|
||||||
|
*/
|
||||||
|
public static CompletableResultCode ofAll(final Collection<CompletableResultCode> codes) {
|
||||||
|
final CompletableResultCode result = new CompletableResultCode();
|
||||||
|
final AtomicInteger pending = new AtomicInteger(codes.size());
|
||||||
|
final AtomicBoolean failed = new AtomicBoolean();
|
||||||
|
for (final CompletableResultCode code : codes) {
|
||||||
|
code.whenComplete(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!code.isSuccess()) {
|
||||||
|
failed.set(true);
|
||||||
|
}
|
||||||
|
if (pending.decrementAndGet() == 0) {
|
||||||
|
if (failed.get()) {
|
||||||
|
result.fail();
|
||||||
|
} else {
|
||||||
|
result.succeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static final CompletableResultCode SUCCESS = new CompletableResultCode().succeed();
|
private static final CompletableResultCode SUCCESS = new CompletableResultCode().succeed();
|
||||||
private static final CompletableResultCode FAILURE = new CompletableResultCode().fail();
|
private static final CompletableResultCode FAILURE = new CompletableResultCode().fail();
|
||||||
|
|
||||||
|
|
@ -14,12 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.sdk.common.export;
|
package io.opentelemetry.sdk.common;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.awaitility.Awaitility.await;
|
import static org.awaitility.Awaitility.await;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.Uninterruptibles;
|
import com.google.common.util.concurrent.Uninterruptibles;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -200,6 +201,36 @@ class CompletableResultCodeTest {
|
||||||
assertThat(result.isDone()).isTrue();
|
assertThat(result.isDone()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void ofAll() {
|
||||||
|
CompletableResultCode result1 = new CompletableResultCode();
|
||||||
|
CompletableResultCode result2 = new CompletableResultCode();
|
||||||
|
CompletableResultCode result3 = new CompletableResultCode();
|
||||||
|
|
||||||
|
CompletableResultCode all =
|
||||||
|
CompletableResultCode.ofAll(Arrays.asList(result1, result2, result3));
|
||||||
|
assertThat(all.isDone()).isFalse();
|
||||||
|
result1.succeed();
|
||||||
|
assertThat(all.isDone()).isFalse();
|
||||||
|
result2.succeed();
|
||||||
|
assertThat(all.isDone()).isFalse();
|
||||||
|
result3.succeed();
|
||||||
|
assertThat(all.isDone()).isTrue();
|
||||||
|
assertThat(all.isSuccess()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void ofAllWithFailure() {
|
||||||
|
assertThat(
|
||||||
|
CompletableResultCode.ofAll(
|
||||||
|
Arrays.asList(
|
||||||
|
CompletableResultCode.ofSuccess(),
|
||||||
|
CompletableResultCode.ofFailure(),
|
||||||
|
CompletableResultCode.ofSuccess()))
|
||||||
|
.isSuccess())
|
||||||
|
.isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void join() {
|
void join() {
|
||||||
CompletableResultCode result = new CompletableResultCode();
|
CompletableResultCode result = new CompletableResultCode();
|
||||||
|
|
@ -18,8 +18,8 @@ package io.opentelemetry.sdk.metrics.export;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
import io.opentelemetry.internal.Utils;
|
import io.opentelemetry.internal.Utils;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.DaemonThreadFactory;
|
import io.opentelemetry.sdk.common.DaemonThreadFactory;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.metrics.export;
|
package io.opentelemetry.sdk.metrics.export;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import io.opentelemetry.common.Labels;
|
import io.opentelemetry.common.Labels;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilderTest.ConfigTester;
|
import io.opentelemetry.sdk.common.export.ConfigBuilderTest.ConfigTester;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData;
|
import io.opentelemetry.sdk.metrics.data.MetricData;
|
||||||
import io.opentelemetry.sdk.metrics.data.MetricData.Descriptor;
|
import io.opentelemetry.sdk.metrics.data.MetricData.Descriptor;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.trace;
|
package io.opentelemetry.sdk.trace;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
@ -66,17 +67,21 @@ public final class MultiSpanProcessor implements SpanProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
|
List<CompletableResultCode> results = new ArrayList<>(spanProcessorsAll.size());
|
||||||
for (SpanProcessor spanProcessor : spanProcessorsAll) {
|
for (SpanProcessor spanProcessor : spanProcessorsAll) {
|
||||||
spanProcessor.shutdown();
|
results.add(spanProcessor.shutdown());
|
||||||
}
|
}
|
||||||
|
return CompletableResultCode.ofAll(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {
|
public CompletableResultCode forceFlush() {
|
||||||
|
List<CompletableResultCode> results = new ArrayList<>(spanProcessorsAll.size());
|
||||||
for (SpanProcessor spanProcessor : spanProcessorsAll) {
|
for (SpanProcessor spanProcessor : spanProcessorsAll) {
|
||||||
spanProcessor.forceFlush();
|
results.add(spanProcessor.forceFlush());
|
||||||
}
|
}
|
||||||
|
return CompletableResultCode.ofAll(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MultiSpanProcessor(List<SpanProcessor> spanProcessors) {
|
private MultiSpanProcessor(List<SpanProcessor> spanProcessors) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.trace;
|
package io.opentelemetry.sdk.trace;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
|
|
||||||
final class NoopSpanProcessor implements SpanProcessor {
|
final class NoopSpanProcessor implements SpanProcessor {
|
||||||
private static final NoopSpanProcessor INSTANCE = new NoopSpanProcessor();
|
private static final NoopSpanProcessor INSTANCE = new NoopSpanProcessor();
|
||||||
|
|
||||||
|
|
@ -40,10 +42,14 @@ final class NoopSpanProcessor implements SpanProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {}
|
public CompletableResultCode shutdown() {
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {}
|
public CompletableResultCode forceFlush() {
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
private NoopSpanProcessor() {}
|
private NoopSpanProcessor() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.trace;
|
package io.opentelemetry.sdk.trace;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.trace.Span;
|
import io.opentelemetry.trace.Span;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,17 +61,17 @@ public interface SpanProcessor {
|
||||||
boolean isEndRequired();
|
boolean isEndRequired();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when {@link TracerSdkProvider#shutdown()} is called.
|
* Processes all span events that have not yet been processed and closes used resources.
|
||||||
*
|
*
|
||||||
* <p>Implementations must ensure that all span events are processed before returning.
|
* @return a {@link CompletableResultCode} which completes when shutdown is finished.
|
||||||
*/
|
*/
|
||||||
void shutdown();
|
CompletableResultCode shutdown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes all span events that have not yet been processed.
|
* Processes all span events that have not yet been processed.
|
||||||
*
|
*
|
||||||
* <p>This method is executed synchronously on the calling thread, and should not throw
|
* @return a {@link CompletableResultCode} which completes when currently queued spans are
|
||||||
* exceptions.
|
* finished processing.
|
||||||
*/
|
*/
|
||||||
void forceFlush();
|
CompletableResultCode forceFlush();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.opentelemetry.sdk.resources.Resource;
|
||||||
import io.opentelemetry.sdk.trace.config.TraceConfig;
|
import io.opentelemetry.sdk.trace.config.TraceConfig;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
||||||
// Represents the shared state/config between all Tracers created by the same TracerProvider.
|
// Represents the shared state/config between all Tracers created by the same TracerProvider.
|
||||||
|
|
@ -113,7 +114,7 @@ final class TracerSharedState {
|
||||||
if (isStopped) {
|
if (isStopped) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activeSpanProcessor.shutdown();
|
activeSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
isStopped = true;
|
isStopped = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import io.opentelemetry.internal.Utils;
|
||||||
import io.opentelemetry.metrics.LongCounter;
|
import io.opentelemetry.metrics.LongCounter;
|
||||||
import io.opentelemetry.metrics.LongCounter.BoundLongCounter;
|
import io.opentelemetry.metrics.LongCounter.BoundLongCounter;
|
||||||
import io.opentelemetry.metrics.Meter;
|
import io.opentelemetry.metrics.Meter;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.DaemonThreadFactory;
|
import io.opentelemetry.sdk.common.DaemonThreadFactory;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
|
|
@ -123,18 +123,12 @@ public final class BatchSpanProcessor implements SpanProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
worker.shutdown();
|
return worker.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {
|
public CompletableResultCode forceFlush() {
|
||||||
worker.forceFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO remove this when this.forceFlush returns CompletableResultCode
|
|
||||||
@VisibleForTesting
|
|
||||||
CompletableResultCode flush() {
|
|
||||||
return worker.forceFlush();
|
return worker.forceFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,15 +235,31 @@ public final class BatchSpanProcessor implements SpanProcessor {
|
||||||
nextExportTime = System.nanoTime() + scheduleDelayNanos;
|
nextExportTime = System.nanoTime() + scheduleDelayNanos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdown() {
|
private CompletableResultCode shutdown() {
|
||||||
long pendingBatchesCountInQueue = queue.size() / maxExportBatchSize + 1L;
|
final CompletableResultCode result = new CompletableResultCode();
|
||||||
long pendingBatchesCount = pendingBatchesCountInQueue + 1;
|
|
||||||
long shutdownTimeout = pendingBatchesCount * exporterTimeoutMillis;
|
|
||||||
|
|
||||||
forceFlush().join(shutdownTimeout, TimeUnit.MILLISECONDS);
|
final CompletableResultCode flushResult = forceFlush();
|
||||||
|
flushResult.whenComplete(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
continueWork = false;
|
||||||
|
final CompletableResultCode shutdownResult = spanExporter.shutdown();
|
||||||
|
shutdownResult.whenComplete(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!flushResult.isSuccess() || !shutdownResult.isSuccess()) {
|
||||||
|
result.fail();
|
||||||
|
} else {
|
||||||
|
result.succeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
spanExporter.shutdown();
|
return result;
|
||||||
continueWork = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableResultCode forceFlush() {
|
private CompletableResultCode forceFlush() {
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.trace.export;
|
package io.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
@ -33,6 +33,7 @@ import java.util.logging.Logger;
|
||||||
*/
|
*/
|
||||||
public final class MultiSpanExporter implements SpanExporter {
|
public final class MultiSpanExporter implements SpanExporter {
|
||||||
private static final Logger logger = Logger.getLogger(MultiSpanExporter.class.getName());
|
private static final Logger logger = Logger.getLogger(MultiSpanExporter.class.getName());
|
||||||
|
|
||||||
private final SpanExporter[] spanExporters;
|
private final SpanExporter[] spanExporters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,19 +48,20 @@ public final class MultiSpanExporter implements SpanExporter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableResultCode export(Collection<SpanData> spans) {
|
public CompletableResultCode export(Collection<SpanData> spans) {
|
||||||
final CompletableResultCode compositeResultCode = new CompletableResultCode();
|
List<CompletableResultCode> results = new ArrayList<>(spanExporters.length);
|
||||||
final AtomicInteger completionsToProcess = new AtomicInteger(spanExporters.length);
|
|
||||||
for (SpanExporter spanExporter : spanExporters) {
|
for (SpanExporter spanExporter : spanExporters) {
|
||||||
|
final CompletableResultCode exportResult;
|
||||||
try {
|
try {
|
||||||
final CompletableResultCode singleResult = spanExporter.export(spans);
|
exportResult = spanExporter.export(spans);
|
||||||
mergeResultCode(compositeResultCode, singleResult, completionsToProcess);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// If an exception was thrown by the exporter
|
// If an exception was thrown by the exporter
|
||||||
logger.log(Level.WARNING, "Exception thrown by the export.", e);
|
logger.log(Level.WARNING, "Exception thrown by the export.", e);
|
||||||
compositeResultCode.fail();
|
results.add(CompletableResultCode.ofFailure());
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
results.add(exportResult);
|
||||||
}
|
}
|
||||||
return compositeResultCode;
|
return CompletableResultCode.ofAll(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,45 +71,38 @@ public final class MultiSpanExporter implements SpanExporter {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public CompletableResultCode flush() {
|
public CompletableResultCode flush() {
|
||||||
final CompletableResultCode compositeResultCode = new CompletableResultCode();
|
List<CompletableResultCode> results = new ArrayList<>(spanExporters.length);
|
||||||
final AtomicInteger completionsToProcess = new AtomicInteger(spanExporters.length);
|
|
||||||
for (SpanExporter spanExporter : spanExporters) {
|
for (SpanExporter spanExporter : spanExporters) {
|
||||||
|
final CompletableResultCode flushResult;
|
||||||
try {
|
try {
|
||||||
mergeResultCode(compositeResultCode, spanExporter.flush(), completionsToProcess);
|
flushResult = spanExporter.flush();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// If an exception was thrown by the exporter
|
// If an exception was thrown by the exporter
|
||||||
logger.log(Level.WARNING, "Exception thrown by the flush.", e);
|
logger.log(Level.WARNING, "Exception thrown by the flush.", e);
|
||||||
compositeResultCode.fail();
|
results.add(CompletableResultCode.ofFailure());
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
results.add(flushResult);
|
||||||
}
|
}
|
||||||
return compositeResultCode;
|
return CompletableResultCode.ofAll(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
|
List<CompletableResultCode> results = new ArrayList<>(spanExporters.length);
|
||||||
for (SpanExporter spanExporter : spanExporters) {
|
for (SpanExporter spanExporter : spanExporters) {
|
||||||
spanExporter.shutdown();
|
final CompletableResultCode shutdownResult;
|
||||||
|
try {
|
||||||
|
shutdownResult = spanExporter.shutdown();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// If an exception was thrown by the exporter
|
||||||
|
logger.log(Level.WARNING, "Exception thrown by the shutdown.", e);
|
||||||
|
results.add(CompletableResultCode.ofFailure());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.add(shutdownResult);
|
||||||
}
|
}
|
||||||
}
|
return CompletableResultCode.ofAll(results);
|
||||||
|
|
||||||
private static void mergeResultCode(
|
|
||||||
final CompletableResultCode compositeResultCode,
|
|
||||||
final CompletableResultCode singleResultCode,
|
|
||||||
final AtomicInteger completionsToProcess) {
|
|
||||||
singleResultCode.whenComplete(
|
|
||||||
new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
int completionsRemaining = completionsToProcess.decrementAndGet();
|
|
||||||
if (singleResultCode.isSuccess()) {
|
|
||||||
if (completionsRemaining == 0) {
|
|
||||||
compositeResultCode.succeed();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
compositeResultCode.fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MultiSpanExporter(List<SpanExporter> spanExporters) {
|
private MultiSpanExporter(List<SpanExporter> spanExporters) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.opentelemetry.sdk.trace.export;
|
package io.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
|
|
@ -102,13 +102,14 @@ public final class SimpleSpanProcessor implements SpanProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
spanExporter.shutdown();
|
return spanExporter.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {
|
public CompletableResultCode forceFlush() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.trace.export;
|
package io.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.TracerSdkProvider;
|
import io.opentelemetry.sdk.trace.TracerSdkProvider;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
@ -53,6 +53,8 @@ public interface SpanExporter {
|
||||||
/**
|
/**
|
||||||
* Called when {@link TracerSdkProvider#shutdown()} is called, if this {@code SpanExporter} is
|
* Called when {@link TracerSdkProvider#shutdown()} is called, if this {@code SpanExporter} is
|
||||||
* register to a {@code TracerSdkProvider} object.
|
* register to a {@code TracerSdkProvider} object.
|
||||||
|
*
|
||||||
|
* @return a {@link CompletableResultCode} which is completed when shutdown completes.
|
||||||
*/
|
*/
|
||||||
void shutdown();
|
CompletableResultCode shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
@ -42,8 +43,12 @@ class MultiSpanProcessorTest {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
when(spanProcessor1.isStartRequired()).thenReturn(true);
|
when(spanProcessor1.isStartRequired()).thenReturn(true);
|
||||||
when(spanProcessor1.isEndRequired()).thenReturn(true);
|
when(spanProcessor1.isEndRequired()).thenReturn(true);
|
||||||
|
when(spanProcessor1.forceFlush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
|
when(spanProcessor1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
when(spanProcessor2.isStartRequired()).thenReturn(true);
|
when(spanProcessor2.isStartRequired()).thenReturn(true);
|
||||||
when(spanProcessor2.isEndRequired()).thenReturn(true);
|
when(spanProcessor2.isEndRequired()).thenReturn(true);
|
||||||
|
when(spanProcessor2.forceFlush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
|
when(spanProcessor2.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,8 @@ abstract class StressTestRunner {
|
||||||
for (Thread thread : operationThreads) {
|
for (Thread thread : operationThreads) {
|
||||||
Uninterruptibles.joinUninterruptibly(thread);
|
Uninterruptibles.joinUninterruptibly(thread);
|
||||||
}
|
}
|
||||||
getSpanProcessor().shutdown();
|
|
||||||
|
getSpanProcessor().shutdown().join(1, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Builder builder() {
|
static Builder builder() {
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@ package io.opentelemetry.sdk.trace;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.Clock;
|
import io.opentelemetry.sdk.common.Clock;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
||||||
import io.opentelemetry.sdk.resources.Resource;
|
import io.opentelemetry.sdk.resources.Resource;
|
||||||
import io.opentelemetry.sdk.trace.config.TraceConfig;
|
import io.opentelemetry.sdk.trace.config.TraceConfig;
|
||||||
|
|
@ -28,18 +30,24 @@ import io.opentelemetry.trace.DefaultSpan;
|
||||||
import io.opentelemetry.trace.Span;
|
import io.opentelemetry.trace.Span;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
|
import org.mockito.quality.Strictness;
|
||||||
|
|
||||||
/** Unit tests for {@link TracerSdkProvider}. */
|
/** Unit tests for {@link TracerSdkProvider}. */
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
class TracerSdkProviderTest {
|
class TracerSdkProviderTest {
|
||||||
@Mock private SpanProcessor spanProcessor;
|
@Mock private SpanProcessor spanProcessor;
|
||||||
private final TracerSdkProvider tracerFactory = TracerSdkProvider.builder().build();
|
private final TracerSdkProvider tracerFactory = TracerSdkProvider.builder().build();
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
when(spanProcessor.forceFlush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
|
when(spanProcessor.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
tracerFactory.addSpanProcessor(spanProcessor);
|
tracerFactory.addSpanProcessor(spanProcessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import io.grpc.Context;
|
import io.grpc.Context;
|
||||||
import io.opentelemetry.common.AttributeValue;
|
import io.opentelemetry.common.AttributeValue;
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
|
||||||
import io.opentelemetry.sdk.trace.StressTestRunner.OperationUpdater;
|
import io.opentelemetry.sdk.trace.StressTestRunner.OperationUpdater;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
|
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
|
||||||
|
|
@ -185,13 +185,15 @@ class TracerSdkTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
// no-op
|
// no-op
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {
|
public CompletableResultCode forceFlush() {
|
||||||
// no-op
|
// no-op
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,8 +231,9 @@ class TracerSdkTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
// no-op
|
// no-op
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@ package io.opentelemetry.sdk.trace.export;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.awaitility.Awaitility.await;
|
import static org.awaitility.Awaitility.await;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilderTest.ConfigTester;
|
import io.opentelemetry.sdk.common.export.ConfigBuilderTest.ConfigTester;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
import io.opentelemetry.sdk.trace.Samplers;
|
import io.opentelemetry.sdk.trace.Samplers;
|
||||||
|
|
@ -208,7 +209,7 @@ class BatchSpanProcessorTest {
|
||||||
assertThat(exported).isNotNull();
|
assertThat(exported).isNotNull();
|
||||||
assertThat(exported.size()).isEqualTo(98);
|
assertThat(exported.size()).isEqualTo(98);
|
||||||
|
|
||||||
batchSpanProcessor.flush().join(10, TimeUnit.SECONDS);
|
batchSpanProcessor.forceFlush().join(10, TimeUnit.SECONDS);
|
||||||
exported = waitingSpanExporter.getExported();
|
exported = waitingSpanExporter.getExported();
|
||||||
assertThat(exported).isNotNull();
|
assertThat(exported).isNotNull();
|
||||||
assertThat(exported.size()).isEqualTo(2);
|
assertThat(exported.size()).isEqualTo(2);
|
||||||
|
|
@ -450,6 +451,24 @@ class BatchSpanProcessorTest {
|
||||||
assertThat(waitingSpanExporter.shutDownCalled.get()).isTrue();
|
assertThat(waitingSpanExporter.shutDownCalled.get()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shutdownPropagatesSuccess() {
|
||||||
|
when(mockServiceHandler.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
|
BatchSpanProcessor processor = BatchSpanProcessor.newBuilder(mockServiceHandler).build();
|
||||||
|
CompletableResultCode result = processor.shutdown();
|
||||||
|
result.join(1, TimeUnit.SECONDS);
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shutdownPropagatesFailure() {
|
||||||
|
when(mockServiceHandler.shutdown()).thenReturn(CompletableResultCode.ofFailure());
|
||||||
|
BatchSpanProcessor processor = BatchSpanProcessor.newBuilder(mockServiceHandler).build();
|
||||||
|
CompletableResultCode result = processor.shutdown();
|
||||||
|
result.join(1, TimeUnit.SECONDS);
|
||||||
|
assertThat(result.isSuccess()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
private static final class BlockingSpanExporter implements SpanExporter {
|
private static final class BlockingSpanExporter implements SpanExporter {
|
||||||
|
|
||||||
final Object monitor = new Object();
|
final Object monitor = new Object();
|
||||||
|
|
@ -498,8 +517,9 @@ class BatchSpanProcessorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
// Do nothing;
|
// Do nothing;
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unblock() {
|
private void unblock() {
|
||||||
|
|
@ -548,8 +568,8 @@ class BatchSpanProcessorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
flush();
|
return flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -613,8 +633,9 @@ class BatchSpanProcessorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
shutDownCalled.set(true);
|
shutDownCalled.set(true);
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
|
|
||||||
|
|
@ -18,32 +18,29 @@ package io.opentelemetry.sdk.trace.export;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.trace.TestUtils;
|
import io.opentelemetry.sdk.trace.TestUtils;
|
||||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentMatchers;
|
import org.mockito.ArgumentMatchers;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
/** Unit tests for {@link MultiSpanExporterTest}. */
|
/** Unit tests for {@link MultiSpanExporterTest}. */
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
class MultiSpanExporterTest {
|
class MultiSpanExporterTest {
|
||||||
@Mock private SpanExporter spanExporter1;
|
@Mock private SpanExporter spanExporter1;
|
||||||
@Mock private SpanExporter spanExporter2;
|
@Mock private SpanExporter spanExporter2;
|
||||||
private static final List<SpanData> SPAN_LIST =
|
private static final List<SpanData> SPAN_LIST =
|
||||||
Collections.singletonList(TestUtils.makeBasicSpan());
|
Collections.singletonList(TestUtils.makeBasicSpan());
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void empty() {
|
void empty() {
|
||||||
SpanExporter multiSpanExporter = MultiSpanExporter.create(Collections.emptyList());
|
SpanExporter multiSpanExporter = MultiSpanExporter.create(Collections.emptyList());
|
||||||
|
|
@ -56,15 +53,15 @@ class MultiSpanExporterTest {
|
||||||
SpanExporter multiSpanExporter =
|
SpanExporter multiSpanExporter =
|
||||||
MultiSpanExporter.create(Collections.singletonList(spanExporter1));
|
MultiSpanExporter.create(Collections.singletonList(spanExporter1));
|
||||||
|
|
||||||
Mockito.when(spanExporter1.export(same(SPAN_LIST)))
|
when(spanExporter1.export(same(SPAN_LIST))).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
.thenReturn(CompletableResultCode.ofSuccess());
|
|
||||||
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isTrue();
|
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isTrue();
|
||||||
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
||||||
|
|
||||||
Mockito.when(spanExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
when(spanExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
assertThat(multiSpanExporter.flush().isSuccess()).isTrue();
|
assertThat(multiSpanExporter.flush().isSuccess()).isTrue();
|
||||||
Mockito.verify(spanExporter1).flush();
|
Mockito.verify(spanExporter1).flush();
|
||||||
|
|
||||||
|
when(spanExporter1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
multiSpanExporter.shutdown();
|
multiSpanExporter.shutdown();
|
||||||
Mockito.verify(spanExporter1).shutdown();
|
Mockito.verify(spanExporter1).shutdown();
|
||||||
}
|
}
|
||||||
|
|
@ -74,20 +71,20 @@ class MultiSpanExporterTest {
|
||||||
SpanExporter multiSpanExporter =
|
SpanExporter multiSpanExporter =
|
||||||
MultiSpanExporter.create(Arrays.asList(spanExporter1, spanExporter2));
|
MultiSpanExporter.create(Arrays.asList(spanExporter1, spanExporter2));
|
||||||
|
|
||||||
Mockito.when(spanExporter1.export(same(SPAN_LIST)))
|
when(spanExporter1.export(same(SPAN_LIST))).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
.thenReturn(CompletableResultCode.ofSuccess());
|
when(spanExporter2.export(same(SPAN_LIST))).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
Mockito.when(spanExporter2.export(same(SPAN_LIST)))
|
|
||||||
.thenReturn(CompletableResultCode.ofSuccess());
|
|
||||||
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isTrue();
|
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isTrue();
|
||||||
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
||||||
Mockito.verify(spanExporter2).export(same(SPAN_LIST));
|
Mockito.verify(spanExporter2).export(same(SPAN_LIST));
|
||||||
|
|
||||||
Mockito.when(spanExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
when(spanExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
Mockito.when(spanExporter2.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
when(spanExporter2.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
assertThat(multiSpanExporter.flush().isSuccess()).isTrue();
|
assertThat(multiSpanExporter.flush().isSuccess()).isTrue();
|
||||||
Mockito.verify(spanExporter1).flush();
|
Mockito.verify(spanExporter1).flush();
|
||||||
Mockito.verify(spanExporter2).flush();
|
Mockito.verify(spanExporter2).flush();
|
||||||
|
|
||||||
|
when(spanExporter1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
|
when(spanExporter2.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
multiSpanExporter.shutdown();
|
multiSpanExporter.shutdown();
|
||||||
Mockito.verify(spanExporter1).shutdown();
|
Mockito.verify(spanExporter1).shutdown();
|
||||||
Mockito.verify(spanExporter2).shutdown();
|
Mockito.verify(spanExporter2).shutdown();
|
||||||
|
|
@ -98,19 +95,23 @@ class MultiSpanExporterTest {
|
||||||
SpanExporter multiSpanExporter =
|
SpanExporter multiSpanExporter =
|
||||||
MultiSpanExporter.create(Arrays.asList(spanExporter1, spanExporter2));
|
MultiSpanExporter.create(Arrays.asList(spanExporter1, spanExporter2));
|
||||||
|
|
||||||
Mockito.when(spanExporter1.export(same(SPAN_LIST)))
|
when(spanExporter1.export(same(SPAN_LIST))).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
.thenReturn(CompletableResultCode.ofSuccess());
|
when(spanExporter2.export(same(SPAN_LIST))).thenReturn(CompletableResultCode.ofFailure());
|
||||||
Mockito.when(spanExporter2.export(same(SPAN_LIST)))
|
|
||||||
.thenReturn(CompletableResultCode.ofFailure());
|
|
||||||
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isFalse();
|
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isFalse();
|
||||||
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
||||||
Mockito.verify(spanExporter2).export(same(SPAN_LIST));
|
Mockito.verify(spanExporter2).export(same(SPAN_LIST));
|
||||||
|
|
||||||
Mockito.when(spanExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
when(spanExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
Mockito.when(spanExporter2.flush()).thenReturn(CompletableResultCode.ofFailure());
|
when(spanExporter2.flush()).thenReturn(CompletableResultCode.ofFailure());
|
||||||
assertThat(multiSpanExporter.flush().isSuccess()).isFalse();
|
assertThat(multiSpanExporter.flush().isSuccess()).isFalse();
|
||||||
Mockito.verify(spanExporter1).flush();
|
Mockito.verify(spanExporter1).flush();
|
||||||
Mockito.verify(spanExporter2).flush();
|
Mockito.verify(spanExporter2).flush();
|
||||||
|
|
||||||
|
when(spanExporter1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
|
when(spanExporter2.shutdown()).thenReturn(CompletableResultCode.ofFailure());
|
||||||
|
assertThat(multiSpanExporter.shutdown().isSuccess()).isFalse();
|
||||||
|
Mockito.verify(spanExporter1).shutdown();
|
||||||
|
Mockito.verify(spanExporter2).shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -121,16 +122,23 @@ class MultiSpanExporterTest {
|
||||||
Mockito.doThrow(new IllegalArgumentException("No export for you."))
|
Mockito.doThrow(new IllegalArgumentException("No export for you."))
|
||||||
.when(spanExporter1)
|
.when(spanExporter1)
|
||||||
.export(ArgumentMatchers.anyList());
|
.export(ArgumentMatchers.anyList());
|
||||||
Mockito.when(spanExporter2.export(same(SPAN_LIST)))
|
when(spanExporter2.export(same(SPAN_LIST))).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
.thenReturn(CompletableResultCode.ofSuccess());
|
|
||||||
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isFalse();
|
assertThat(multiSpanExporter.export(SPAN_LIST).isSuccess()).isFalse();
|
||||||
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
Mockito.verify(spanExporter1).export(same(SPAN_LIST));
|
||||||
Mockito.verify(spanExporter2).export(same(SPAN_LIST));
|
Mockito.verify(spanExporter2).export(same(SPAN_LIST));
|
||||||
|
|
||||||
Mockito.doThrow(new IllegalArgumentException("No flush for you.")).when(spanExporter1).flush();
|
Mockito.doThrow(new IllegalArgumentException("No flush for you.")).when(spanExporter1).flush();
|
||||||
Mockito.when(spanExporter2.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
when(spanExporter2.flush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
assertThat(multiSpanExporter.flush().isSuccess()).isFalse();
|
assertThat(multiSpanExporter.flush().isSuccess()).isFalse();
|
||||||
Mockito.verify(spanExporter1).flush();
|
Mockito.verify(spanExporter1).flush();
|
||||||
Mockito.verify(spanExporter2).flush();
|
Mockito.verify(spanExporter2).flush();
|
||||||
|
|
||||||
|
Mockito.doThrow(new IllegalArgumentException("No shutdown for you."))
|
||||||
|
.when(spanExporter1)
|
||||||
|
.shutdown();
|
||||||
|
when(spanExporter2.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||||
|
assertThat(multiSpanExporter.shutdown().isSuccess()).isFalse();
|
||||||
|
Mockito.verify(spanExporter1).shutdown();
|
||||||
|
Mockito.verify(spanExporter2).shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import io.opentelemetry.sdk.common.export.CompletableResultCode;
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilderTest.ConfigTester;
|
import io.opentelemetry.sdk.common.export.ConfigBuilderTest.ConfigTester;
|
||||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
|
|
@ -45,10 +45,15 @@ import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
|
import org.mockito.quality.Strictness;
|
||||||
|
|
||||||
/** Unit tests for {@link SimpleSpanProcessor}. */
|
/** Unit tests for {@link SimpleSpanProcessor}. */
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
class SimpleSpanProcessorTest {
|
class SimpleSpanProcessorTest {
|
||||||
private static final long MAX_SCHEDULE_DELAY_MILLIS = 500;
|
private static final long MAX_SCHEDULE_DELAY_MILLIS = 500;
|
||||||
private static final String SPAN_NAME = "MySpanName";
|
private static final String SPAN_NAME = "MySpanName";
|
||||||
|
|
@ -69,7 +74,6 @@ class SimpleSpanProcessorTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
simpleSampledSpansProcessor = SimpleSpanProcessor.newBuilder(spanExporter).build();
|
simpleSampledSpansProcessor = SimpleSpanProcessor.newBuilder(spanExporter).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.opentelemetry.sdk.extensions.trace.export;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.lmax.disruptor.SleepingWaitStrategy;
|
import com.lmax.disruptor.SleepingWaitStrategy;
|
||||||
import com.lmax.disruptor.WaitStrategy;
|
import com.lmax.disruptor.WaitStrategy;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
|
|
@ -91,13 +92,13 @@ public final class DisruptorAsyncSpanProcessor implements SpanProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
disruptorEventQueue.shutdown();
|
return disruptorEventQueue.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {
|
public CompletableResultCode forceFlush() {
|
||||||
disruptorEventQueue.forceFlush();
|
return disruptorEventQueue.forceFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ import com.lmax.disruptor.RingBuffer;
|
||||||
import com.lmax.disruptor.WaitStrategy;
|
import com.lmax.disruptor.WaitStrategy;
|
||||||
import com.lmax.disruptor.dsl.Disruptor;
|
import com.lmax.disruptor.dsl.Disruptor;
|
||||||
import com.lmax.disruptor.dsl.ProducerType;
|
import com.lmax.disruptor.dsl.ProducerType;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.DaemonThreadFactory;
|
import io.opentelemetry.sdk.common.DaemonThreadFactory;
|
||||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
import io.opentelemetry.sdk.trace.SpanProcessor;
|
import io.opentelemetry.sdk.trace.SpanProcessor;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
@ -42,17 +42,19 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||||
final class DisruptorEventQueue {
|
final class DisruptorEventQueue {
|
||||||
private static final Logger logger = Logger.getLogger(DisruptorEventQueue.class.getName());
|
private static final Logger logger = Logger.getLogger(DisruptorEventQueue.class.getName());
|
||||||
private static final String WORKER_THREAD_NAME = "DisruptorEventQueue_WorkerThread";
|
private static final String WORKER_THREAD_NAME = "DisruptorEventQueue_WorkerThread";
|
||||||
private static final EventTranslatorThreeArg<DisruptorEvent, EventType, Object, CountDownLatch>
|
|
||||||
|
private static final EventTranslatorThreeArg<
|
||||||
|
DisruptorEvent, EventType, Object, CompletableResultCode>
|
||||||
TRANSLATOR_THREE_ARG =
|
TRANSLATOR_THREE_ARG =
|
||||||
new EventTranslatorThreeArg<DisruptorEvent, EventType, Object, CountDownLatch>() {
|
new EventTranslatorThreeArg<DisruptorEvent, EventType, Object, CompletableResultCode>() {
|
||||||
@Override
|
@Override
|
||||||
public void translateTo(
|
public void translateTo(
|
||||||
DisruptorEvent event,
|
DisruptorEvent event,
|
||||||
long sequence,
|
long sequence,
|
||||||
EventType eventType,
|
EventType eventType,
|
||||||
Object span,
|
Object span,
|
||||||
CountDownLatch countDownLatch) {
|
CompletableResultCode result) {
|
||||||
event.setEntry(eventType, span, countDownLatch);
|
event.setEntry(eventType, span, result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private static final EventFactory<DisruptorEvent> EVENT_FACTORY =
|
private static final EventFactory<DisruptorEvent> EVENT_FACTORY =
|
||||||
|
|
@ -68,12 +70,6 @@ final class DisruptorEventQueue {
|
||||||
private volatile boolean isShutdown = false;
|
private volatile boolean isShutdown = false;
|
||||||
private final boolean blocking;
|
private final boolean blocking;
|
||||||
|
|
||||||
/**
|
|
||||||
* Only one consumer for {@link DisruptorEventQueue#forceFlush()} and {@link
|
|
||||||
* DisruptorEventQueue#shutdown()} invocation.
|
|
||||||
*/
|
|
||||||
private static final byte NUM_CONSUMERS = 1;
|
|
||||||
|
|
||||||
private enum EventType {
|
private enum EventType {
|
||||||
ON_START,
|
ON_START,
|
||||||
ON_END,
|
ON_END,
|
||||||
|
|
@ -121,47 +117,41 @@ final class DisruptorEventQueue {
|
||||||
|
|
||||||
// Shuts down the underlying disruptor. Ensures that when this method returns the disruptor is
|
// Shuts down the underlying disruptor. Ensures that when this method returns the disruptor is
|
||||||
// shutdown.
|
// shutdown.
|
||||||
void shutdown() {
|
CompletableResultCode shutdown() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (isShutdown) {
|
if (isShutdown) {
|
||||||
// Race condition between two calls to shutdown. The other call already finished.
|
// Race condition between two calls to shutdown. The other call already finished.
|
||||||
return;
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
isShutdown = true;
|
isShutdown = true;
|
||||||
enqueueAndLock(EventType.ON_SHUTDOWN);
|
return enqueueWithResult(EventType.ON_SHUTDOWN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force to publish the ended spans to the SpanProcessor
|
// Force to publish the ended spans to the SpanProcessor
|
||||||
void forceFlush() {
|
CompletableResultCode forceFlush() {
|
||||||
if (isShutdown) {
|
if (isShutdown) {
|
||||||
if (!loggedShutdownMessage.getAndSet(true)) {
|
if (!loggedShutdownMessage.getAndSet(true)) {
|
||||||
logger.info("Attempted to flush after Disruptor shutdown.");
|
logger.info("Attempted to flush after Disruptor shutdown.");
|
||||||
}
|
}
|
||||||
return;
|
return CompletableResultCode.ofFailure();
|
||||||
}
|
}
|
||||||
enqueueAndLock(EventType.ON_FORCE_FLUSH);
|
return enqueueWithResult(EventType.ON_FORCE_FLUSH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enqueueAndLock(EventType event) {
|
private CompletableResultCode enqueueWithResult(EventType event) {
|
||||||
CountDownLatch waitingCounter = new CountDownLatch(NUM_CONSUMERS); // only one processor.
|
CompletableResultCode result = new CompletableResultCode();
|
||||||
enqueue(event, null, waitingCounter);
|
enqueue(event, null, result);
|
||||||
try {
|
return result;
|
||||||
waitingCounter.await();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Preserve the interruption.
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
logger.warning("Thread interrupted, shutdown may not finished.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enqueues an event on the {@link DisruptorEventQueue}.
|
// Enqueues an event on the {@link DisruptorEventQueue}.
|
||||||
private void enqueue(EventType eventType, Object span, CountDownLatch flushLatch) {
|
private void enqueue(EventType eventType, Object span, CompletableResultCode result) {
|
||||||
if (blocking) {
|
if (blocking) {
|
||||||
ringBuffer.publishEvent(TRANSLATOR_THREE_ARG, eventType, span, flushLatch);
|
ringBuffer.publishEvent(TRANSLATOR_THREE_ARG, eventType, span, result);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Record metrics if element not added.
|
// TODO: Record metrics if element not added.
|
||||||
ringBuffer.tryPublishEvent(TRANSLATOR_THREE_ARG, eventType, span, flushLatch);
|
ringBuffer.tryPublishEvent(TRANSLATOR_THREE_ARG, eventType, span, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,13 +159,15 @@ final class DisruptorEventQueue {
|
||||||
private static final class DisruptorEvent {
|
private static final class DisruptorEvent {
|
||||||
@Nullable private Object span = null;
|
@Nullable private Object span = null;
|
||||||
@Nullable private EventType eventType = null;
|
@Nullable private EventType eventType = null;
|
||||||
@Nullable private CountDownLatch waitingCounter = null;
|
@Nullable private CompletableResultCode result = null;
|
||||||
|
|
||||||
void setEntry(
|
void setEntry(
|
||||||
@Nullable EventType eventType, @Nullable Object span, @Nullable CountDownLatch flushLatch) {
|
@Nullable EventType eventType,
|
||||||
|
@Nullable Object span,
|
||||||
|
@Nullable CompletableResultCode result) {
|
||||||
this.span = span;
|
this.span = span;
|
||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
this.waitingCounter = flushLatch;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -188,9 +180,15 @@ final class DisruptorEventQueue {
|
||||||
return eventType;
|
return eventType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void countDownWaitingCounter() {
|
void succeed() {
|
||||||
if (waitingCounter != null) {
|
if (result != null) {
|
||||||
waitingCounter.countDown();
|
result.succeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail() {
|
||||||
|
if (result != null) {
|
||||||
|
result.fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +201,7 @@ final class DisruptorEventQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(DisruptorEvent event, long sequence, boolean endOfBatch) {
|
public void onEvent(final DisruptorEvent event, long sequence, boolean endOfBatch) {
|
||||||
final Object readableSpan = event.getSpan();
|
final Object readableSpan = event.getSpan();
|
||||||
final EventType eventType = event.getEventType();
|
final EventType eventType = event.getEventType();
|
||||||
if (eventType == null) {
|
if (eventType == null) {
|
||||||
|
|
@ -219,12 +217,11 @@ final class DisruptorEventQueue {
|
||||||
spanProcessor.onEnd((ReadableSpan) readableSpan);
|
spanProcessor.onEnd((ReadableSpan) readableSpan);
|
||||||
break;
|
break;
|
||||||
case ON_SHUTDOWN:
|
case ON_SHUTDOWN:
|
||||||
spanProcessor.shutdown();
|
propagateResult(spanProcessor.shutdown(), event);
|
||||||
event.countDownWaitingCounter();
|
|
||||||
break;
|
break;
|
||||||
case ON_FORCE_FLUSH:
|
case ON_FORCE_FLUSH:
|
||||||
spanProcessor.forceFlush();
|
propagateResult(spanProcessor.forceFlush(), event);
|
||||||
event.countDownWaitingCounter();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -233,4 +230,19 @@ final class DisruptorEventQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void propagateResult(
|
||||||
|
final CompletableResultCode result, final DisruptorEvent event) {
|
||||||
|
result.whenComplete(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
event.succeed();
|
||||||
|
} else {
|
||||||
|
event.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.opentelemetry.sdk.extensions.trace.export;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.trace.MultiSpanProcessor;
|
import io.opentelemetry.sdk.trace.MultiSpanProcessor;
|
||||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
|
|
@ -26,14 +27,16 @@ import io.opentelemetry.sdk.trace.SpanProcessor;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
/** Unit tests for {@link DisruptorAsyncSpanProcessor}. */
|
/** Unit tests for {@link DisruptorAsyncSpanProcessor}. */
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
class DisruptorAsyncSpanProcessorTest {
|
class DisruptorAsyncSpanProcessorTest {
|
||||||
private static final boolean REQUIRED = true;
|
private static final boolean REQUIRED = true;
|
||||||
private static final boolean NOT_REQUIRED = false;
|
private static final boolean NOT_REQUIRED = false;
|
||||||
|
|
@ -41,11 +44,6 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
@Mock private ReadableSpan readableSpan;
|
@Mock private ReadableSpan readableSpan;
|
||||||
@Mock private ReadWriteSpan readWriteSpan;
|
@Mock private ReadWriteSpan readWriteSpan;
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventQueueEntry for incrementing a Counter.
|
// EventQueueEntry for incrementing a Counter.
|
||||||
private static class IncrementSpanProcessor implements SpanProcessor {
|
private static class IncrementSpanProcessor implements SpanProcessor {
|
||||||
private final AtomicInteger counterOnStart = new AtomicInteger(0);
|
private final AtomicInteger counterOnStart = new AtomicInteger(0);
|
||||||
|
|
@ -85,15 +83,17 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
counterOnShutdown.incrementAndGet();
|
counterOnShutdown.incrementAndGet();
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {
|
public CompletableResultCode forceFlush() {
|
||||||
counterOnForceFlush.incrementAndGet();
|
counterOnForceFlush.incrementAndGet();
|
||||||
deltaExportedForceFlushSpans.set(counterEndSpans.getAndSet(0));
|
deltaExportedForceFlushSpans.set(counterEndSpans.getAndSet(0));
|
||||||
counterOnExportedForceFlushSpans.addAndGet(deltaExportedForceFlushSpans.get());
|
counterOnExportedForceFlushSpans.addAndGet(deltaExportedForceFlushSpans.get());
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getCounterOnStart() {
|
private int getCounterOnStart() {
|
||||||
|
|
@ -132,8 +132,8 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
||||||
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
||||||
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
||||||
disruptorAsyncSpanProcessor.forceFlush();
|
disruptorAsyncSpanProcessor.forceFlush().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(1);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(1);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(1);
|
||||||
|
|
@ -152,8 +152,8 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
||||||
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
||||||
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
||||||
disruptorAsyncSpanProcessor.forceFlush();
|
disruptorAsyncSpanProcessor.forceFlush().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(0);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(1);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(1);
|
||||||
|
|
@ -172,8 +172,8 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
||||||
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
||||||
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
||||||
disruptorAsyncSpanProcessor.forceFlush();
|
disruptorAsyncSpanProcessor.forceFlush().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(1);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(1);
|
||||||
|
|
@ -185,11 +185,11 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
IncrementSpanProcessor incrementSpanProcessor = new IncrementSpanProcessor(REQUIRED, REQUIRED);
|
IncrementSpanProcessor incrementSpanProcessor = new IncrementSpanProcessor(REQUIRED, REQUIRED);
|
||||||
DisruptorAsyncSpanProcessor disruptorAsyncSpanProcessor =
|
DisruptorAsyncSpanProcessor disruptorAsyncSpanProcessor =
|
||||||
DisruptorAsyncSpanProcessor.newBuilder(incrementSpanProcessor).build();
|
DisruptorAsyncSpanProcessor.newBuilder(incrementSpanProcessor).build();
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnShutdown()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnShutdown()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,14 +198,14 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
IncrementSpanProcessor incrementSpanProcessor = new IncrementSpanProcessor(REQUIRED, REQUIRED);
|
IncrementSpanProcessor incrementSpanProcessor = new IncrementSpanProcessor(REQUIRED, REQUIRED);
|
||||||
DisruptorAsyncSpanProcessor disruptorAsyncSpanProcessor =
|
DisruptorAsyncSpanProcessor disruptorAsyncSpanProcessor =
|
||||||
DisruptorAsyncSpanProcessor.newBuilder(incrementSpanProcessor).build();
|
DisruptorAsyncSpanProcessor.newBuilder(incrementSpanProcessor).build();
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
||||||
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
||||||
disruptorAsyncSpanProcessor.forceFlush();
|
disruptorAsyncSpanProcessor.forceFlush().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(0);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(0);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(0);
|
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(0);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnShutdown()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnShutdown()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,13 +219,13 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
||||||
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
||||||
if (i % 10 == 0) {
|
if (i % 10 == 0) {
|
||||||
disruptorAsyncSpanProcessor.forceFlush();
|
disruptorAsyncSpanProcessor.forceFlush().join(10, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(tenK);
|
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(tenK);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(tenK);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(tenK);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(tenK / 10);
|
assertThat(incrementSpanProcessor.getCounterOnForceFlush()).isEqualTo(tenK / 10);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnShutdown()).isEqualTo(1);
|
assertThat(incrementSpanProcessor.getCounterOnShutdown()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,7 +240,7 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
.build();
|
.build();
|
||||||
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
||||||
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor1.getCounterOnStart()).isEqualTo(1);
|
assertThat(incrementSpanProcessor1.getCounterOnStart()).isEqualTo(1);
|
||||||
assertThat(incrementSpanProcessor1.getCounterOnEnd()).isEqualTo(1);
|
assertThat(incrementSpanProcessor1.getCounterOnEnd()).isEqualTo(1);
|
||||||
assertThat(incrementSpanProcessor1.getCounterOnShutdown()).isEqualTo(1);
|
assertThat(incrementSpanProcessor1.getCounterOnShutdown()).isEqualTo(1);
|
||||||
|
|
@ -261,11 +261,11 @@ class DisruptorAsyncSpanProcessorTest {
|
||||||
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
disruptorAsyncSpanProcessor.onStart(readWriteSpan);
|
||||||
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
disruptorAsyncSpanProcessor.onEnd(readableSpan);
|
||||||
if (i % 100 == 0) {
|
if (i % 100 == 0) {
|
||||||
disruptorAsyncSpanProcessor.forceFlush();
|
disruptorAsyncSpanProcessor.forceFlush().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getDeltaExportedForceFlushSpans()).isEqualTo(100);
|
assertThat(incrementSpanProcessor.getDeltaExportedForceFlushSpans()).isEqualTo(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
disruptorAsyncSpanProcessor.shutdown();
|
disruptorAsyncSpanProcessor.shutdown().join(10, TimeUnit.SECONDS);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(tenK);
|
assertThat(incrementSpanProcessor.getCounterOnStart()).isEqualTo(tenK);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(tenK);
|
assertThat(incrementSpanProcessor.getCounterOnEnd()).isEqualTo(tenK);
|
||||||
assertThat(incrementSpanProcessor.getCounterOnExportedForceFlushSpans()).isEqualTo(tenK);
|
assertThat(incrementSpanProcessor.getCounterOnExportedForceFlushSpans()).isEqualTo(tenK);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.opentelemetry.sdk.extensions.zpages;
|
package io.opentelemetry.sdk.extensions.zpages;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
import io.opentelemetry.sdk.common.export.ConfigBuilder;
|
||||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
|
|
@ -89,13 +90,15 @@ final class TracezSpanProcessor implements SpanProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public CompletableResultCode shutdown() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceFlush() {
|
public CompletableResultCode forceFlush() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue