23
23
import io .opentelemetry .sdk .trace .data .LinkData ;
24
24
import io .opentelemetry .sdk .trace .data .SpanData ;
25
25
import io .opentelemetry .sdk .trace .data .StatusData ;
26
+ import io .opentelemetry .sdk .trace .internal .ExtendedSpanProcessor ;
26
27
import io .opentelemetry .sdk .trace .internal .data .ExceptionEventData ;
27
28
import java .util .ArrayList ;
28
29
import java .util .Collections ;
@@ -95,9 +96,24 @@ final class SdkSpan implements ReadWriteSpan {
95
96
@ GuardedBy ("lock" )
96
97
private long endEpochNanos ;
97
98
98
- // True if the span is ended.
99
+ private enum EndState {
100
+ NOT_ENDED ,
101
+ ENDING ,
102
+ ENDED
103
+ }
104
+
99
105
@ GuardedBy ("lock" )
100
- private boolean hasEnded ;
106
+ private EndState hasEnded ;
107
+
108
+ /**
109
+ * The thread on which {@link #end()} is called and which will be invoking the {@link
110
+ * SpanProcessor}s. This field is used to ensure that only this thread may modify the span while
111
+ * it is in state {@link EndState#ENDING} to prevent concurrent updates outside of {@link
112
+ * ExtendedSpanProcessor#onEnding(ReadWriteSpan)}.
113
+ */
114
+ @ GuardedBy ("lock" )
115
+ @ Nullable
116
+ private Thread spanEndingThread ;
101
117
102
118
private SdkSpan (
103
119
SpanContext context ,
@@ -122,7 +138,7 @@ private SdkSpan(
122
138
this .kind = kind ;
123
139
this .spanProcessor = spanProcessor ;
124
140
this .resource = resource ;
125
- this .hasEnded = false ;
141
+ this .hasEnded = EndState . NOT_ENDED ;
126
142
this .clock = clock ;
127
143
this .startEpochNanos = startEpochNanos ;
128
144
this .attributes = attributes ;
@@ -220,7 +236,7 @@ public SpanData toSpanData() {
220
236
status ,
221
237
name ,
222
238
endEpochNanos ,
223
- hasEnded );
239
+ hasEnded == EndState . ENDED );
224
240
}
225
241
}
226
242
@@ -242,7 +258,7 @@ public Attributes getAttributes() {
242
258
@ Override
243
259
public boolean hasEnded () {
244
260
synchronized (lock ) {
245
- return hasEnded ;
261
+ return hasEnded == EndState . ENDED ;
246
262
}
247
263
}
248
264
@@ -288,7 +304,7 @@ public InstrumentationScopeInfo getInstrumentationScopeInfo() {
288
304
@ Override
289
305
public long getLatencyNanos () {
290
306
synchronized (lock ) {
291
- return (hasEnded ? endEpochNanos : clock .now ()) - startEpochNanos ;
307
+ return (hasEnded == EndState . NOT_ENDED ? clock .now () : endEpochNanos ) - startEpochNanos ;
292
308
}
293
309
}
294
310
@@ -303,7 +319,7 @@ public <T> ReadWriteSpan setAttribute(AttributeKey<T> key, T value) {
303
319
return this ;
304
320
}
305
321
synchronized (lock ) {
306
- if (hasEnded ) {
322
+ if (! isModifiableByCurrentThread () ) {
307
323
logger .log (Level .FINE , "Calling setAttribute() on an ended Span." );
308
324
return this ;
309
325
}
@@ -318,6 +334,12 @@ public <T> ReadWriteSpan setAttribute(AttributeKey<T> key, T value) {
318
334
return this ;
319
335
}
320
336
337
+ @ GuardedBy ("lock" )
338
+ private boolean isModifiableByCurrentThread () {
339
+ return hasEnded == EndState .NOT_ENDED
340
+ || (hasEnded == EndState .ENDING && Thread .currentThread () == spanEndingThread );
341
+ }
342
+
321
343
@ Override
322
344
public ReadWriteSpan addEvent (String name ) {
323
345
if (name == null ) {
@@ -380,7 +402,7 @@ public ReadWriteSpan addEvent(String name, Attributes attributes, long timestamp
380
402
381
403
private void addTimedEvent (EventData timedEvent ) {
382
404
synchronized (lock ) {
383
- if (hasEnded ) {
405
+ if (! isModifiableByCurrentThread () ) {
384
406
logger .log (Level .FINE , "Calling addEvent() on an ended Span." );
385
407
return ;
386
408
}
@@ -400,7 +422,7 @@ public ReadWriteSpan setStatus(StatusCode statusCode, @Nullable String descripti
400
422
return this ;
401
423
}
402
424
synchronized (lock ) {
403
- if (hasEnded ) {
425
+ if (! isModifiableByCurrentThread () ) {
404
426
logger .log (Level .FINE , "Calling setStatus() on an ended Span." );
405
427
return this ;
406
428
} else if (this .status .getStatusCode () == StatusCode .OK ) {
@@ -438,7 +460,7 @@ public ReadWriteSpan updateName(String name) {
438
460
return this ;
439
461
}
440
462
synchronized (lock ) {
441
- if (hasEnded ) {
463
+ if (! isModifiableByCurrentThread () ) {
442
464
logger .log (Level .FINE , "Calling updateName() on an ended Span." );
443
465
return this ;
444
466
}
@@ -463,7 +485,7 @@ public Span addLink(SpanContext spanContext, Attributes attributes) {
463
485
spanLimits .getMaxNumberOfAttributesPerLink (),
464
486
spanLimits .getMaxAttributeValueLength ()));
465
487
synchronized (lock ) {
466
- if (hasEnded ) {
488
+ if (! isModifiableByCurrentThread () ) {
467
489
logger .log (Level .FINE , "Calling addLink() on an ended Span." );
468
490
return this ;
469
491
}
@@ -493,12 +515,22 @@ public void end(long timestamp, TimeUnit unit) {
493
515
494
516
private void endInternal (long endEpochNanos ) {
495
517
synchronized (lock ) {
496
- if (hasEnded ) {
497
- logger .log (Level .FINE , "Calling end() on an ended Span." );
518
+ if (hasEnded != EndState . NOT_ENDED ) {
519
+ logger .log (Level .FINE , "Calling end() on an ended or ending Span." );
498
520
return ;
499
521
}
500
522
this .endEpochNanos = endEpochNanos ;
501
- hasEnded = true ;
523
+ spanEndingThread = Thread .currentThread ();
524
+ hasEnded = EndState .ENDING ;
525
+ }
526
+ if (spanProcessor instanceof ExtendedSpanProcessor ) {
527
+ ExtendedSpanProcessor extendedSpanProcessor = (ExtendedSpanProcessor ) spanProcessor ;
528
+ if (extendedSpanProcessor .isOnEndingRequired ()) {
529
+ extendedSpanProcessor .onEnding (this );
530
+ }
531
+ }
532
+ synchronized (lock ) {
533
+ hasEnded = EndState .ENDED ;
502
534
}
503
535
if (spanProcessor .isEndRequired ()) {
504
536
spanProcessor .onEnd (this );
@@ -508,7 +540,7 @@ private void endInternal(long endEpochNanos) {
508
540
@ Override
509
541
public boolean isRecording () {
510
542
synchronized (lock ) {
511
- return ! hasEnded ;
543
+ return hasEnded != EndState . ENDED ;
512
544
}
513
545
}
514
546
@@ -533,7 +565,7 @@ private List<EventData> getImmutableTimedEvents() {
533
565
534
566
// if the span has ended, then the events are unmodifiable
535
567
// so we can return them directly and save copying all the data.
536
- if (hasEnded ) {
568
+ if (hasEnded == EndState . ENDED ) {
537
569
return Collections .unmodifiableList (events );
538
570
}
539
571
@@ -547,7 +579,7 @@ private Attributes getImmutableAttributes() {
547
579
}
548
580
// if the span has ended, then the attributes are unmodifiable,
549
581
// so we can return them directly and save copying all the data.
550
- if (hasEnded ) {
582
+ if (hasEnded == EndState . ENDED ) {
551
583
return attributes ;
552
584
}
553
585
// otherwise, make a copy of the data into an immutable container.
0 commit comments