Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(transport): fix memory leak in grpc.Dial #2329

Merged
merged 3 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 23 additions & 1 deletion transport/grpc/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net"
"os"
"strings"
"sync"
"time"

"cloud.google.com/go/compute/metadata"
Expand All @@ -27,6 +28,7 @@ import (
grpcgoogle "google.golang.org/grpc/credentials/google"
grpcinsecure "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/credentials/oauth"
"google.golang.org/grpc/stats"

// Install grpclb, which is required for direct path.
_ "google.golang.org/grpc/balancer/grpclb"
Expand All @@ -47,6 +49,26 @@ var logRateLimiter = rate.Sometimes{Interval: 1 * time.Second}
// Assign to var for unit test replacement
var dialContext = grpc.DialContext

// otelStatsHandler is a singleton otelgrpc.clientHandler to be used across
// all dial connections to avoid the memory leak documented in
// https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4226
//
// TODO: If 4226 has been fixed in opentelemetry-go-contrib, replace this
// singleton with inline usage for simplicity.
var (
initOtelStatsHandlerOnce sync.Once
otelStatsHandler stats.Handler
)

// otelGRPCStatsHandler returns singleton otelStatsHandler for reuse across all
// dial connections.
func otelGRPCStatsHandler() stats.Handler {
initOtelStatsHandlerOnce.Do(func() {
otelStatsHandler = otelgrpc.NewClientHandler()
})
return otelStatsHandler
}

// Dial returns a GRPC connection for use communicating with a Google cloud
// service, configured with the given ClientOptions.
func Dial(ctx context.Context, opts ...option.ClientOption) (*grpc.ClientConn, error) {
Expand Down Expand Up @@ -219,7 +241,7 @@ func addOpenTelemetryStatsHandler(opts []grpc.DialOption, settings *internal.Dia
if settings.TelemetryDisabled {
return opts
}
return append(opts, grpc.WithStatsHandler(otelgrpc.NewClientHandler()))
return append(opts, grpc.WithStatsHandler(otelGRPCStatsHandler()))
}

// grpcTokenSource supplies PerRPCCredentials from an oauth.TokenSource.
Expand Down
2 changes: 1 addition & 1 deletion transport/grpc/dial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func TestLogDirectPathMisconfigNotOnGCE(t *testing.T) {
logDirectPathMisconfig(endpoint, creds.TokenSource, o)

if !metadata.OnGCE() {
wantedLog := "WARNING: DirectPath is misconfigured. DirectPath is only available in a GCE environment.."
wantedLog := "WARNING: DirectPath is misconfigured. DirectPath is only available in a GCE environment."
if !strings.Contains(buf.String(), wantedLog) {
t.Fatalf("got: %v, want: %v", buf.String(), wantedLog)
}
Expand Down