diff --git a/call.go b/call.go index b2590e97b..f73b7d552 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 ff376cb01..a79f385a7 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...)