Skip to content
This repository has been archived by the owner on Jul 1, 2022. It is now read-only.

After finish, disallow setTag. #467

Closed

Conversation

nikron
Copy link

@nikron nikron commented Jun 27, 2018

Which problem is this PR solving?

If you set the tag after the span finishes, this can cause a race condition in the sender as it tries to iterate over the tag map.

To fix that, you really shouldn't even be able to set a tag after the span finishes.

Short description of the changes

Check finish on setTagAsObject and return if the span is already finished.

@codecov
Copy link

codecov bot commented Jun 27, 2018

Codecov Report

Merging #467 into master will decrease coverage by 1.54%.
The diff coverage is 100%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #467      +/-   ##
============================================
- Coverage     89.76%   88.21%   -1.55%     
+ Complexity      563      501      -62     
============================================
  Files            69       63       -6     
  Lines          2061     1867     -194     
  Branches        261      247      -14     
============================================
- Hits           1850     1647     -203     
- Misses          132      142      +10     
+ Partials         79       78       -1
Impacted Files Coverage Δ Complexity Δ
...e/src/main/java/io/jaegertracing/JaegerTracer.java 89.71% <100%> (ø) 23 <0> (?)
...ore/src/main/java/io/jaegertracing/JaegerSpan.java 93.75% <100%> (ø) 54 <8> (?)
...xceptions/MalformedTracerStateStringException.java
...in/java/io/jaegertracing/internal/utils/Utils.java
...nal/exceptions/SamplingStrategyErrorException.java
...io/jaegertracing/internal/PropagationRegistry.java
...ng/internal/exceptions/NotFourOctetsException.java
...io/jaegertracing/internal/baggage/Restriction.java
...rtracing/internal/reporters/CompositeReporter.java
...g/tracerresolver/internal/JaegerTracerFactory.java
... and 121 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3947cbf...5c6d8a5. Read the comment docs.

Copy link
Collaborator

@jpkrohling jpkrohling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution! Before we accept it, however, you'd need to sign-off your commit, as part of the "DCO" (https://github.com/probot/dco#how-it-works).

You can do that by running git commit --amend -s and force-pushing the commit (git push --force). This will get this PR updated.

@@ -206,6 +206,11 @@ public synchronized JaegerSpan setTag(String key, Number value) {
}

private JaegerSpan setTagAsObject(String key, Object value) {
if (finished) {
log.warn("Span has already been finished; cannot set tag.", new RuntimeException("Called from:"));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a sample output for this log message? I'm not sure creating a new exception here (even if not throwing it) is what we want...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is useful otherwise debugging this will be very hard in a large code base.

    09:25:12.802 [Test worker] WARN io.jaegertracing.JaegerSpan - Span has already been finished; cannot set tag.
    java.lang.RuntimeException: Called from:
        at io.jaegertracing.JaegerSpan.setTagAsObject(JaegerSpan.java:210)
        at io.jaegertracing.JaegerSpan.setTag(JaegerSpan.java:195)
        at io.jaegertracing.JaegerSpanTest.testSetStringTagAfterFinish(JaegerSpanTest.java:141)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:106)
        at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
        at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
        at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:66)
        at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
        at sun.reflect.GeneratedMethodAccessor34.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
        at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
        at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
        at com.sun.proxy.$Proxy1.processTestClass(Unknown Source)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
        at sun.reflect.GeneratedMethodAccessor33.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can, of course, remove the exception. For my use case, providing the stack trace will make it much easier to hunt down these errors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception seems useful, but dual-finish doesn't use it currently, so if we want to have exception, I'd say it should be a tracer config switch to enable it.

If we are making this change for tags, it should also apply for all methods that write to the span, i.e. setOperation, log(), etc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree that the exception seems useful, but I'd rather have two log entries then. One at warning level, with a message like "Cannot set tag, as the span is already closed. Set log level to debug for information about the caller". Then, the second would be in debug level, wrapped in if (log.isDebugEnabled()) { logger.debug(...); }, with the stack trace.

