in span.go [366:472]
func (s *Span) End() {
s.mu.Lock()
defer s.mu.Unlock()
if s.ended() {
return
}
if s.Type == "" {
s.Type = "custom"
}
// Store the span type and subtype on the Span struct so we can filter
// out child spans of exit spans with non-matching type/subtype below.
s.finalType = s.Type
s.finalSubtype = s.Subtype
if s.parent.IsExitSpan() {
// Children of exit spans must not have service destination/target
// context, as otherwise service destination metrics will be double
// counted.
s.Context.model.Destination = nil
s.Context.model.Service = nil
var parentType, parentSubtype string
s.parent.mu.RLock()
if s.parent.ended() {
parentType = s.parent.finalType
parentSubtype = s.parent.finalSubtype
} else {
parentType = s.parent.Type
parentSubtype = s.parent.Subtype
}
s.parent.mu.RUnlock()
if s.Type != parentType || s.Subtype != parentSubtype {
s.dropWhen(true)
if s.tx != nil {
s.tx.mu.Lock()
defer s.tx.mu.Unlock()
if !s.tx.ended() {
s.tx.TransactionData.mu.Lock()
defer s.tx.TransactionData.mu.Unlock()
}
}
s.end()
return
}
}
if s.exit && !s.Context.setDestinationServiceCalled {
// The span was created as an exit span, but the user did not
// manually set the destination.service.resource
s.setExitSpanDestinationService()
}
s.updateSpanServiceTarget()
if s.Duration < 0 {
s.Duration = time.Since(s.timestamp)
}
if s.Outcome == "" {
s.Outcome = s.Context.outcome()
if s.Outcome == "" {
if s.errorCaptured {
s.Outcome = "failure"
} else {
s.Outcome = "success"
}
}
}
switch {
case s.stackStackTraceMinDuration < 0:
// If s.stackFramesMinDuration < 0, we never set stacktrace.
case s.stackStackTraceMinDuration == 0:
// Always set stacktrace
s.setStacktrace(1)
default:
if !s.dropped() && len(s.stacktrace) == 0 &&
s.Duration >= s.stackStackTraceMinDuration {
s.setStacktrace(1)
}
}
// If this span has a parent span, lock it before proceeding to
// prevent deadlocking when concurrently ending parent and child.
if s.parent != nil {
s.parent.mu.Lock()
defer s.parent.mu.Unlock()
}
if s.tx != nil {
s.tx.mu.RLock()
defer s.tx.mu.RUnlock()
if !s.tx.ended() {
s.tx.TransactionData.mu.Lock()
defer s.tx.TransactionData.mu.Unlock()
s.reportSelfTime()
}
}
evictedSpan, cached := s.attemptCompress()
if evictedSpan != nil {
evictedSpan.end()
}
if cached {
// s has been cached for potential compression, and will be enqueued
// by a future call to attemptCompress on a sibling span, or when the
// parent is ended.
return
}
s.end()
}