Skip to content

Commit 6d2aeb0

Browse files
pkwarrenMrAliasAneurysm9
authored
Empty queued spans when ForceFlush called (#2335)
* Empty queued spans when ForceFlush called Update the implementation of ForceFlush() to first ensure that all spans which are queued are added to the batch before calling export spans. Create a small ReadOnlySpan implementation which can be used as a marker that ForceFlush has been invoked and used to notify when all spans are ready to be exported. Fixes #2080. * Add a changelog entry. * Update CHANGELOG.md Co-authored-by: Tyler Yahn <[email protected]> * Update sdk/trace/batch_span_processor.go Co-authored-by: Tyler Yahn <[email protected]> * Improve test case to verify multiple flushes. * Refactor code to use enqueue. * Be more defensive on waiting for queue. Update the handling of the force flush span so we only wait on the channel if we were able to enqueue the span to the queue. * Fix linter. Co-authored-by: Tyler Yahn <[email protected]> Co-authored-by: Anthony Mirabella <[email protected]>
1 parent 7ce58f3 commit 6d2aeb0

File tree

3 files changed

+65
-5
lines changed

3 files changed

+65
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1616
- The following interface types simply moved from `metric` to `metric/sdkapi`: `Descriptor`, `MeterImpl`, `InstrumentImpl`, `SyncImpl`, `BoundSyncImpl`, `AsyncImpl`, `AsyncRunner`, `AsyncSingleRunner`, and `AsyncBatchRunner`
1717
- The following struct types moved and are replaced with type aliases, since they are exposed to the user: `Observation`, `Measurement`.
1818
- The No-op implementations of sync and async instruments are no longer exported, new functions `sdkapi.NewNoopAsyncInstrument()` and `sdkapi.NewNoopSyncInstrument()` are provided instead. (#2271)
19+
- Update the SDK `BatchSpanProcessor` to export all queued spans when `ForceFlush` is called. (#2080, #2335)
1920

2021
### Added
2122

sdk/trace/batch_span_processor.go

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"time"
2323

2424
"go.opentelemetry.io/otel"
25+
"go.opentelemetry.io/otel/trace"
2526
)
2627

2728
// Defaults for BatchSpanProcessorOptions.
@@ -153,10 +154,29 @@ func (bsp *batchSpanProcessor) Shutdown(ctx context.Context) error {
153154
return err
154155
}
155156

157+
type forceFlushSpan struct {
158+
ReadOnlySpan
159+
flushed chan struct{}
160+
}
161+
162+
func (f forceFlushSpan) SpanContext() trace.SpanContext {
163+
return trace.NewSpanContext(trace.SpanContextConfig{TraceFlags: trace.FlagsSampled})
164+
}
165+
156166
// ForceFlush exports all ended spans that have not yet been exported.
157167
func (bsp *batchSpanProcessor) ForceFlush(ctx context.Context) error {
158168
var err error
159169
if bsp.e != nil {
170+
flushCh := make(chan struct{})
171+
if bsp.enqueueBlockOnQueueFull(ctx, forceFlushSpan{flushed: flushCh}, true) {
172+
select {
173+
case <-flushCh:
174+
// Processed any items in queue prior to ForceFlush being called
175+
case <-ctx.Done():
176+
return ctx.Err()
177+
}
178+
}
179+
160180
wait := make(chan error)
161181
go func() {
162182
wait <- bsp.exportSpans(ctx)
@@ -248,6 +268,10 @@ func (bsp *batchSpanProcessor) processQueue() {
248268
otel.Handle(err)
249269
}
250270
case sd := <-bsp.queue:
271+
if ffs, ok := sd.(forceFlushSpan); ok {
272+
close(ffs.flushed)
273+
continue
274+
}
251275
bsp.batchMutex.Lock()
252276
bsp.batch = append(bsp.batch, sd)
253277
shouldExport := len(bsp.batch) >= bsp.o.MaxExportBatchSize
@@ -296,8 +320,12 @@ func (bsp *batchSpanProcessor) drainQueue() {
296320
}
297321

298322
func (bsp *batchSpanProcessor) enqueue(sd ReadOnlySpan) {
323+
bsp.enqueueBlockOnQueueFull(context.TODO(), sd, bsp.o.BlockOnQueueFull)
324+
}
325+
326+
func (bsp *batchSpanProcessor) enqueueBlockOnQueueFull(ctx context.Context, sd ReadOnlySpan, block bool) bool {
299327
if !sd.SpanContext().IsSampled() {
300-
return
328+
return false
301329
}
302330

303331
// This ensures the bsp.queue<- below does not panic as the
@@ -317,18 +345,24 @@ func (bsp *batchSpanProcessor) enqueue(sd ReadOnlySpan) {
317345

318346
select {
319347
case <-bsp.stopCh:
320-
return
348+
return false
321349
default:
322350
}
323351

324-
if bsp.o.BlockOnQueueFull {
325-
bsp.queue <- sd
326-
return
352+
if block {
353+
select {
354+
case bsp.queue <- sd:
355+
return true
356+
case <-ctx.Done():
357+
return false
358+
}
327359
}
328360

329361
select {
330362
case bsp.queue <- sd:
363+
return true
331364
default:
332365
atomic.AddUint32(&bsp.dropped, 1)
333366
}
367+
return false
334368
}

sdk/trace/batch_span_processor_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ import (
1818
"context"
1919
"encoding/binary"
2020
"errors"
21+
"fmt"
2122
"sync"
2223
"testing"
2324
"time"
2425

2526
"github.com/stretchr/testify/assert"
2627
"github.com/stretchr/testify/require"
2728

29+
"go.opentelemetry.io/otel/sdk/trace/tracetest"
30+
2831
"go.opentelemetry.io/otel/trace"
2932

3033
sdktrace "go.opentelemetry.io/otel/sdk/trace"
@@ -457,3 +460,25 @@ func TestBatchSpanProcessorForceFlushCancellation(t *testing.T) {
457460
t.Errorf("expected %q error, got %v", want, got)
458461
}
459462
}
463+
464+
func TestBatchSpanProcessorForceFlushQueuedSpans(t *testing.T) {
465+
ctx := context.Background()
466+
467+
exp := tracetest.NewInMemoryExporter()
468+
469+
tp := sdktrace.NewTracerProvider(
470+
sdktrace.WithBatcher(exp),
471+
)
472+
473+
tracer := tp.Tracer("tracer")
474+
475+
for i := 0; i < 10; i++ {
476+
_, span := tracer.Start(ctx, fmt.Sprintf("span%d", i))
477+
span.End()
478+
479+
err := tp.ForceFlush(ctx)
480+
assert.NoError(t, err)
481+
482+
assert.Len(t, exp.GetSpans(), i+1)
483+
}
484+
}

0 commit comments

Comments
 (0)