diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java index a7c5453b1..1b3a93bee 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AbstractTracerContext.java @@ -16,7 +16,6 @@ * */ - package org.apache.skywalking.apm.agent.core.context; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; @@ -28,41 +27,38 @@ import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; */ public interface AbstractTracerContext { /** - * Prepare for the cross-process propagation. - * How to initialize the carrier, depends on the implementation. + * Prepare for the cross-process propagation. How to initialize the carrier, depends on the implementation. * * @param carrier to carry the context for crossing process. */ void inject(ContextCarrier carrier); /** - * Build the reference between this segment and a cross-process segment. - * How to build, depends on the implementation. + * Build the reference between this segment and a cross-process segment. How to build, depends on the + * implementation. * * @param carrier carried the context from a cross-process segment. */ void extract(ContextCarrier carrier); /** - * Capture a snapshot for cross-thread propagation. - * It's a similar concept with ActiveSpan.Continuation in OpenTracing-java - * How to build, depends on the implementation. + * Capture a snapshot for cross-thread propagation. It's a similar concept with ActiveSpan.Continuation in + * OpenTracing-java How to build, depends on the implementation. * * @return the {@link ContextSnapshot} , which includes the reference context. */ ContextSnapshot capture(); /** - * Build the reference between this segment and a cross-thread segment. - * How to build, depends on the implementation. + * Build the reference between this segment and a cross-thread segment. How to build, depends on the + * implementation. * * @param snapshot from {@link #capture()} in the parent thread. */ void continued(ContextSnapshot snapshot); /** - * Get the global trace id, if needEnhance. - * How to build, depends on the implementation. + * Get the global trace id, if needEnhance. How to build, depends on the implementation. * * @return the string represents the id. */ @@ -105,4 +101,15 @@ public interface AbstractTracerContext { */ void stopSpan(AbstractSpan span); + /** + * Notify this context, current span is going to be finished async in another thread. + */ + AbstractTracerContext awaitFinishAsync(); + + /** + * The given span could be stopped officially. + * + * @param span to be stopped. + */ + void asyncStop(AsyncSpan span); } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AsyncSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AsyncSpan.java new file mode 100644 index 000000000..f667f59c5 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/AsyncSpan.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.agent.core.context; + +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; + +/** + * Span could use these APIs to active and extend its lift cycle across thread. + * + * This is typical used in async plugin, especially RPC plugins. + * + * @author wusheng + */ +public interface AsyncSpan { + /** + * The span finish at current tracing context, but the current span is still alive, until {@link #asyncFinish} + * called. + * + * This method must be called
+ * 1. In original thread(tracing context). + * 2. Current span is active span. + * + * During alive, tags, logs and attributes of the span could be changed, in any thread. + * + * The execution times of {@link #prepareForAsync} and {@link #asyncFinish()} must match. + * + * @return the current span + */ + AbstractSpan prepareForAsync(); + + /** + * Notify the span, it could be finished. + * + * The execution times of {@link #prepareForAsync} and {@link #asyncFinish()} must match. + * + * @return the current span + */ + AbstractSpan asyncFinish(); +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java index aeaa708d2..378602b9b 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/ContextManager.java @@ -18,14 +18,11 @@ package org.apache.skywalking.apm.agent.core.context; -import org.apache.skywalking.apm.agent.core.boot.BootService; -import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.boot.*; import org.apache.skywalking.apm.agent.core.conf.RemoteDownstreamConfig; -import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; -import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.context.trace.*; import org.apache.skywalking.apm.agent.core.dictionary.DictionaryUtil; -import org.apache.skywalking.apm.agent.core.logging.api.ILog; -import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.logging.api.*; import org.apache.skywalking.apm.agent.core.sampling.SamplingService; import org.apache.skywalking.apm.util.StringUtil; @@ -59,7 +56,7 @@ public class ContextManager implements TracingContextListener, BootService, Igno } else { if (RemoteDownstreamConfig.Agent.SERVICE_ID != DictionaryUtil.nullValue() && RemoteDownstreamConfig.Agent.SERVICE_INSTANCE_ID != DictionaryUtil.nullValue() - ) { + ) { context = EXTEND_SERVICE.createTraceContext(operationName, forceSampling); } else { /** @@ -152,6 +149,14 @@ public class ContextManager implements TracingContextListener, BootService, Igno } } + public static AbstractTracerContext awaitFinishAsync(AbstractSpan span) { + AbstractSpan activeSpan = activeSpan(); + if (span != activeSpan) { + throw new RuntimeException("Span is not the active in current context."); + } + return get().awaitFinishAsync(); + } + public static AbstractSpan activeSpan() { return get().activeSpan(); } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java index fd115e566..cef591eec 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/IgnoredTracerContext.java @@ -95,6 +95,14 @@ public class IgnoredTracerContext implements AbstractTracerContext { } } + @Override public AbstractTracerContext awaitFinishAsync() { + return this; + } + + @Override public void asyncStop(AsyncSpan span) { + + } + public static class ListenerManager { private static List LISTENERS = new LinkedList(); diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java index df35adabd..66f90cc94 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/TracingContext.java @@ -18,25 +18,14 @@ package org.apache.skywalking.apm.agent.core.context; -import java.util.LinkedList; -import java.util.List; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; import org.apache.skywalking.apm.agent.core.boot.ServiceManager; import org.apache.skywalking.apm.agent.core.conf.Config; -import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; -import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; -import org.apache.skywalking.apm.agent.core.context.trace.EntrySpan; -import org.apache.skywalking.apm.agent.core.context.trace.ExitSpan; -import org.apache.skywalking.apm.agent.core.context.trace.LocalSpan; -import org.apache.skywalking.apm.agent.core.context.trace.NoopExitSpan; -import org.apache.skywalking.apm.agent.core.context.trace.NoopSpan; -import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; -import org.apache.skywalking.apm.agent.core.context.trace.TraceSegmentRef; -import org.apache.skywalking.apm.agent.core.context.trace.WithPeerInfo; -import org.apache.skywalking.apm.agent.core.dictionary.DictionaryManager; -import org.apache.skywalking.apm.agent.core.dictionary.DictionaryUtil; -import org.apache.skywalking.apm.agent.core.dictionary.PossibleFound; -import org.apache.skywalking.apm.agent.core.logging.api.ILog; -import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.context.trace.*; +import org.apache.skywalking.apm.agent.core.dictionary.*; +import org.apache.skywalking.apm.agent.core.logging.api.*; import org.apache.skywalking.apm.agent.core.sampling.SamplingService; import org.apache.skywalking.apm.util.StringUtil; @@ -80,6 +69,13 @@ public class TracingContext implements AbstractTracerContext { */ private int spanIdGenerator; + /** + * The counter indicates + */ + private AtomicInteger asyncSpanCounter; + private volatile boolean isRunningInAsyncMode; + private ReentrantLock asyncFinishLock; + /** * Initialize all fields with default value. */ @@ -89,6 +85,9 @@ public class TracingContext implements AbstractTracerContext { if (samplingService == null) { samplingService = ServiceManager.INSTANCE.findService(SamplingService.class); } + asyncSpanCounter = new AtomicInteger(0); + isRunningInAsyncMode = false; + asyncFinishLock = new ReentrantLock(); } /** @@ -407,11 +406,41 @@ public class TracingContext implements AbstractTracerContext { throw new IllegalStateException("Stopping the unexpected span = " + span); } - if (activeSpanStack.isEmpty()) { - this.finish(); + if (checkFinishConditions()) { + finish(); } } + @Override public AbstractTracerContext awaitFinishAsync() { + isRunningInAsyncMode = true; + asyncSpanCounter.addAndGet(1); + return this; + } + + @Override public void asyncStop(AsyncSpan span) { + asyncSpanCounter.addAndGet(-1); + + if (checkFinishConditions()) { + finish(); + } + } + + private boolean checkFinishConditions() { + if (isRunningInAsyncMode) { + asyncFinishLock.lock(); + } + try { + if (activeSpanStack.isEmpty() && asyncSpanCounter.get() == 0) { + return true; + } + } finally { + if (isRunningInAsyncMode) { + asyncFinishLock.unlock(); + } + } + return false; + } + /** * Finish this context, and notify all {@link TracingContextListener}s, managed by {@link * TracingContext.ListenerManager} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java index 17e8aefb7..00858967c 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractSpan.java @@ -19,6 +19,7 @@ package org.apache.skywalking.apm.agent.core.context.trace; import java.util.Map; +import org.apache.skywalking.apm.agent.core.context.AsyncSpan; import org.apache.skywalking.apm.agent.core.context.tag.AbstractTag; import org.apache.skywalking.apm.network.trace.component.Component; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; @@ -28,7 +29,7 @@ import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; * * @author wusheng */ -public interface AbstractSpan { +public interface AbstractSpan extends AsyncSpan { /** * Set the component id, which defines in {@link ComponentsDefine} * diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java index 803ce1b48..e05e8458a 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/AbstractTracingSpan.java @@ -18,15 +18,10 @@ package org.apache.skywalking.apm.agent.core.context.trace; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import org.apache.skywalking.apm.agent.core.context.tag.AbstractTag; -import org.apache.skywalking.apm.agent.core.context.tag.StringTag; -import org.apache.skywalking.apm.agent.core.context.util.KeyValuePair; -import org.apache.skywalking.apm.agent.core.context.util.TagValuePair; -import org.apache.skywalking.apm.agent.core.context.util.ThrowableTransformer; +import java.util.*; +import org.apache.skywalking.apm.agent.core.context.*; +import org.apache.skywalking.apm.agent.core.context.tag.*; +import org.apache.skywalking.apm.agent.core.context.util.*; import org.apache.skywalking.apm.agent.core.dictionary.DictionaryUtil; import org.apache.skywalking.apm.network.language.agent.SpanType; import org.apache.skywalking.apm.network.language.agent.v2.SpanObjectV2; @@ -45,6 +40,9 @@ public abstract class AbstractTracingSpan implements AbstractSpan { protected String operationName; protected int operationId; protected SpanLayer layer; + protected boolean isInAsyncMode = false; + protected volatile AbstractTracerContext context; + /** * The start time of this Span. */ @@ -322,4 +320,19 @@ public abstract class AbstractTracingSpan implements AbstractSpan { refs.add(ref); } } + + @Override public AbstractSpan prepareForAsync() { + context = ContextManager.awaitFinishAsync(this); + return this; + } + + @Override public AbstractSpan asyncFinish() { + if (!isInAsyncMode) { + throw new RuntimeException("Span is not in async mode, please use '#prepareForAsync' to active."); + } + + this.endTime = System.currentTimeMillis(); + context.asyncStop(this); + return this; + } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java index 774ebca69..d91a813fb 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/trace/NoopSpan.java @@ -115,4 +115,12 @@ public class NoopSpan implements AbstractSpan { @Override public AbstractSpan setPeer(String remotePeer) { return this; } + + @Override public AbstractSpan prepareForAsync() { + return this; + } + + @Override public AbstractSpan asyncFinish() { + return this; + } } diff --git a/docs/en/guides/Java-Plugin-Development-Guide.md b/docs/en/guides/Java-Plugin-Development-Guide.md index fd6ed452f..992770700 100644 --- a/docs/en/guides/Java-Plugin-Development-Guide.md +++ b/docs/en/guides/Java-Plugin-Development-Guide.md @@ -160,6 +160,42 @@ SpanLayer is the catalog of span. Here are 5 values: Component IDs are defined and reserved by SkyWalking project. For component name/ID extension, please follow [cComponent library definition and extension](Component-library-settings.md) document. +### Advanced APIs +#### Async Span APIs +There is a set of advanced APIs in Span, which work specific for async scenario. When tags, logs, attributes(including end time) of the span +needs to set in another thread, you should use these APIs. + +```java + /** + * The span finish at current tracing context, but the current span is still alive, until {@link #asyncFinish} + * called. + * + * This method must be called
+ * 1. In original thread(tracing context). + * 2. Current span is active span. + * + * During alive, tags, logs and attributes of the span could be changed, in any thread. + * + * The execution times of {@link #prepareForAsync} and {@link #asyncFinish()} must match. + * + * @return the current span + */ + AbstractSpan prepareForAsync(); + + /** + * Notify the span, it could be finished. + * + * The execution times of {@link #prepareForAsync} and {@link #asyncFinish()} must match. + * + * @return the current span + */ + AbstractSpan asyncFinish(); +``` +1. Call `#prepareForAsync` in original context. +1. Propagate the span to any other thread. +1. After all set, call `#asyncFinish` in any thread. +1. Tracing context will be finished and report to backend when all spans's `#prepareForAsync` finished(Judged by count of API execution). + ## Develop a plugin ### Abstract The basic method to trace is intercepting a Java method, by using byte code manipulation tech and AOP concept.