From 1a70180f35f423e98c3f91cffdba0322f460cb36 Mon Sep 17 00:00:00 2001 From: Joshua Humphries Date: Thu, 29 Mar 2018 13:34:29 -0400 Subject: [PATCH] client: Fix race when using both client-side default CallOptions and per-call CallOptions (#1948) --- call.go | 17 ++++++++++++++++- stream.go | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/call.go b/call.go index b2590e97..f73b7d55 100644 --- a/call.go +++ b/call.go @@ -29,7 +29,7 @@ import ( func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error { // allow interceptor to see all applicable call options, which means those // configured as defaults from dial option as well as per-call options - opts = append(cc.dopts.callOptions, opts...) + opts = combine(cc.dopts.callOptions, opts) if cc.dopts.unaryInt != nil { return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...) @@ -37,6 +37,21 @@ func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply int return invoke(ctx, method, args, reply, cc, opts...) } +func combine(o1 []CallOption, o2 []CallOption) []CallOption { + // we don't use append because o1 could have extra capacity whose + // elements would be overwritten, which could cause inadvertent + // sharing (and race connditions) between concurrent calls + if len(o1) == 0 { + return o2 + } else if len(o2) == 0 { + return o1 + } + ret := make([]CallOption, len(o1)+len(o2)) + copy(ret, o1) + copy(ret[len(o1):], o2) + return ret +} + // Invoke sends the RPC request on the wire and returns after response is // received. This is typically called by generated code. // diff --git a/stream.go b/stream.go index ff376cb0..a79f385a 100644 --- a/stream.go +++ b/stream.go @@ -104,7 +104,7 @@ type ClientStream interface { func (cc *ClientConn) NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error) { // allow interceptor to see all applicable call options, which means those // configured as defaults from dial option as well as per-call options - opts = append(cc.dopts.callOptions, opts...) + opts = combine(cc.dopts.callOptions, opts) if cc.dopts.streamInt != nil { return cc.dopts.streamInt(ctx, desc, cc, method, newClientStream, opts...)