1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package trace // import "go.opentelemetry.io/otel/sdk/trace"
16
17import (
18	"context"
19	"sync"
20
21	"go.opentelemetry.io/otel"
22)
23
24// simpleSpanProcessor is a SpanProcessor that synchronously sends all
25// completed Spans to a trace.Exporter immediately.
26type simpleSpanProcessor struct {
27	exporterMu sync.RWMutex
28	exporter   SpanExporter
29	stopOnce   sync.Once
30}
31
32var _ SpanProcessor = (*simpleSpanProcessor)(nil)
33
34// NewSimpleSpanProcessor returns a new SpanProcessor that will synchronously
35// send completed spans to the exporter immediately.
36//
37// This SpanProcessor is not recommended for production use. The synchronous
38// nature of this SpanProcessor make it good for testing, debugging, or
39// showing examples of other feature, but it will be slow and have a high
40// computation resource usage overhead. The BatchSpanProcessor is recommended
41// for production use instead.
42func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor {
43	ssp := &simpleSpanProcessor{
44		exporter: exporter,
45	}
46	return ssp
47}
48
49// OnStart does nothing.
50func (ssp *simpleSpanProcessor) OnStart(context.Context, ReadWriteSpan) {}
51
52// OnEnd immediately exports a ReadOnlySpan.
53func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) {
54	ssp.exporterMu.RLock()
55	defer ssp.exporterMu.RUnlock()
56
57	if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() {
58		if err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}); err != nil {
59			otel.Handle(err)
60		}
61	}
62}
63
64// Shutdown shuts down the exporter this SimpleSpanProcessor exports to.
65func (ssp *simpleSpanProcessor) Shutdown(ctx context.Context) error {
66	var err error
67	ssp.stopOnce.Do(func() {
68		stopFunc := func(exp SpanExporter) (<-chan error, func()) {
69			done := make(chan error)
70			return done, func() { done <- exp.Shutdown(ctx) }
71		}
72
73		// The exporter field of the simpleSpanProcessor needs to be zeroed to
74		// signal it is shut down, meaning all subsequent calls to OnEnd will
75		// be gracefully ignored. This needs to be done synchronously to avoid
76		// any race condition.
77		//
78		// A closure is used to keep reference to the exporter and then the
79		// field is zeroed. This ensures the simpleSpanProcessor is shut down
80		// before the exporter. This order is important as it avoids a
81		// potential deadlock. If the exporter shut down operation generates a
82		// span, that span would need to be exported. Meaning, OnEnd would be
83		// called and try acquiring the lock that is held here.
84		ssp.exporterMu.Lock()
85		done, shutdown := stopFunc(ssp.exporter)
86		ssp.exporter = nil
87		ssp.exporterMu.Unlock()
88
89		go shutdown()
90
91		// Wait for the exporter to shut down or the deadline to expire.
92		select {
93		case err = <-done:
94		case <-ctx.Done():
95			// It is possible for the exporter to have immediately shut down
96			// and the context to be done simultaneously. In that case this
97			// outer select statement will randomly choose a case. This will
98			// result in a different returned error for similar scenarios.
99			// Instead, double check if the exporter shut down at the same
100			// time and return that error if so. This will ensure consistency
101			// as well as ensure the caller knows the exporter shut down
102			// successfully (they can already determine if the deadline is
103			// expired given they passed the context).
104			select {
105			case err = <-done:
106			default:
107				err = ctx.Err()
108			}
109		}
110	})
111	return err
112}
113
114// ForceFlush does nothing as there is no data to flush.
115func (ssp *simpleSpanProcessor) ForceFlush(context.Context) error {
116	return nil
117}
118