@@ -41,6 +41,7 @@ import (
41
41
"google.golang.org/grpc/status"
42
42
"google.golang.org/protobuf/proto"
43
43
44
+ "github.com/open-telemetry/opentelemetry-collector-contrib/internal/grpcutil"
44
45
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/otelarrow/admission"
45
46
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/otelarrow/netstats"
46
47
internalmetadata "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otelarrowreceiver/internal/metadata"
@@ -173,7 +174,9 @@ func newHeaderReceiver(streamCtx context.Context, as auth.Server, includeMetadat
173
174
// client.Info with additional key:values associated with the arrow batch.
174
175
func (h * headerReceiver ) combineHeaders (ctx context.Context , hdrsBytes []byte ) (context.Context , map [string ][]string , error ) {
175
176
if len (hdrsBytes ) == 0 && len (h .streamHdrs ) == 0 {
176
- return ctx , nil , nil
177
+ // Note: call newContext in this case to ensure that
178
+ // connInfo is added to the context, for Auth.
179
+ return h .newContext (ctx , nil ), nil , nil
177
180
}
178
181
179
182
if len (hdrsBytes ) == 0 {
@@ -420,8 +423,8 @@ func (r *Receiver) anyStream(serverStream anyStreamServer, method string) (retEr
420
423
}
421
424
}
422
425
423
- func (r * receiverStream ) newInFlightData (ctx context.Context , method string , batchID int64 , pendingCh chan <- batchResp ) (context. Context , * inFlightData ) {
424
- ctx , span := r .tracer .Start (ctx , "otel_arrow_stream_inflight" )
426
+ func (r * receiverStream ) newInFlightData (ctx context.Context , method string , batchID int64 , pendingCh chan <- batchResp ) * inFlightData {
427
+ _ , span := r .tracer .Start (ctx , "otel_arrow_stream_inflight" )
425
428
426
429
r .inFlightWG .Add (1 )
427
430
r .telemetryBuilder .OtelArrowReceiverInFlightRequests .Add (ctx , 1 )
@@ -433,7 +436,7 @@ func (r *receiverStream) newInFlightData(ctx context.Context, method string, bat
433
436
span : span ,
434
437
}
435
438
id .refs .Add (1 )
436
- return ctx , id
439
+ return id
437
440
}
438
441
439
442
// inFlightData is responsible for storing the resources held by one request.
@@ -549,35 +552,43 @@ func (r *receiverStream) recvOne(streamCtx context.Context, serverStream anyStre
549
552
550
553
// Receive a batch corresponding with one ptrace.Traces, pmetric.Metrics,
551
554
// or plog.Logs item.
552
- req , err := serverStream .Recv ()
555
+ req , recvErr := serverStream .Recv ()
556
+
557
+ // the incoming stream context is the parent of the in-flight context, which
558
+ // carries a span covering sequential stream-processing work. the context
559
+ // is severed at this point, with flight.span a contextless child that will be
560
+ // finished in recvDone().
561
+ flight := r .newInFlightData (streamCtx , method , req .GetBatchId (), pendingCh )
553
562
554
563
// inflightCtx is carried through into consumeAndProcess on the success path.
555
- inflightCtx , flight := r .newInFlightData (streamCtx , method , req .GetBatchId (), pendingCh )
564
+ // this inherits the stream context so that its auth headers are present
565
+ // when the per-data Auth call is made.
566
+ inflightCtx := streamCtx
556
567
defer flight .recvDone (inflightCtx , & retErr )
557
568
558
- if err != nil {
559
- if errors .Is (err , io .EOF ) {
560
- return err
569
+ if recvErr != nil {
570
+ if errors .Is (recvErr , io .EOF ) {
571
+ return recvErr
561
572
562
- } else if errors .Is (err , context .Canceled ) {
573
+ } else if errors .Is (recvErr , context .Canceled ) {
563
574
// This is a special case to avoid introducing a span error
564
575
// for a canceled operation.
565
576
return io .EOF
566
577
567
- } else if status , ok := status .FromError (err ); ok && status .Code () == codes .Canceled {
578
+ } else if status , ok := status .FromError (recvErr ); ok && status .Code () == codes .Canceled {
568
579
// This is a special case to avoid introducing a span error
569
580
// for a canceled operation.
570
581
return io .EOF
571
582
}
572
583
// Note: err is directly from gRPC, should already have status.
573
- return err
584
+ return recvErr
574
585
}
575
586
576
587
// Check for optional headers and set the incoming context.
577
- inflightCtx , authHdrs , err := hrcv .combineHeaders (inflightCtx , req .GetHeaders ())
578
- if err != nil {
588
+ inflightCtx , authHdrs , hdrErr := hrcv .combineHeaders (inflightCtx , req .GetHeaders ())
589
+ if hdrErr != nil {
579
590
// Failing to parse the incoming headers breaks the stream.
580
- return status .Errorf (codes .Internal , "arrow metadata error: %v" , err )
591
+ return status .Errorf (codes .Internal , "arrow metadata error: %v" , hdrErr )
581
592
}
582
593
583
594
// start this span after hrcv.combineHeaders returns extracted context. This will allow this span
@@ -601,9 +612,29 @@ func (r *receiverStream) recvOne(streamCtx context.Context, serverStream anyStre
601
612
// This is a compressed size so make sure to acquire the difference when request is decompressed.
602
613
prevAcquiredBytes = int64 (proto .Size (req ))
603
614
} else {
604
- prevAcquiredBytes , err = strconv .ParseInt (uncompSizeHeaderStr [0 ], 10 , 64 )
605
- if err != nil {
606
- return status .Errorf (codes .Internal , "failed to convert string to request size: %v" , err )
615
+ var parseErr error
616
+ prevAcquiredBytes , parseErr = strconv .ParseInt (uncompSizeHeaderStr [0 ], 10 , 64 )
617
+ if parseErr != nil {
618
+ return status .Errorf (codes .Internal , "failed to convert string to request size: %v" , parseErr )
619
+ }
620
+ }
621
+
622
+ var callerCancel context.CancelFunc
623
+ if encodedTimeout , has := authHdrs ["grpc-timeout" ]; has && len (encodedTimeout ) == 1 {
624
+ if timeout , decodeErr := grpcutil .DecodeTimeout (encodedTimeout [0 ]); decodeErr != nil {
625
+ r .telemetry .Logger .Debug ("grpc-timeout parse error" , zap .Error (decodeErr ))
626
+ } else {
627
+ // timeout parsed successfully
628
+ inflightCtx , callerCancel = context .WithTimeout (inflightCtx , timeout )
629
+
630
+ // if we return before the new goroutine is started below
631
+ // cancel the context. callerCancel will be non-nil until
632
+ // the new goroutine is created at the end of this function.
633
+ defer func () {
634
+ if callerCancel != nil {
635
+ callerCancel ()
636
+ }
637
+ }()
607
638
}
608
639
}
609
640
@@ -612,19 +643,19 @@ func (r *receiverStream) recvOne(streamCtx context.Context, serverStream anyStre
612
643
// immediately if there are too many waiters, or will
613
644
// otherwise block until timeout or enough memory becomes
614
645
// available.
615
- err = r .boundedQueue .Acquire (inflightCtx , prevAcquiredBytes )
616
- if err != nil {
617
- return status .Errorf (codes .ResourceExhausted , "otel-arrow bounded queue: %v" , err )
646
+ acquireErr : = r .boundedQueue .Acquire (inflightCtx , prevAcquiredBytes )
647
+ if acquireErr != nil {
648
+ return status .Errorf (codes .ResourceExhausted , "otel-arrow bounded queue: %v" , acquireErr )
618
649
}
619
650
flight .numAcquired = prevAcquiredBytes
620
651
621
- data , numItems , uncompSize , err := r .consumeBatch (ac , req )
652
+ data , numItems , uncompSize , consumeErr := r .consumeBatch (ac , req )
622
653
623
- if err != nil {
624
- if errors .Is (err , arrowRecord .ErrConsumerMemoryLimit ) {
625
- return status .Errorf (codes .ResourceExhausted , "otel-arrow decode: %v" , err )
654
+ if consumeErr != nil {
655
+ if errors .Is (consumeErr , arrowRecord .ErrConsumerMemoryLimit ) {
656
+ return status .Errorf (codes .ResourceExhausted , "otel-arrow decode: %v" , consumeErr )
626
657
}
627
- return status .Errorf (codes .Internal , "otel-arrow decode: %v" , err )
658
+ return status .Errorf (codes .Internal , "otel-arrow decode: %v" , consumeErr )
628
659
}
629
660
630
661
flight .uncompSize = uncompSize
@@ -633,27 +664,35 @@ func (r *receiverStream) recvOne(streamCtx context.Context, serverStream anyStre
633
664
r .telemetryBuilder .OtelArrowReceiverInFlightBytes .Add (inflightCtx , uncompSize )
634
665
r .telemetryBuilder .OtelArrowReceiverInFlightItems .Add (inflightCtx , int64 (numItems ))
635
666
636
- numAcquired , err := r .acquireAdditionalBytes (inflightCtx , prevAcquiredBytes , uncompSize , hrcv .connInfo .Addr , uncompSizeHeaderFound )
667
+ numAcquired , secondAcquireErr := r .acquireAdditionalBytes (inflightCtx , prevAcquiredBytes , uncompSize , hrcv .connInfo .Addr , uncompSizeHeaderFound )
637
668
638
669
flight .numAcquired = numAcquired
639
- if err != nil {
640
- return status .Errorf (codes .ResourceExhausted , "otel-arrow bounded queue re-acquire: %v" , err )
670
+ if secondAcquireErr != nil {
671
+ return status .Errorf (codes .ResourceExhausted , "otel-arrow bounded queue re-acquire: %v" , secondAcquireErr )
641
672
}
642
673
643
674
// Recognize that the request is still in-flight via consumeAndRespond()
644
675
flight .refs .Add (1 )
645
676
646
677
// consumeAndRespond consumes the data and returns control to the sender loop.
647
- go r .consumeAndRespond (inflightCtx , data , flight )
678
+ go func (callerCancel context.CancelFunc ) {
679
+ if callerCancel != nil {
680
+ defer callerCancel ()
681
+ }
682
+ r .consumeAndRespond (inflightCtx , streamCtx , data , flight )
683
+ }(callerCancel )
684
+
685
+ // Reset callerCancel so the deferred function above does not call it here.
686
+ callerCancel = nil
648
687
649
688
return nil
650
689
}
651
690
652
691
// consumeAndRespond finishes the span started in recvOne and logs the
653
692
// result after invoking the pipeline to consume the data.
654
- func (r * Receiver ) consumeAndRespond (ctx context.Context , data any , flight * inFlightData ) {
693
+ func (r * Receiver ) consumeAndRespond (ctx , streamCtx context.Context , data any , flight * inFlightData ) {
655
694
var err error
656
- defer flight .consumeDone (ctx , & err )
695
+ defer flight .consumeDone (streamCtx , & err )
657
696
658
697
// recoverErr is a special function because it recovers panics, so we
659
698
// keep it in a separate defer than the processing above, which will
0 commit comments