My concerns are:

  1. Pollute the logs with stack traces
  2. Confuse users into thinking these stack traces are actual exceptions
  3. Wasted resources in collecting the stack trace for every single time the span is closed, as this information is useful only once per caller.

@nikron nikron force-pushed the dont_set_tag_after_finish branch 2 times, most recently from 70b9c76 to 1c66558 Compare June 27, 2018 14:28
@nikron
Copy link
Author

nikron commented Jun 27, 2018

Before I actually test the logging (because it's sort of annoying to test that...) does this look good?

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks reasonable

@@ -536,6 +540,11 @@ public Builder withExpandExceptionLogs() {
return this;
}

public Builder withLoggingStackTraces() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoggingStackTraces name might be confused with some more serious feature. I would rename to withLogStackTraceOnWarnings.

Also add javadoc explaining the option.

log.warn("Span has already been finished; cannot log.");
}
return this;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to DRY it by moving this to a method spanAlreadyFinished(String funcName) and then replacing the checks with

if (spanAlreadyFinished("setTag")) {
    return this;
}

@nikron nikron force-pushed the dont_set_tag_after_finish branch 2 times, most recently from c683b7e to 5eb07b0 Compare June 27, 2018 23:15
Copy link
Collaborator

@jpkrohling jpkrohling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the concerns I listed regarding adding the stack trace to a warn level, this does LGTM.

@yurishkuro
Copy link
Member

@jpkrohling suggestion is way better & simpler than mine of doing a custom flag.

@nikron
Copy link
Author

nikron commented Jun 28, 2018

Changed it to do two lines when debug is on.

@@ -99,6 +99,10 @@ public JaegerTracer getTracer() {
@Override
public JaegerSpan setOperationName(String operationName) {
synchronized (this) {
if (spanAlreadyFinished("set baggage item")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set operation name

@@ -132,6 +136,10 @@ public JaegerSpan setBaggageItem(String key, String value) {
return this;
}
synchronized (this) {
if (spanAlreadyFinished("set baggage item")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strictly speaking setting baggage would work even after the span is finished

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unlikely that the user would intend to do this, but do you want me to remove it?

@nikron nikron force-pushed the dont_set_tag_after_finish branch from 9640ec7 to c24752e Compare June 29, 2018 05:33
Copy link
Collaborator

@jpkrohling jpkrohling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I have a couple of very minor comments and it's up to you to fix them on this PR or not.

Thank you very much for your contribution!


@Test
public void testLogWithTimestampAfterFinish() {
Map<String, String> unexpectedFields = new HashMap<String, String>() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cleaner to use Collections.singletonMap("foo", "bar") in this case.

@Test
public void testNoLoggingingStackTracesOnDebug() {
log.setEnabledLevels(Level.WARN);
JaegerSpan jaegerSpan = (JaegerSpan)tracer.buildSpan("foo").start();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just got a PR merged yesterday that makes this cast unnecessary. Could you rebase your PR and check it?

@Test
public void testLoggingingStackTracesOnDebug() {
log.setEnabledLevels(Level.WARN, Level.DEBUG);
JaegerSpan jaegerSpan = (JaegerSpan)tracer.buildSpan("foo").start();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the previous comment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There have to be a few more changes for this to be possible (buildSpan has to return a JaegerTracer.SpanBuilder, and SpanBuilder needs to be public.

I'll make these changes, but I don't know if you want to do that in a separate diff

Copy link
Collaborator

@jpkrohling jpkrohling Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do more logging on debug levels.

Signed-off-by: Nikhil Bysani <nikhil.bysani@signifyd.com>
@nikron nikron force-pushed the dont_set_tag_after_finish branch from 53b89dc to 5c6d8a5 Compare June 29, 2018 14:57
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants