Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0
go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/trace v1.43.0
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
golang.org/x/crypto v0.52.0
golang.org/x/net v0.55.0
Expand Down
30 changes: 25 additions & 5 deletions internal/impl/protobuf/multimodule_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (

type MultiModuleWatcher struct {
bsrClients map[string]*prototransform.SchemaWatcher
httpClient *http.Client
cancel context.CancelFunc
}

var _ prototransform.Resolver = &MultiModuleWatcher{}
Expand All @@ -27,10 +29,18 @@ func newMultiModuleWatcher(bsrModules []*service.ParsedConfig) (*MultiModuleWatc
if len(bsrModules) == 0 {
return nil, errors.New("no modules provided")
}
multiModuleWatcher := &MultiModuleWatcher{}

ctx, cancel := context.WithCancel(context.Background())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't really be persisting ctx types. I think this should rather be a shutdown.Signaller that creates a new ctx.

e.g.

ctx, cancel := multiModuleWatcher.shutSig.SoftStopCtx(context.Background())

httpClient := &http.Client{
Transport: &http.Transport{},
Comment thread
gregfurman marked this conversation as resolved.
}
multiModuleWatcher := &MultiModuleWatcher{
bsrClients: make(map[string]*prototransform.SchemaWatcher),
httpClient: httpClient,
cancel: cancel,
}

// Initialise one client for each module
multiModuleWatcher.bsrClients = make(map[string]*prototransform.SchemaWatcher)
for _, bsrModule := range bsrModules {
var bsrURL string
bsrURL, err := bsrModule.FieldString(fieldBSRUrl)
Expand All @@ -53,7 +63,7 @@ func newMultiModuleWatcher(bsrModules []*service.ParsedConfig) (*MultiModuleWatc
return nil, err
}

watcher, err := newSchemaWatcher(context.Background(), bsrURL, bsrAPIKey, module, version)
watcher, err := newSchemaWatcher(ctx, httpClient, bsrURL, bsrAPIKey, module, version)
if err != nil {
return nil, err
}
Expand All @@ -63,7 +73,7 @@ func newMultiModuleWatcher(bsrModules []*service.ParsedConfig) (*MultiModuleWatc
return multiModuleWatcher, nil
}

func newSchemaWatcher(ctx context.Context, bsrURL string, bsrAPIKey string, module string, version string) (*prototransform.SchemaWatcher, error) {
func newSchemaWatcher(ctx context.Context, httpClient *http.Client, bsrURL string, bsrAPIKey string, module string, version string) (*prototransform.SchemaWatcher, error) {
// If no BSR url provided, extract from module
if bsrURL == "" {
segments := strings.Split(module, "/")
Expand All @@ -80,7 +90,7 @@ func newSchemaWatcher(ctx context.Context, bsrURL string, bsrAPIKey string, modu
if bsrAPIKey != "" {
opts = append(opts, connectrpc.WithInterceptors(prototransform.NewAuthInterceptor(bsrAPIKey)))
}
client := reflectv1beta1connect.NewFileDescriptorSetServiceClient(http.DefaultClient, bsrURL, opts...)
client := reflectv1beta1connect.NewFileDescriptorSetServiceClient(httpClient, bsrURL, opts...)

cfg := &prototransform.SchemaWatcherConfig{
SchemaPoller: prototransform.NewSchemaPoller(
Expand Down Expand Up @@ -173,3 +183,13 @@ func (w *MultiModuleWatcher) FindEnumByName(enum protoreflect.FullName) (protore
}
return nil, fmt.Errorf("could not find %s in any loaded modules", enum)
}

func (m *MultiModuleWatcher) Close() {
m.cancel()
for _, v := range m.bsrClients {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Is it a memory leak that we never actually clear these clients stored in the map? Also, what if one stops before we call close -- should it be deleted early? 🤔

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a m.bsrClients = nil - It should be ok to call Stop() multiple times.

v.Stop()
}
if t, ok := m.httpClient.Transport.(*http.Transport); ok {
t.CloseIdleConnections()
}
}
5 changes: 4 additions & 1 deletion internal/impl/protobuf/processor_protobuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,9 @@ func (p *protobufProc) Process(ctx context.Context, msg *service.Message) (servi
return service.MessageBatch{msg}, nil
}

func (p *protobufProc) Close(context.Context) error {
func (p *protobufProc) Close(ctx context.Context) error {
if p.multiModuleWatcher != nil {
p.multiModuleWatcher.Close()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be setting p.multiModuleWatcher = nil here?

}
return nil
}
41 changes: 31 additions & 10 deletions internal/impl/protobuf/processor_protobuf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import (
"context"
"errors"
"fmt"
"net"
"net/http"
Expand All @@ -14,6 +13,7 @@
"connectrpc.com/connect"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
Expand All @@ -22,6 +22,10 @@
"github.com/warpstreamlabs/bento/public/service"
)

func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}

const protosPath = "../../../config/test/protobuf/schema"

func TestProtobufFromJSON(t *testing.T) {
Expand Down Expand Up @@ -103,6 +107,7 @@
assert.Contains(t, string(mBytes), exp)
}
require.NoError(t, msgs[0].GetError())

})

t.Run(test.name+" bsr", func(t *testing.T) {
Expand All @@ -125,6 +130,9 @@
require.NoError(t, res)
require.Len(t, msgs, 1)

err = proc.Close(context.Background())
require.NoError(t, err)

mBytes, err := msgs[0].AsBytes()
require.NoError(t, err)

Expand Down Expand Up @@ -238,6 +246,9 @@
require.NoError(t, res)
require.Len(t, msgs, 1)

err = proc.Close(context.Background())
require.NoError(t, err)

mBytes, err := msgs[0].AsBytes()
require.NoError(t, err)

Expand Down Expand Up @@ -266,6 +277,9 @@
require.NoError(t, res)
require.Len(t, msgs, 1)

err = proc.Close(context.Background())
require.NoError(t, err)

mBytes, err := msgs[0].AsBytes()
require.NoError(t, err)

Expand Down Expand Up @@ -324,6 +338,9 @@
_, err = proc.Process(context.Background(), service.NewMessage([]byte(test.input)))
require.Error(t, err)
require.Contains(t, err.Error(), test.output)

err = proc.Close(context.Background())
require.NoError(t, err)
})

t.Run(test.name+" bsr", func(tt *testing.T) {
Expand All @@ -344,6 +361,9 @@
_, err = proc.Process(context.Background(), service.NewMessage([]byte(test.input)))
require.Error(t, err)
require.Contains(t, err.Error(), test.output)

err = proc.Close(context.Background())
require.NoError(t, err)
})
}
}
Expand Down Expand Up @@ -456,16 +476,17 @@
mux := http.NewServeMux()
fileDescriptorSetServer := &fileDescriptorSetServer{fileDescriptorSet: fileDescriptorSet}
mux.Handle(reflectv1beta1connect.NewFileDescriptorSetServiceHandler(fileDescriptorSetServer))
go func() {
srv := &http.Server{Handler: mux}
srv.Protocols = new(http.Protocols)
srv.Protocols.SetHTTP1(true)
srv.Protocols.SetUnencryptedHTTP2(true)

if err := http.Serve(listener, srv.Handler); err != nil && !errors.Is(err, http.ErrServerClosed) {
require.NoError(t, err)
}
}()
srv := &http.Server{Handler: mux}
srv.Protocols = new(http.Protocols)
srv.Protocols.SetHTTP1(true)
srv.Protocols.SetUnencryptedHTTP2(true)

go srv.Serve(listener)

Check failure on line 485 in internal/impl/protobuf/processor_protobuf_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `srv.Serve` is not checked (errcheck)

t.Cleanup(func() {
srv.Close()
})

return listener.Addr().String()
}
Loading