2020-09-09 19:19:03 +02:00
|
|
|
// 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 otlp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
Some cleanups in otlp exporter (#1350)
* Drop WorkerCount option
This is not a good option - the user isn't likely to know how many
worker goroutines is optimal. This should be something that an
exporter should figure out itself. The second problem with the option
is that it is specific to the metric transformation from SDK export
format into protobuf. When the exporter starts supporting other
protocols (HTTP/JSON for example), this option may be of no use. So
the option should rather belong to the protocol, not to the
exporter. Currently both mean the same, but later they will be
separated, and this option breaks the separation.
* Make stop channel a typical signalling channel
Signalling channels are idiomatically defined as chan struct{}, so
let's make it so, to avoid confusion about the meaning of the bool
type.
* Close a race when grpc connection is closed multiple times
If several goroutines call Shutdown at the same time, then the
following scenario is possible:
goroutine A locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine B locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine A does not return early in the "if !started" conditional and
continues to close the connection and execute the rest of the function
(where it finally sets the started member to false), gets preempted
goroutine B also does not return early, because it got a copy of
started before goroutine A set it to false, so it tries to close the
connection again.
* Update changelog
2020-11-20 04:03:25 +02:00
|
|
|
"sync"
|
2020-09-09 19:19:03 +02:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestExporterShutdownHonorsTimeout(t *testing.T) {
|
|
|
|
orig := closeStopCh
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
|
|
|
defer func() {
|
|
|
|
cancel()
|
|
|
|
closeStopCh = orig
|
|
|
|
}()
|
Some cleanups in otlp exporter (#1350)
* Drop WorkerCount option
This is not a good option - the user isn't likely to know how many
worker goroutines is optimal. This should be something that an
exporter should figure out itself. The second problem with the option
is that it is specific to the metric transformation from SDK export
format into protobuf. When the exporter starts supporting other
protocols (HTTP/JSON for example), this option may be of no use. So
the option should rather belong to the protocol, not to the
exporter. Currently both mean the same, but later they will be
separated, and this option breaks the separation.
* Make stop channel a typical signalling channel
Signalling channels are idiomatically defined as chan struct{}, so
let's make it so, to avoid confusion about the meaning of the bool
type.
* Close a race when grpc connection is closed multiple times
If several goroutines call Shutdown at the same time, then the
following scenario is possible:
goroutine A locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine B locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine A does not return early in the "if !started" conditional and
continues to close the connection and execute the rest of the function
(where it finally sets the started member to false), gets preempted
goroutine B also does not return early, because it got a copy of
started before goroutine A set it to false, so it tries to close the
connection again.
* Update changelog
2020-11-20 04:03:25 +02:00
|
|
|
closeStopCh = func(stopCh chan struct{}) {
|
2020-09-09 19:19:03 +02:00
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
close(stopCh)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
e := NewUnstartedExporter()
|
|
|
|
if err := e.Start(); err != nil {
|
|
|
|
t.Fatalf("failed to start exporter: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
innerCtx, innerCancel := context.WithTimeout(ctx, time.Microsecond)
|
|
|
|
if err := e.Shutdown(innerCtx); err == nil {
|
|
|
|
t.Error("expected context DeadlineExceeded error, got nil")
|
|
|
|
} else if !errors.Is(err, context.DeadlineExceeded) {
|
|
|
|
t.Errorf("expected context DeadlineExceeded error, got %v", err)
|
|
|
|
}
|
|
|
|
innerCancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExporterShutdownHonorsCancel(t *testing.T) {
|
|
|
|
orig := closeStopCh
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
|
|
|
defer func() {
|
|
|
|
cancel()
|
|
|
|
closeStopCh = orig
|
|
|
|
}()
|
Some cleanups in otlp exporter (#1350)
* Drop WorkerCount option
This is not a good option - the user isn't likely to know how many
worker goroutines is optimal. This should be something that an
exporter should figure out itself. The second problem with the option
is that it is specific to the metric transformation from SDK export
format into protobuf. When the exporter starts supporting other
protocols (HTTP/JSON for example), this option may be of no use. So
the option should rather belong to the protocol, not to the
exporter. Currently both mean the same, but later they will be
separated, and this option breaks the separation.
* Make stop channel a typical signalling channel
Signalling channels are idiomatically defined as chan struct{}, so
let's make it so, to avoid confusion about the meaning of the bool
type.
* Close a race when grpc connection is closed multiple times
If several goroutines call Shutdown at the same time, then the
following scenario is possible:
goroutine A locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine B locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine A does not return early in the "if !started" conditional and
continues to close the connection and execute the rest of the function
(where it finally sets the started member to false), gets preempted
goroutine B also does not return early, because it got a copy of
started before goroutine A set it to false, so it tries to close the
connection again.
* Update changelog
2020-11-20 04:03:25 +02:00
|
|
|
closeStopCh = func(stopCh chan struct{}) {
|
2020-09-09 19:19:03 +02:00
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
close(stopCh)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
e := NewUnstartedExporter()
|
|
|
|
if err := e.Start(); err != nil {
|
|
|
|
t.Fatalf("failed to start exporter: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var innerCancel context.CancelFunc
|
|
|
|
ctx, innerCancel = context.WithCancel(ctx)
|
|
|
|
innerCancel()
|
|
|
|
if err := e.Shutdown(ctx); err == nil {
|
|
|
|
t.Error("expected context canceled error, got nil")
|
|
|
|
} else if !errors.Is(err, context.Canceled) {
|
|
|
|
t.Errorf("expected context canceled error, got %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExporterShutdownNoError(t *testing.T) {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
e := NewUnstartedExporter()
|
|
|
|
if err := e.Start(); err != nil {
|
|
|
|
t.Fatalf("failed to start exporter: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := e.Shutdown(ctx); err != nil {
|
|
|
|
t.Errorf("shutdown errored: expected nil, got %v", err)
|
|
|
|
}
|
|
|
|
}
|
Some cleanups in otlp exporter (#1350)
* Drop WorkerCount option
This is not a good option - the user isn't likely to know how many
worker goroutines is optimal. This should be something that an
exporter should figure out itself. The second problem with the option
is that it is specific to the metric transformation from SDK export
format into protobuf. When the exporter starts supporting other
protocols (HTTP/JSON for example), this option may be of no use. So
the option should rather belong to the protocol, not to the
exporter. Currently both mean the same, but later they will be
separated, and this option breaks the separation.
* Make stop channel a typical signalling channel
Signalling channels are idiomatically defined as chan struct{}, so
let's make it so, to avoid confusion about the meaning of the bool
type.
* Close a race when grpc connection is closed multiple times
If several goroutines call Shutdown at the same time, then the
following scenario is possible:
goroutine A locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine B locks a mutex, reads a started member, unlocks the mutex
and gets preempted
goroutine A does not return early in the "if !started" conditional and
continues to close the connection and execute the rest of the function
(where it finally sets the started member to false), gets preempted
goroutine B also does not return early, because it got a copy of
started before goroutine A set it to false, so it tries to close the
connection again.
* Update changelog
2020-11-20 04:03:25 +02:00
|
|
|
|
|
|
|
func TestExporterShutdownManyTimes(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
e, err := NewExporter()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to start an exporter: %v", err)
|
|
|
|
}
|
|
|
|
ch := make(chan struct{})
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
const num int = 20
|
|
|
|
wg.Add(num)
|
|
|
|
errs := make([]error, num)
|
|
|
|
for i := 0; i < num; i++ {
|
|
|
|
go func(idx int) {
|
|
|
|
defer wg.Done()
|
|
|
|
<-ch
|
|
|
|
errs[idx] = e.Shutdown(ctx)
|
|
|
|
}(i)
|
|
|
|
}
|
|
|
|
close(ch)
|
|
|
|
wg.Wait()
|
|
|
|
for _, err := range errs {
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to shutdown exporter: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|