Skip to content

Commit f59fcff

Browse files
authored
feat(spanner): add GCP standard otel attributes (#11652)
* feat(spanner): add GCP standard otel attributes * fix go vet * add gfe/afe latency values in traces * added benchmark and improved test cases * fix tests * replace hard code version in test
1 parent 0b74f43 commit f59fcff

11 files changed

Lines changed: 535 additions & 42 deletions

File tree

spanner/client.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"go.opentelemetry.io/otel/attribute"
3636
"go.opentelemetry.io/otel/metric"
3737
"go.opentelemetry.io/otel/metric/noop"
38+
otrace "go.opentelemetry.io/otel/trace"
3839
"google.golang.org/api/iterator"
3940
"google.golang.org/api/option"
4041
"google.golang.org/api/option/internaloption"
@@ -371,6 +372,7 @@ type ClientConfig struct {
371372
type openTelemetryConfig struct {
372373
enabled bool
373374
meterProvider metric.MeterProvider
375+
commonTraceStartOptions []otrace.SpanStartOption
374376
attributeMap []attribute.KeyValue
375377
attributeMapWithMultiplexed []attribute.KeyValue
376378
attributeMapWithoutMultiplexed []attribute.KeyValue
@@ -418,8 +420,8 @@ func newClientWithConfig(ctx context.Context, database string, config ClientConf
418420
return nil, err
419421
}
420422

421-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.NewClient")
422-
defer func() { trace.EndSpan(ctx, err) }()
423+
ctx, _ = startSpan(ctx, "NewClient")
424+
defer func() { endSpan(ctx, err) }()
423425

424426
// Explicitly disable some gRPC experiments as they are not stable yet.
425427
gRPCPickFirstEnvVarName := "GRPC_EXPERIMENTAL_ENABLE_NEW_PICK_FIRST"
@@ -582,8 +584,8 @@ func newClientWithConfig(ctx context.Context, database string, config ClientConf
582584
// Create a session client.
583585
sc := newSessionClient(pool, database, config.UserAgent, sessionLabels, config.DatabaseRole, config.DisableRouteToLeader, md, config.BatchTimeout, config.Logger, config.CallOptions)
584586

585-
// Create a OpenTelemetry configuration
586-
otConfig, err := createOpenTelemetryConfig(config.OpenTelemetryMeterProvider, config.Logger, sc.id, database)
587+
// Create an OpenTelemetry configuration
588+
otConfig, err := createOpenTelemetryConfig(ctx, config.OpenTelemetryMeterProvider, config.Logger, sc.id, database)
587589
if err != nil {
588590
// The error returned here will be due to database name parsing
589591
return nil, err
@@ -718,8 +720,10 @@ func metricsInterceptor() grpc.UnaryClientInterceptor {
718720
statusCode, _ := status.FromError(err)
719721
mt.currOp.currAttempt.setStatus(statusCode.Code().String())
720722
mt.currOp.currAttempt.setDirectPathUsed(peer.NewContext(ctx, peerInfo))
721-
metrics := parseServerTimingHeader(md)
722-
mt.currOp.currAttempt.setServerTimingMetrics(metrics)
723+
latencies := parseServerTimingHeader(md)
724+
span := otrace.SpanFromContext(ctx)
725+
setGFEAndAFESpanAttributes(span, latencies)
726+
mt.currOp.currAttempt.setServerTimingMetrics(latencies)
723727
recordAttemptCompletion(mt)
724728
return err
725729
}
@@ -1009,8 +1013,8 @@ func checkNestedTxn(ctx context.Context) error {
10091013
// See https://godoc.org/cloud.google.com/go/spanner#ReadWriteTransaction for
10101014
// more details.
10111015
func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error) (commitTimestamp time.Time, err error) {
1012-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.ReadWriteTransaction")
1013-
defer func() { trace.EndSpan(ctx, err) }()
1016+
ctx, _ = startSpan(ctx, "ReadWriteTransaction", c.otConfig.commonTraceStartOptions...)
1017+
defer func() { endSpan(ctx, err) }()
10141018
resp, err := c.rwTransaction(ctx, f, TransactionOptions{})
10151019
return resp.CommitTs, err
10161020
}
@@ -1023,8 +1027,8 @@ func (c *Client) ReadWriteTransaction(ctx context.Context, f func(context.Contex
10231027
// See https://godoc.org/cloud.google.com/go/spanner#ReadWriteTransaction for
10241028
// more details.
10251029
func (c *Client) ReadWriteTransactionWithOptions(ctx context.Context, f func(context.Context, *ReadWriteTransaction) error, options TransactionOptions) (resp CommitResponse, err error) {
1026-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.ReadWriteTransactionWithOptions")
1027-
defer func() { trace.EndSpan(ctx, err) }()
1030+
ctx, _ = startSpan(ctx, "ReadWriteTransactionWithOptions", c.otConfig.commonTraceStartOptions...)
1031+
defer func() { endSpan(ctx, err) }()
10281032
resp, err = c.rwTransaction(ctx, f, options)
10291033
return resp, err
10301034
}
@@ -1206,8 +1210,8 @@ func (c *Client) Apply(ctx context.Context, ms []*Mutation, opts ...ApplyOption)
12061210
opt(ao)
12071211
}
12081212

1209-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.Apply")
1210-
defer func() { trace.EndSpan(ctx, err) }()
1213+
ctx, _ = startSpan(ctx, "Apply", c.otConfig.commonTraceStartOptions...)
1214+
defer func() { endSpan(ctx, err) }()
12111215

12121216
if !ao.atLeastOnce {
12131217
resp, err := c.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, t *ReadWriteTransaction) error {
@@ -1379,7 +1383,7 @@ func (c *Client) BatchWrite(ctx context.Context, mgs []*MutationGroup) *BatchWri
13791383

13801384
// BatchWriteWithOptions is same as BatchWrite. It accepts additional options to customize the request.
13811385
func (c *Client) BatchWriteWithOptions(ctx context.Context, mgs []*MutationGroup, opts BatchWriteOptions) *BatchWriteResponseIterator {
1382-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.BatchWrite")
1386+
ctx, _ = startSpan(ctx, "BatchWrite", c.otConfig.commonTraceStartOptions...)
13831387

13841388
var err error
13851389
defer func() {
@@ -1440,7 +1444,7 @@ func (c *Client) BatchWriteWithOptions(ctx context.Context, mgs []*MutationGroup
14401444
}
14411445

14421446
ctx, cancel := context.WithCancel(ctx)
1443-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.BatchWriteResponseIterator")
1447+
ctx, _ = startSpan(ctx, "BatchWriteResponseIterator", c.otConfig.commonTraceStartOptions...)
14441448
return &BatchWriteResponseIterator{
14451449
ctx: ctx,
14461450
meterTracerFactory: c.metricsTracerFactory,

spanner/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
go.opentelemetry.io/contrib/detectors/gcp v1.36.0
1818
go.opentelemetry.io/otel v1.36.0
1919
go.opentelemetry.io/otel/metric v1.36.0
20+
go.opentelemetry.io/otel/sdk v1.36.0
2021
go.opentelemetry.io/otel/sdk/metric v1.36.0
2122
go.opentelemetry.io/otel/trace v1.36.0
2223
golang.org/x/oauth2 v0.30.0
@@ -53,7 +54,6 @@ require (
5354
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
5455
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
5556
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
56-
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
5757
golang.org/x/crypto v0.40.0 // indirect
5858
golang.org/x/net v0.42.0 // indirect
5959
golang.org/x/sys v0.34.0 // indirect

spanner/grpc_client.go

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import (
2020
"context"
2121
"strings"
2222
"sync/atomic"
23+
"time"
2324

2425
vkit "cloud.google.com/go/spanner/apiv1"
2526
"cloud.google.com/go/spanner/apiv1/spannerpb"
2627
"cloud.google.com/go/spanner/internal"
2728
"github.com/googleapis/gax-go/v2"
29+
"go.opentelemetry.io/otel/attribute"
30+
oteltrace "go.opentelemetry.io/otel/trace"
2831
"google.golang.org/api/option"
2932
"google.golang.org/grpc"
3033
"google.golang.org/grpc/peer"
@@ -171,7 +174,66 @@ func (g *grpcSpannerClient) DeleteSession(ctx context.Context, req *spannerpb.De
171174
return err
172175
}
173176

177+
// setSpanAttributes dynamically sets span attributes based on the request type.
178+
func setSpanAttributes[T any](span oteltrace.Span, req T) {
179+
if !span.IsRecording() {
180+
return
181+
}
182+
183+
var attrs []attribute.KeyValue
184+
185+
if r, ok := any(req).(interface {
186+
GetRequestOptions() *spannerpb.RequestOptions
187+
}); ok {
188+
if tag := r.GetRequestOptions().GetTransactionTag(); tag != "" {
189+
attrs = append(attrs, attribute.String("transaction.tag", tag))
190+
}
191+
if tag := r.GetRequestOptions().GetRequestTag(); tag != "" {
192+
attrs = append(attrs, attribute.String("statement.tag", tag))
193+
}
194+
}
195+
196+
if r, ok := any(req).(interface{ GetSql() string }); ok {
197+
if sql := r.GetSql(); sql != "" {
198+
attrs = append(attrs, attribute.String("db.statement", sql))
199+
}
200+
} else if r, ok := any(req).(interface {
201+
GetStatements() []*spannerpb.ExecuteBatchDmlRequest_Statement
202+
}); ok {
203+
if stmts := r.GetStatements(); len(stmts) > 0 {
204+
sqls := make([]string, len(stmts))
205+
for i, stmt := range stmts {
206+
sqls[i] = stmt.GetSql()
207+
}
208+
attrs = append(attrs, attribute.StringSlice("db.statement", sqls))
209+
}
210+
}
211+
212+
if r, ok := any(req).(interface{ GetTable() string }); ok {
213+
if table := r.GetTable(); table != "" {
214+
attrs = append(attrs, attribute.String("db.table", table))
215+
}
216+
}
217+
218+
span.SetAttributes(attrs...)
219+
}
220+
221+
func setGFEAndAFESpanAttributes(span oteltrace.Span, latencyMap map[string]time.Duration) {
222+
if !span.IsRecording() {
223+
return
224+
}
225+
for t, v := range latencyMap {
226+
if t == gfeTimingHeader || t == afeTimingHeader {
227+
span.SetAttributes(
228+
attribute.Float64(t[:3]+".latency_ms", float64(v.Nanoseconds())/1e6),
229+
)
230+
}
231+
}
232+
}
233+
174234
func (g *grpcSpannerClient) ExecuteSql(ctx context.Context, req *spannerpb.ExecuteSqlRequest, opts ...gax.CallOption) (*spannerpb.ResultSet, error) {
235+
span := oteltrace.SpanFromContext(ctx)
236+
setSpanAttributes(span, req)
175237
mt := g.newBuiltinMetricsTracer(ctx)
176238
defer recordOperationCompletion(mt)
177239
ctx = context.WithValue(ctx, metricsTracerKey, mt)
@@ -182,6 +244,8 @@ func (g *grpcSpannerClient) ExecuteSql(ctx context.Context, req *spannerpb.Execu
182244
}
183245

184246
func (g *grpcSpannerClient) ExecuteStreamingSql(ctx context.Context, req *spannerpb.ExecuteSqlRequest, opts ...gax.CallOption) (spannerpb.Spanner_ExecuteStreamingSqlClient, error) {
247+
span := oteltrace.SpanFromContext(ctx)
248+
setSpanAttributes(span, req)
185249
// Note: This method does not add g.optsWithNextRequestID to inject x-goog-spanner-request-id
186250
// as it is already manually added when creating Stream iterators for ExecuteStreamingSql.
187251
client, err := g.raw.ExecuteStreamingSql(peer.NewContext(ctx, &peer.Peer{}), req, opts...)
@@ -191,13 +255,17 @@ func (g *grpcSpannerClient) ExecuteStreamingSql(ctx context.Context, req *spanne
191255
}
192256
if mt != nil && client != nil && mt.currOp.currAttempt != nil {
193257
md, _ := client.Header()
194-
mt.currOp.currAttempt.setServerTimingMetrics(parseServerTimingHeader(md))
258+
latencyMap := parseServerTimingHeader(md)
259+
setGFEAndAFESpanAttributes(span, latencyMap)
260+
mt.currOp.currAttempt.setServerTimingMetrics(latencyMap)
195261
mt.currOp.currAttempt.setDirectPathUsed(client.Context())
196262
}
197263
return client, err
198264
}
199265

200266
func (g *grpcSpannerClient) ExecuteBatchDml(ctx context.Context, req *spannerpb.ExecuteBatchDmlRequest, opts ...gax.CallOption) (*spannerpb.ExecuteBatchDmlResponse, error) {
267+
span := oteltrace.SpanFromContext(ctx)
268+
setSpanAttributes(span, req)
201269
mt := g.newBuiltinMetricsTracer(ctx)
202270
defer recordOperationCompletion(mt)
203271
ctx = context.WithValue(ctx, metricsTracerKey, mt)
@@ -208,6 +276,8 @@ func (g *grpcSpannerClient) ExecuteBatchDml(ctx context.Context, req *spannerpb.
208276
}
209277

210278
func (g *grpcSpannerClient) Read(ctx context.Context, req *spannerpb.ReadRequest, opts ...gax.CallOption) (*spannerpb.ResultSet, error) {
279+
span := oteltrace.SpanFromContext(ctx)
280+
setSpanAttributes(span, req)
211281
mt := g.newBuiltinMetricsTracer(ctx)
212282
defer recordOperationCompletion(mt)
213283
ctx = context.WithValue(ctx, metricsTracerKey, mt)
@@ -220,14 +290,18 @@ func (g *grpcSpannerClient) Read(ctx context.Context, req *spannerpb.ReadRequest
220290
func (g *grpcSpannerClient) StreamingRead(ctx context.Context, req *spannerpb.ReadRequest, opts ...gax.CallOption) (spannerpb.Spanner_StreamingReadClient, error) {
221291
// Note: This method does not add g.optsWithNextRequestID, as it is already
222292
// manually added when creating Stream iterators for StreamingRead.
293+
span := oteltrace.SpanFromContext(ctx)
294+
setSpanAttributes(span, req)
223295
client, err := g.raw.StreamingRead(peer.NewContext(ctx, &peer.Peer{}), req, opts...)
224296
mt, ok := ctx.Value(metricsTracerKey).(*builtinMetricsTracer)
225297
if !ok {
226298
return client, err
227299
}
228300
if mt != nil && client != nil && mt.currOp.currAttempt != nil {
229301
md, _ := client.Header()
230-
mt.currOp.currAttempt.setServerTimingMetrics(parseServerTimingHeader(md))
302+
latencyMap := parseServerTimingHeader(md)
303+
setGFEAndAFESpanAttributes(span, latencyMap)
304+
mt.currOp.currAttempt.setServerTimingMetrics(latencyMap)
231305
mt.currOp.currAttempt.setDirectPathUsed(client.Context())
232306
}
233307
return client, err
@@ -244,6 +318,8 @@ func (g *grpcSpannerClient) BeginTransaction(ctx context.Context, req *spannerpb
244318
}
245319

246320
func (g *grpcSpannerClient) Commit(ctx context.Context, req *spannerpb.CommitRequest, opts ...gax.CallOption) (*spannerpb.CommitResponse, error) {
321+
span := oteltrace.SpanFromContext(ctx)
322+
setSpanAttributes(span, req)
247323
mt := g.newBuiltinMetricsTracer(ctx)
248324
defer recordOperationCompletion(mt)
249325
ctx = context.WithValue(ctx, metricsTracerKey, mt)
@@ -264,6 +340,8 @@ func (g *grpcSpannerClient) Rollback(ctx context.Context, req *spannerpb.Rollbac
264340
}
265341

266342
func (g *grpcSpannerClient) PartitionQuery(ctx context.Context, req *spannerpb.PartitionQueryRequest, opts ...gax.CallOption) (*spannerpb.PartitionResponse, error) {
343+
span := oteltrace.SpanFromContext(ctx)
344+
setSpanAttributes(span, req)
267345
mt := g.newBuiltinMetricsTracer(ctx)
268346
defer recordOperationCompletion(mt)
269347
ctx = context.WithValue(ctx, metricsTracerKey, mt)
@@ -274,6 +352,8 @@ func (g *grpcSpannerClient) PartitionQuery(ctx context.Context, req *spannerpb.P
274352
}
275353

276354
func (g *grpcSpannerClient) PartitionRead(ctx context.Context, req *spannerpb.PartitionReadRequest, opts ...gax.CallOption) (*spannerpb.PartitionResponse, error) {
355+
span := oteltrace.SpanFromContext(ctx)
356+
setSpanAttributes(span, req)
277357
mt := g.newBuiltinMetricsTracer(ctx)
278358
defer recordOperationCompletion(mt)
279359
ctx = context.WithValue(ctx, metricsTracerKey, mt)
@@ -284,14 +364,18 @@ func (g *grpcSpannerClient) PartitionRead(ctx context.Context, req *spannerpb.Pa
284364
}
285365

286366
func (g *grpcSpannerClient) BatchWrite(ctx context.Context, req *spannerpb.BatchWriteRequest, opts ...gax.CallOption) (spannerpb.Spanner_BatchWriteClient, error) {
367+
span := oteltrace.SpanFromContext(ctx)
368+
setSpanAttributes(span, req)
287369
client, err := g.raw.BatchWrite(peer.NewContext(ctx, &peer.Peer{}), req, g.optsWithNextRequestID(opts)...)
288370
mt, ok := ctx.Value(metricsTracerKey).(*builtinMetricsTracer)
289371
if !ok {
290372
return client, err
291373
}
292374
if mt != nil && client != nil && mt.currOp.currAttempt != nil {
293375
md, _ := client.Header()
294-
mt.currOp.currAttempt.setServerTimingMetrics(parseServerTimingHeader(md))
376+
latencyMap := parseServerTimingHeader(md)
377+
setGFEAndAFESpanAttributes(span, latencyMap)
378+
mt.currOp.currAttempt.setServerTimingMetrics(latencyMap)
295379
mt.currOp.currAttempt.setDirectPathUsed(client.Context())
296380
}
297381
return client, err

spanner/ot_metrics.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"go.opentelemetry.io/otel"
2424
"go.opentelemetry.io/otel/attribute"
2525
"go.opentelemetry.io/otel/metric"
26+
"go.opentelemetry.io/otel/trace"
2627
"google.golang.org/grpc/metadata"
2728
)
2829

@@ -47,22 +48,31 @@ var (
4748
otMu = sync.RWMutex{}
4849
)
4950

50-
func createOpenTelemetryConfig(mp metric.MeterProvider, logger *log.Logger, sessionClientID string, db string) (*openTelemetryConfig, error) {
51+
func createOpenTelemetryConfig(ctx context.Context, mp metric.MeterProvider, logger *log.Logger, sessionClientID string, db string) (*openTelemetryConfig, error) {
5152
// Important: snapshot the value of the global variable to ensure a
5253
// consistent value for the lifetime of this client.
5354
enabled := IsOpenTelemetryMetricsEnabled()
54-
55+
_, instance, database, err := parseDatabaseName(db)
56+
if err != nil {
57+
return nil, err
58+
}
5559
config := &openTelemetryConfig{
5660
enabled: enabled,
5761
attributeMap: []attribute.KeyValue{},
62+
commonTraceStartOptions: []trace.SpanStartOption{
63+
trace.WithAttributes(
64+
attribute.String("db.name", database),
65+
attribute.String("instance.name", instance),
66+
attribute.String("cloud.region", detectClientLocation(ctx)),
67+
attribute.String("gcp.client.version", internal.Version),
68+
attribute.String("gcp.client.repo", gcpClientRepo),
69+
attribute.String("gcp.client.artifact", gcpClientArtifact),
70+
),
71+
},
5872
}
5973
if !enabled {
6074
return config, nil
6175
}
62-
_, instance, database, err := parseDatabaseName(db)
63-
if err != nil {
64-
return nil, err
65-
}
6676

6777
// Construct attributes for Metrics
6878
attributeMap := []attribute.KeyValue{

spanner/pdml.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ func (c *Client) PartitionedUpdateWithOptions(ctx context.Context, statement Sta
4646
}
4747

4848
func (c *Client) partitionedUpdate(ctx context.Context, statement Statement, options QueryOptions) (count int64, err error) {
49-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.PartitionedUpdate")
50-
defer func() { trace.EndSpan(ctx, err) }()
49+
ctx, _ = startSpan(ctx, "PartitionedUpdate", c.otConfig.commonTraceStartOptions...)
50+
defer func() { endSpan(ctx, err) }()
5151
if err := checkNestedTxn(ctx); err != nil {
5252
return 0, err
5353
}

spanner/read.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func streamWithReplaceSessionFunc(
9292
gsc *grpcSpannerClient,
9393
) *RowIterator {
9494
ctx, cancel := context.WithCancel(ctx)
95-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.RowIterator")
95+
ctx, _ = startSpan(ctx, "RowIterator")
9696
return &RowIterator{
9797
meterTracerFactory: meterTracerFactory,
9898
streamd: newResumableStreamDecoder(ctx, cancel, logger, rpc, replaceSession, gsc),

spanner/sessionclient.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/googleapis/gax-go/v2"
3333
"go.opencensus.io/tag"
3434
"google.golang.org/api/option"
35+
3536
gtransport "google.golang.org/api/transport/grpc"
3637
"google.golang.org/grpc"
3738
"google.golang.org/grpc/codes"
@@ -269,8 +270,8 @@ func (sc *sessionClient) executeBatchCreateSessions(client spannerClient, create
269270
defer sc.waitWorkers.Done()
270271
ctx, cancel := context.WithTimeout(context.Background(), sc.batchTimeout)
271272
defer cancel()
272-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.BatchCreateSessions")
273-
defer func() { trace.EndSpan(ctx, nil) }()
273+
ctx, _ = startSpan(ctx, "BatchCreateSessions", sc.otConfig.commonTraceStartOptions...)
274+
defer func() { endSpan(ctx, nil) }()
274275
trace.TracePrintf(ctx, nil, "Creating a batch of %d sessions", createCount)
275276

276277
remainingCreateCount := createCount
@@ -341,8 +342,8 @@ func (sc *sessionClient) executeBatchCreateSessions(client spannerClient, create
341342
}
342343

343344
func (sc *sessionClient) executeCreateMultiplexedSession(ctx context.Context, client spannerClient, md metadata.MD, consumer sessionConsumer) {
344-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/spanner.CreateSession")
345-
defer func() { trace.EndSpan(ctx, nil) }()
345+
ctx, _ = startSpan(ctx, "CreateSession", sc.otConfig.commonTraceStartOptions...)
346+
defer func() { endSpan(ctx, nil) }()
346347
trace.TracePrintf(ctx, nil, "Creating a multiplexed session")
347348
sc.mu.Lock()
348349
closed := sc.closed

0 commit comments

Comments
 (0)