-
Notifications
You must be signed in to change notification settings - Fork 19
Trace thread state is removed when root spans are completed #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
38b237a
10b89ba
de812a3
1cfd152
102b5df
1bbfefa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -46,11 +46,7 @@ public final class Tracer { | |||||||||||||||
| private Tracer() {} | ||||||||||||||||
|
|
||||||||||||||||
| // Thread-safe since thread-local | ||||||||||||||||
| private static final ThreadLocal<Trace> currentTrace = ThreadLocal.withInitial(() -> { | ||||||||||||||||
| Trace trace = createTrace(Optional.empty(), Tracers.randomId()); | ||||||||||||||||
| MDC.put(Tracers.TRACE_ID_KEY, trace.getTraceId()); | ||||||||||||||||
| return trace; | ||||||||||||||||
| }); | ||||||||||||||||
| private static final ThreadLocal<Trace> currentTrace = new ThreadLocal<>(); | ||||||||||||||||
|
|
||||||||||||||||
| // Only access in a class-synchronized fashion | ||||||||||||||||
| private static final Map<String, SpanObserver> observers = new HashMap<>(); | ||||||||||||||||
|
|
@@ -85,7 +81,8 @@ public static void initTrace(Optional<Boolean> isObservable, String traceId) { | |||||||||||||||
| * when the current trace is empty. | ||||||||||||||||
| */ | ||||||||||||||||
| public static OpenSpan startSpan(String operation, String parentSpanId, SpanType type) { | ||||||||||||||||
| Preconditions.checkState(currentTrace.get().isEmpty(), | ||||||||||||||||
| Trace current = getOrCreateCurrentTrace(); | ||||||||||||||||
| Preconditions.checkState(current.isEmpty(), | ||||||||||||||||
| "Cannot start a span with explicit parent if the current thread's trace is non-empty"); | ||||||||||||||||
| Preconditions.checkArgument(parentSpanId != null && !parentSpanId.isEmpty(), | ||||||||||||||||
| "parentTraceId must be non-empty: %s", parentSpanId); | ||||||||||||||||
|
|
@@ -95,7 +92,7 @@ public static OpenSpan startSpan(String operation, String parentSpanId, SpanType | |||||||||||||||
| .parentSpanId(parentSpanId) | ||||||||||||||||
| .type(type) | ||||||||||||||||
| .build(); | ||||||||||||||||
| currentTrace.get().push(span); | ||||||||||||||||
| current.push(span); | ||||||||||||||||
| return span; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -119,13 +116,15 @@ private static OpenSpan startSpanInternal(String operation, SpanType type) { | |||||||||||||||
| .spanId(Tracers.randomId()) | ||||||||||||||||
| .type(type); | ||||||||||||||||
|
|
||||||||||||||||
| Optional<OpenSpan> prevState = currentTrace.get().top(); | ||||||||||||||||
| Trace trace = getOrCreateCurrentTrace(); | ||||||||||||||||
| Optional<OpenSpan> prevState = trace.top(); | ||||||||||||||||
| // Avoid lambda allocation in hot paths | ||||||||||||||||
| if (prevState.isPresent()) { | ||||||||||||||||
| spanBuilder.parentSpanId(prevState.get().getSpanId()); | ||||||||||||||||
| } | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: could simplify
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I opted to avoid the lambda allocation since this is a relatively hot path. We do a lot elsewhere, so I could go either way.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense |
||||||||||||||||
|
|
||||||||||||||||
| OpenSpan span = spanBuilder.build(); | ||||||||||||||||
| currentTrace.get().push(span); | ||||||||||||||||
| trace.push(span); | ||||||||||||||||
| return span; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -143,10 +142,14 @@ public static void fastCompleteSpan() { | |||||||||||||||
| * Like {@link #fastCompleteSpan()}, but adds {@code metadata} to the current span being completed. | ||||||||||||||||
| */ | ||||||||||||||||
| public static void fastCompleteSpan(Map<String, String> metadata) { | ||||||||||||||||
| popCurrentSpan() | ||||||||||||||||
| .filter(openSpan -> currentTrace.get().isObservable()) | ||||||||||||||||
| .map(openSpan -> toSpan(openSpan, metadata)) | ||||||||||||||||
| .ifPresent(Tracer::notifyObservers); | ||||||||||||||||
| Trace trace = currentTrace.get(); | ||||||||||||||||
| if (trace != null) { | ||||||||||||||||
| Optional<OpenSpan> span = popCurrentSpan(); | ||||||||||||||||
| if (trace.isObservable()) { | ||||||||||||||||
| span.map(openSpan -> toSpan(openSpan, metadata, trace.getTraceId())) | ||||||||||||||||
| .ifPresent(Tracer::notifyObservers); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
|
|
@@ -161,15 +164,18 @@ public static Optional<Span> completeSpan() { | |||||||||||||||
| * Like {@link #completeSpan()}, but adds {@code metadata} to the current span being completed. | ||||||||||||||||
| */ | ||||||||||||||||
| public static Optional<Span> completeSpan(Map<String, String> metadata) { | ||||||||||||||||
| Trace trace = currentTrace.get(); | ||||||||||||||||
| if (trace == null) { | ||||||||||||||||
| return Optional.empty(); | ||||||||||||||||
| } | ||||||||||||||||
| Optional<Span> maybeSpan = popCurrentSpan() | ||||||||||||||||
| .map(openSpan -> toSpan(openSpan, metadata)); | ||||||||||||||||
| .map(openSpan -> toSpan(openSpan, metadata, trace.getTraceId())); | ||||||||||||||||
|
|
||||||||||||||||
| // Notify subscribers iff trace is observable | ||||||||||||||||
| maybeSpan.ifPresent(span -> { | ||||||||||||||||
| if (currentTrace.get().isObservable()) { | ||||||||||||||||
| notifyObservers(span); | ||||||||||||||||
| } | ||||||||||||||||
| }); | ||||||||||||||||
| if (maybeSpan.isPresent() && trace.isObservable()) { | ||||||||||||||||
| // Avoid lambda allocation in hot paths | ||||||||||||||||
| notifyObservers(maybeSpan.get()); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return maybeSpan; | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -181,12 +187,20 @@ private static void notifyObservers(Span span) { | |||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private static Optional<OpenSpan> popCurrentSpan() { | ||||||||||||||||
| return currentTrace.get().pop(); | ||||||||||||||||
| Trace trace = currentTrace.get(); | ||||||||||||||||
| if (trace != null) { | ||||||||||||||||
| Optional<OpenSpan> span = trace.pop(); | ||||||||||||||||
| if (trace.isEmpty()) { | ||||||||||||||||
| clearCurrentTrace(); | ||||||||||||||||
| } | ||||||||||||||||
| return span; | ||||||||||||||||
| } | ||||||||||||||||
| return Optional.empty(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private static Span toSpan(OpenSpan openSpan, Map<String, String> metadata) { | ||||||||||||||||
| private static Span toSpan(OpenSpan openSpan, Map<String, String> metadata, String traceId) { | ||||||||||||||||
| return Span.builder() | ||||||||||||||||
| .traceId(getTraceId()) | ||||||||||||||||
| .traceId(traceId) | ||||||||||||||||
| .spanId(openSpan.getSpanId()) | ||||||||||||||||
| .type(openSpan.type()) | ||||||||||||||||
| .parentSpanId(openSpan.getParentSpanId()) | ||||||||||||||||
|
|
@@ -237,16 +251,20 @@ public static void setSampler(TraceSampler sampler) { | |||||||||||||||
| Tracer.sampler = sampler; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** Returns true if there is an active trace on this thread. */ | ||||||||||||||||
| public static boolean hasTraceId() { | ||||||||||||||||
| return currentTrace.get() != null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** Returns the globally unique identifier for this thread's trace. */ | ||||||||||||||||
| public static String getTraceId() { | ||||||||||||||||
| return currentTrace.get().getTraceId(); | ||||||||||||||||
| return Preconditions.checkNotNull(currentTrace.get(), "There is no root span").getTraceId(); | ||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered returning a newly generated ID when no trace is available, but that could potentially result in bad data if we initialize a new span assuming that traceId will be maintained.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly want to use the safelogging Preconditions here so these messages will definitely not get redacted?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that would be ideal, but we don't currently depend on safe-logging. I'd prefer not to add deps in this change.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we expose a Once this PR goes through we can obviously clean up https://github.com/palantir/tritium/blob/develop/tritium-tracing/src/main/java/com/palantir/tritium/tracing/TracingInvocationEventHandler.java#L71 as
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 to a @Override
public HttpConnection create(URL url) throws IOException {
HttpConnection connection = delegate.create(url);
String traceId = Tracer.getTraceId();
connection.setRequestProperty(TraceHttpHeaders.TRACE_ID, traceId);
return connection;
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. I searched for usages of getTraceId, and everything that I found called it after creating a new span, so it should be safe, though there may be cases that I'm not aware of. For what it's worth, anything that does throw here is also responsible for leaking traceIds. |
||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** Clears the current trace id and returns (a copy of) it. */ | ||||||||||||||||
| public static Trace getAndClearTrace() { | ||||||||||||||||
| Trace trace = currentTrace.get(); | ||||||||||||||||
| currentTrace.remove(); | ||||||||||||||||
| MDC.remove(Tracers.TRACE_ID_KEY); | ||||||||||||||||
| Trace trace = getOrCreateCurrentTrace(); | ||||||||||||||||
| clearCurrentTrace(); | ||||||||||||||||
| return trace; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -255,12 +273,17 @@ public static Trace getAndClearTrace() { | |||||||||||||||
| * Tracer#completeSpan span completion}. | ||||||||||||||||
| */ | ||||||||||||||||
| public static boolean isTraceObservable() { | ||||||||||||||||
| return currentTrace.get().isObservable(); | ||||||||||||||||
| Trace trace = currentTrace.get(); | ||||||||||||||||
| return trace != null && trace.isObservable(); | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. related to above comments, could add
Suggested change
|
||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** Returns an independent copy of this thread's {@link Trace}. */ | ||||||||||||||||
| static Trace copyTrace() { | ||||||||||||||||
| return currentTrace.get().deepCopy(); | ||||||||||||||||
| static Optional<Trace> copyTrace() { | ||||||||||||||||
| Trace trace = currentTrace.get(); | ||||||||||||||||
| if (trace != null) { | ||||||||||||||||
| return Optional.of(trace.deepCopy()); | ||||||||||||||||
| } | ||||||||||||||||
| return Optional.empty(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
|
|
@@ -274,4 +297,17 @@ static void setTrace(Trace trace) { | |||||||||||||||
| MDC.put(Tracers.TRACE_ID_KEY, trace.getTraceId()); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private static Trace getOrCreateCurrentTrace() { | ||||||||||||||||
| Trace trace = currentTrace.get(); | ||||||||||||||||
| if (trace == null) { | ||||||||||||||||
| trace = createTrace(Optional.empty(), Tracers.randomId()); | ||||||||||||||||
| setTrace(trace); | ||||||||||||||||
| } | ||||||||||||||||
| return trace; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private static void clearCurrentTrace() { | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the only two places this can be called are:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is correct |
||||||||||||||||
| currentTrace.remove(); | ||||||||||||||||
| MDC.remove(Tracers.TRACE_ID_KEY); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK so I guess this is the key change - this guy now uses
nullto represent the idea that we're not in a trace?I guess we just have to be super careful now everywhere that we use currentTrace.get() that it might actually be null!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is correct, otherwise trace thread state leaks outside of root spans, which causes multiple root spans unless code directly creates a new traceId.