Merge pull request #1138 from go-redis/fix/with-context-race

Fix WithContext race
This commit is contained in:
Vladimir Mihailenco 2019-08-24 13:11:52 +03:00 committed by GitHub
commit d19aba07b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 38 additions and 45 deletions

View File

@ -647,9 +647,6 @@ func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type clusterClient struct { type clusterClient struct {
cmdable
hooks
opt *ClusterOptions opt *ClusterOptions
nodes *clusterNodes nodes *clusterNodes
state *clusterStateHolder //nolint:structcheck state *clusterStateHolder //nolint:structcheck
@ -661,6 +658,8 @@ type clusterClient struct {
// multiple goroutines. // multiple goroutines.
type ClusterClient struct { type ClusterClient struct {
*clusterClient *clusterClient
cmdable
hooks
ctx context.Context ctx context.Context
} }
@ -678,8 +677,8 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
} }
c.state = newClusterStateHolder(c.loadState) c.state = newClusterStateHolder(c.loadState)
c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo)
c.cmdable = c.Process
c.init()
if opt.IdleCheckFrequency > 0 { if opt.IdleCheckFrequency > 0 {
go c.reaper(opt.IdleCheckFrequency) go c.reaper(opt.IdleCheckFrequency)
} }
@ -687,10 +686,6 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
return c return c
} }
func (c *ClusterClient) init() {
c.cmdable = c.Process
}
func (c *ClusterClient) Context() context.Context { func (c *ClusterClient) Context() context.Context {
return c.ctx return c.ctx
} }
@ -700,8 +695,9 @@ func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient {
panic("nil context") panic("nil context")
} }
clone := *c clone := *c
clone.cmdable = clone.Process
clone.hooks.Lock()
clone.ctx = ctx clone.ctx = ctx
clone.init()
return &clone return &clone
} }

View File

@ -2,6 +2,7 @@ package redis_test
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@ -283,6 +284,13 @@ var _ = Describe("races", func() {
wg.Wait() wg.Wait()
Expect(received).To(Equal(uint32(C * N))) Expect(received).To(Equal(uint32(C * N)))
}) })
It("should WithContext", func() {
perform(C, func(_ int) {
err := client.WithContext(context.Background()).Ping().Err()
Expect(err).NotTo(HaveOccurred())
})
})
}) })
var _ = Describe("cluster races", func() { var _ = Describe("cluster races", func() {

View File

@ -32,6 +32,10 @@ type hooks struct {
hooks []Hook hooks []Hook
} }
func (hs hooks) Lock() {
hs.hooks = hs.hooks[:len(hs.hooks):len(hs.hooks)]
}
func (hs *hooks) AddHook(hook Hook) { func (hs *hooks) AddHook(hook Hook) {
hs.hooks = append(hs.hooks, hook) hs.hooks = append(hs.hooks, hook)
} }
@ -466,17 +470,13 @@ func txPipelineReadQueued(rd *proto.Reader, cmds []Cmder) error {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type client struct {
baseClient
cmdable
hooks
}
// Client is a Redis client representing a pool of zero or more // Client is a Redis client representing a pool of zero or more
// underlying connections. It's safe for concurrent use by multiple // underlying connections. It's safe for concurrent use by multiple
// goroutines. // goroutines.
type Client struct { type Client struct {
*client baseClient
cmdable
hooks
ctx context.Context ctx context.Context
} }
@ -485,23 +485,17 @@ func NewClient(opt *Options) *Client {
opt.init() opt.init()
c := Client{ c := Client{
client: &client{ baseClient: baseClient{
baseClient: baseClient{ opt: opt,
opt: opt, connPool: newConnPool(opt),
connPool: newConnPool(opt),
},
}, },
ctx: context.Background(), ctx: context.Background(),
} }
c.init() c.cmdable = c.Process
return &c return &c
} }
func (c *Client) init() {
c.cmdable = c.Process
}
func (c *Client) Context() context.Context { func (c *Client) Context() context.Context {
return c.ctx return c.ctx
} }
@ -511,8 +505,9 @@ func (c *Client) WithContext(ctx context.Context) *Client {
panic("nil context") panic("nil context")
} }
clone := *c clone := *c
clone.cmdable = clone.Process
clone.hooks.Lock()
clone.ctx = ctx clone.ctx = ctx
clone.init()
return &clone return &clone
} }

14
ring.go
View File

@ -338,8 +338,6 @@ func (c *ringShards) Close() error {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type ring struct { type ring struct {
cmdable
hooks
opt *RingOptions opt *RingOptions
shards *ringShards shards *ringShards
cmdsInfoCache *cmdsInfoCache //nolint:structcheck cmdsInfoCache *cmdsInfoCache //nolint:structcheck
@ -361,6 +359,8 @@ type ring struct {
// Otherwise you should use Redis Cluster. // Otherwise you should use Redis Cluster.
type Ring struct { type Ring struct {
*ring *ring
cmdable
hooks
ctx context.Context ctx context.Context
} }
@ -374,9 +374,8 @@ func NewRing(opt *RingOptions) *Ring {
}, },
ctx: context.Background(), ctx: context.Background(),
} }
ring.init()
ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo) ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo)
ring.cmdable = ring.Process
for name, addr := range opt.Addrs { for name, addr := range opt.Addrs {
shard := newRingShard(opt, name, addr) shard := newRingShard(opt, name, addr)
@ -398,10 +397,6 @@ func newRingShard(opt *RingOptions, name, addr string) *Client {
return shard return shard
} }
func (c *Ring) init() {
c.cmdable = c.Process
}
func (c *Ring) Context() context.Context { func (c *Ring) Context() context.Context {
return c.ctx return c.ctx
} }
@ -411,8 +406,9 @@ func (c *Ring) WithContext(ctx context.Context) *Ring {
panic("nil context") panic("nil context")
} }
clone := *c clone := *c
clone.cmdable = clone.Process
clone.hooks.Lock()
clone.ctx = ctx clone.ctx = ctx
clone.init()
return &clone return &clone
} }

View File

@ -90,16 +90,14 @@ func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
} }
c := Client{ c := Client{
client: &client{ baseClient: baseClient{
baseClient: baseClient{ opt: opt,
opt: opt, connPool: failover.Pool(),
connPool: failover.Pool(), onClose: failover.Close,
onClose: failover.Close,
},
}, },
ctx: context.Background(), ctx: context.Background(),
} }
c.init() c.cmdable = c.Process
return &c return &c
} }

4
tx.go
View File

@ -15,10 +15,9 @@ const TxFailedErr = proto.RedisError("redis: transaction failed")
// by multiple goroutines, because Exec resets list of watched keys. // by multiple goroutines, because Exec resets list of watched keys.
// If you don't need WATCH it is better to use Pipeline. // If you don't need WATCH it is better to use Pipeline.
type Tx struct { type Tx struct {
baseClient
cmdable cmdable
statefulCmdable statefulCmdable
baseClient
ctx context.Context ctx context.Context
} }
@ -49,6 +48,7 @@ func (c *Tx) WithContext(ctx context.Context) *Tx {
} }
clone := *c clone := *c
clone.ctx = ctx clone.ctx = ctx
clone.init()
return &clone return &clone
} }