mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-04 09:43:23 +02:00
23a33739e9
* Fix flaky test TestSimpleSpanProcessorShutdownHonorsContextCancel * Add changes to changelog * Prioritize return of exporter error * Update changelog description * Fix deadlock bug
118 lines
4.0 KiB
Go
118 lines
4.0 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed 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 trace // import "go.opentelemetry.io/otel/sdk/trace"
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
)
|
|
|
|
// simpleSpanProcessor is a SpanProcessor that synchronously sends all
|
|
// completed Spans to a trace.Exporter immediately.
|
|
type simpleSpanProcessor struct {
|
|
exporterMu sync.RWMutex
|
|
exporter SpanExporter
|
|
stopOnce sync.Once
|
|
}
|
|
|
|
var _ SpanProcessor = (*simpleSpanProcessor)(nil)
|
|
|
|
// NewSimpleSpanProcessor returns a new SpanProcessor that will synchronously
|
|
// send completed spans to the exporter immediately.
|
|
//
|
|
// This SpanProcessor is not recommended for production use. The synchronous
|
|
// nature of this SpanProcessor make it good for testing, debugging, or
|
|
// showing examples of other feature, but it will be slow and have a high
|
|
// computation resource usage overhead. The BatchSpanProcessor is recommended
|
|
// for production use instead.
|
|
func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor {
|
|
ssp := &simpleSpanProcessor{
|
|
exporter: exporter,
|
|
}
|
|
return ssp
|
|
}
|
|
|
|
// OnStart does nothing.
|
|
func (ssp *simpleSpanProcessor) OnStart(context.Context, ReadWriteSpan) {}
|
|
|
|
// OnEnd immediately exports a ReadOnlySpan.
|
|
func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) {
|
|
ssp.exporterMu.RLock()
|
|
defer ssp.exporterMu.RUnlock()
|
|
|
|
if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() {
|
|
if err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}); err != nil {
|
|
otel.Handle(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shutdown shuts down the exporter this SimpleSpanProcessor exports to.
|
|
func (ssp *simpleSpanProcessor) Shutdown(ctx context.Context) error {
|
|
var err error
|
|
ssp.stopOnce.Do(func() {
|
|
stopFunc := func(exp SpanExporter) (<-chan error, func()) {
|
|
done := make(chan error)
|
|
return done, func() { done <- exp.Shutdown(ctx) }
|
|
}
|
|
|
|
// The exporter field of the simpleSpanProcessor needs to be zeroed to
|
|
// signal it is shut down, meaning all subsequent calls to OnEnd will
|
|
// be gracefully ignored. This needs to be done synchronously to avoid
|
|
// any race condition.
|
|
//
|
|
// A closure is used to keep reference to the exporter and then the
|
|
// field is zeroed. This ensures the simpleSpanProcessor is shut down
|
|
// before the exporter. This order is important as it avoids a
|
|
// potential deadlock. If the exporter shut down operation generates a
|
|
// span, that span would need to be exported. Meaning, OnEnd would be
|
|
// called and try acquiring the lock that is held here.
|
|
ssp.exporterMu.Lock()
|
|
done, shutdown := stopFunc(ssp.exporter)
|
|
ssp.exporter = nil
|
|
ssp.exporterMu.Unlock()
|
|
|
|
go shutdown()
|
|
|
|
// Wait for the exporter to shut down or the deadline to expire.
|
|
select {
|
|
case err = <-done:
|
|
case <-ctx.Done():
|
|
// It is possible for the exporter to have immediately shut down
|
|
// and the context to be done simultaneously. In that case this
|
|
// outer select statement will randomly choose a case. This will
|
|
// result in a different returned error for similar scenarios.
|
|
// Instead, double check if the exporter shut down at the same
|
|
// time and return that error if so. This will ensure consistency
|
|
// as well as ensure the caller knows the exporter shut down
|
|
// successfully (they can already determine if the deadline is
|
|
// expired given they passed the context).
|
|
select {
|
|
case err = <-done:
|
|
default:
|
|
err = ctx.Err()
|
|
}
|
|
}
|
|
})
|
|
return err
|
|
}
|
|
|
|
// ForceFlush does nothing as there is no data to flush.
|
|
func (ssp *simpleSpanProcessor) ForceFlush(context.Context) error {
|
|
return nil
|
|
}
|