| // Copyright 2011 Gary Burd |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"): you may |
| // not use this file except in compliance with the License. You may obtain |
| // a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations |
| // under the License. |
| |
| package redis_test |
| |
| import ( |
| "context" |
| "errors" |
| "io" |
| "net" |
| "reflect" |
| "sync" |
| "testing" |
| "time" |
| |
| "github.com/gomodule/redigo/redis" |
| "github.com/stretchr/testify/require" |
| ) |
| |
| const ( |
| testGoRoutines = 10 |
| ) |
| |
| type poolTestConn struct { |
| d *poolDialer |
| err error |
| redis.Conn |
| } |
| |
| func (c *poolTestConn) Close() error { |
| c.d.mu.Lock() |
| c.d.open -= 1 |
| c.d.mu.Unlock() |
| return c.Conn.Close() |
| } |
| |
| func (c *poolTestConn) Err() error { return c.err } |
| |
| func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) { |
| if commandName == "ERR" { |
| c.err = args[0].(error) |
| commandName = "PING" |
| } |
| if commandName != "" { |
| c.d.commands = append(c.d.commands, commandName) |
| } |
| return c.Conn.Do(commandName, args...) |
| } |
| |
| func (c *poolTestConn) Send(commandName string, args ...interface{}) error { |
| c.d.commands = append(c.d.commands, commandName) |
| return c.Conn.Send(commandName, args...) |
| } |
| |
| type poolDialer struct { |
| mu sync.Mutex |
| t *testing.T |
| dialed int |
| open int |
| commands []string |
| dialErr error |
| } |
| |
| func (d *poolDialer) dial() (redis.Conn, error) { |
| d.mu.Lock() |
| d.dialed += 1 |
| dialErr := d.dialErr |
| d.mu.Unlock() |
| if dialErr != nil { |
| return nil, d.dialErr |
| } |
| c, err := redis.DialDefaultServer() |
| if err != nil { |
| return nil, err |
| } |
| d.mu.Lock() |
| d.open += 1 |
| d.mu.Unlock() |
| return &poolTestConn{d: d, Conn: c}, nil |
| } |
| |
| func (d *poolDialer) dialContext(ctx context.Context) (redis.Conn, error) { |
| return d.dial() |
| } |
| |
| func (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse int) { |
| d.checkAll(message, p, dialed, open, inuse, 0, 0) |
| } |
| |
| func (d *poolDialer) checkAll(message string, p *redis.Pool, dialed, open, inuse int, waitCountMax int64, waitDurationMax time.Duration) { |
| d.mu.Lock() |
| defer d.mu.Unlock() |
| |
| if d.dialed != dialed { |
| d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) |
| } |
| if d.open != open { |
| d.t.Errorf("%s: open=%d, want %d", message, d.open, open) |
| } |
| |
| stats := p.Stats() |
| |
| if stats.ActiveCount != open { |
| d.t.Errorf("%s: active=%d, want %d", message, stats.ActiveCount, open) |
| } |
| if stats.IdleCount != open-inuse { |
| d.t.Errorf("%s: idle=%d, want %d", message, stats.IdleCount, open-inuse) |
| } |
| |
| if stats.WaitCount > waitCountMax { |
| d.t.Errorf("%s: unexpected wait=%d want at most %d", message, stats.WaitCount, waitCountMax) |
| } |
| |
| if waitCountMax == 0 { |
| if stats.WaitDuration != 0 { |
| d.t.Errorf("%s: unexpected waitDuration=%v want %v", message, stats.WaitDuration, 0) |
| } |
| return |
| } |
| |
| if stats.WaitDuration > waitDurationMax { |
| d.t.Errorf("%s: unexpected waitDuration=%v want < %v", message, stats.WaitDuration, waitDurationMax) |
| } |
| } |
| |
| func TestPoolReuse(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| Dial: d.dial, |
| } |
| |
| for i := 0; i < 10; i++ { |
| c1 := p.Get() |
| _, err := c1.Do("PING") |
| require.NoError(t, err) |
| c2 := p.Get() |
| _, err = c2.Do("PING") |
| require.NoError(t, err) |
| require.NoError(t, c1.Close()) |
| require.NoError(t, c2.Close()) |
| } |
| |
| d.check("before close", p, 2, 2, 0) |
| p.Close() |
| d.check("after close", p, 2, 0, 0) |
| } |
| |
| func TestPoolMaxIdle(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| for i := 0; i < 10; i++ { |
| c1 := p.Get() |
| _, err = c1.Do("PING") |
| require.NoError(t, err) |
| c2 := p.Get() |
| _, err = c2.Do("PING") |
| require.NoError(t, err) |
| c3 := p.Get() |
| _, err = c3.Do("PING") |
| require.NoError(t, err) |
| require.NoError(t, c1.Close()) |
| require.NoError(t, c2.Close()) |
| require.NoError(t, c3.Close()) |
| } |
| d.check("before close", p, 12, 2, 0) |
| p.Close() |
| d.check("after close", p, 12, 0, 0) |
| } |
| |
| func TestPoolError(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| _, err := c.Do("ERR", io.EOF) |
| require.NoError(t, err) |
| if c.Err() == nil { |
| t.Errorf("expected c.Err() != nil") |
| } |
| c.Close() |
| |
| c = p.Get() |
| _, err = c.Do("ERR", io.EOF) |
| require.NoError(t, err) |
| c.Close() |
| |
| d.check(".", p, 2, 0, 0) |
| } |
| |
| func TestPoolClose(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| c1 := p.Get() |
| _, err := c1.Do("PING") |
| require.NoError(t, err) |
| c2 := p.Get() |
| _, err = c2.Do("PING") |
| require.NoError(t, err) |
| c3 := p.Get() |
| _, err = c3.Do("PING") |
| require.NoError(t, err) |
| |
| c1.Close() |
| if _, err := c1.Do("PING"); err == nil { |
| t.Errorf("expected error after connection closed") |
| } |
| |
| c2.Close() |
| c2.Close() |
| |
| p.Close() |
| |
| d.check("after pool close", p, 3, 1, 1) |
| |
| if _, err := c1.Do("PING"); err == nil { |
| t.Errorf("expected error after connection and pool closed") |
| } |
| |
| c3.Close() |
| |
| d.check("after conn close", p, 3, 0, 0) |
| |
| c1 = p.Get() |
| if _, err := c1.Do("PING"); err == nil { |
| t.Errorf("expected error after pool closed") |
| } |
| } |
| |
| func TestPoolClosedConn(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| IdleTimeout: 300 * time.Second, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| c := p.Get() |
| if c.Err() != nil { |
| t.Fatal("get failed") |
| } |
| c.Close() |
| if err := c.Err(); err == nil { |
| t.Fatal("Err on closed connection did not return error") |
| } |
| if _, err := c.Do("PING"); err == nil { |
| t.Fatal("Do on closed connection did not return error") |
| } |
| if err := c.Send("PING"); err == nil { |
| t.Fatal("Send on closed connection did not return error") |
| } |
| if err := c.Flush(); err == nil { |
| t.Fatal("Flush on closed connection did not return error") |
| } |
| if _, err := c.Receive(); err == nil { |
| t.Fatal("Receive on closed connection did not return error") |
| } |
| } |
| |
| func TestPoolIdleTimeout(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| IdleTimeout: 300 * time.Second, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| now := time.Now() |
| redis.SetNowFunc(func() time.Time { return now }) |
| defer redis.SetNowFunc(time.Now) |
| |
| c := p.Get() |
| _, err := c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| d.check("1", p, 1, 1, 0) |
| |
| now = now.Add(p.IdleTimeout + 1) |
| |
| c = p.Get() |
| _, err = c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| d.check("2", p, 2, 1, 0) |
| } |
| |
| func TestPoolMaxLifetime(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| MaxConnLifetime: 300 * time.Second, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| now := time.Now() |
| redis.SetNowFunc(func() time.Time { return now }) |
| defer redis.SetNowFunc(time.Now) |
| |
| c := p.Get() |
| _, err := c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| d.check("1", p, 1, 1, 0) |
| |
| now = now.Add(p.MaxConnLifetime + 1) |
| |
| c = p.Get() |
| _, err = c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| d.check("2", p, 2, 1, 0) |
| } |
| |
| func TestPoolConcurrenSendReceive(t *testing.T) { |
| p := &redis.Pool{ |
| Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| done := make(chan error, 1) |
| go func() { |
| _, err := c.Receive() |
| done <- err |
| }() |
| require.NoError(t, c.Send("PING")) |
| c.Flush() |
| err := <-done |
| if err != nil { |
| t.Fatalf("Receive() returned error %v", err) |
| } |
| _, err = c.Do("") |
| if err != nil { |
| t.Fatalf("Do() returned error %v", err) |
| } |
| c.Close() |
| } |
| |
| func TestPoolBorrowCheck(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| Dial: d.dial, |
| TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") }, |
| } |
| defer p.Close() |
| |
| for i := 0; i < 10; i++ { |
| c := p.Get() |
| _, err := c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| } |
| d.check("1", p, 10, 1, 0) |
| } |
| |
| func TestPoolMaxActive(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| MaxActive: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| c1 := p.Get() |
| _, err := c1.Do("PING") |
| require.NoError(t, err) |
| c2 := p.Get() |
| _, err = c2.Do("PING") |
| require.NoError(t, err) |
| |
| d.check("1", p, 2, 2, 2) |
| |
| c3 := p.Get() |
| if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted { |
| t.Errorf("expected pool exhausted") |
| } |
| |
| c3.Close() |
| d.check("2", p, 2, 2, 2) |
| c2.Close() |
| d.check("3", p, 2, 2, 1) |
| |
| c3 = p.Get() |
| if _, err := c3.Do("PING"); err != nil { |
| t.Errorf("expected good channel, err=%v", err) |
| } |
| c3.Close() |
| |
| d.check("4", p, 2, 2, 1) |
| } |
| |
| func TestPoolWaitStats(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| Wait: true, |
| MaxIdle: 2, |
| MaxActive: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| c1 := p.Get() |
| _, err := c1.Do("PING") |
| require.NoError(t, err) |
| c2 := p.Get() |
| _, err = c2.Do("PING") |
| require.NoError(t, err) |
| |
| d.checkAll("1", p, 2, 2, 2, 0, 0) |
| |
| start := time.Now() |
| go func() { |
| time.Sleep(time.Millisecond * 100) |
| c1.Close() |
| }() |
| |
| c3 := p.Get() |
| d.checkAll("2", p, 2, 2, 2, 1, time.Since(start)) |
| |
| if _, err := c3.Do("PING"); err != nil { |
| t.Errorf("expected good channel, err=%v", err) |
| } |
| } |
| |
| func TestPoolMonitorCleanup(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| MaxActive: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| require.NoError(t, c.Send("MONITOR")) |
| c.Close() |
| |
| d.check("", p, 1, 0, 0) |
| } |
| |
| func TestPoolPubSubCleanup(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| MaxActive: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| require.NoError(t, c.Send("SUBSCRIBE", "x")) |
| c.Close() |
| |
| want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} |
| if !reflect.DeepEqual(d.commands, want) { |
| t.Errorf("got commands %v, want %v", d.commands, want) |
| } |
| d.commands = nil |
| |
| c = p.Get() |
| require.NoError(t, c.Send("PSUBSCRIBE", "x*")) |
| c.Close() |
| |
| want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} |
| if !reflect.DeepEqual(d.commands, want) { |
| t.Errorf("got commands %v, want %v", d.commands, want) |
| } |
| d.commands = nil |
| } |
| |
| func TestPoolTransactionCleanup(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 2, |
| MaxActive: 2, |
| Dial: d.dial, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| _, err := c.Do("WATCH", "key") |
| require.NoError(t, err) |
| _, err = c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| want := []string{"WATCH", "PING", "UNWATCH"} |
| if !reflect.DeepEqual(d.commands, want) { |
| t.Errorf("got commands %v, want %v", d.commands, want) |
| } |
| d.commands = nil |
| |
| c = p.Get() |
| _, err = c.Do("WATCH", "key") |
| require.NoError(t, err) |
| _, err = c.Do("UNWATCH") |
| require.NoError(t, err) |
| _, err = c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| want = []string{"WATCH", "UNWATCH", "PING"} |
| if !reflect.DeepEqual(d.commands, want) { |
| t.Errorf("got commands %v, want %v", d.commands, want) |
| } |
| d.commands = nil |
| |
| c = p.Get() |
| _, err = c.Do("WATCH", "key") |
| require.NoError(t, err) |
| _, err = c.Do("MULTI") |
| require.NoError(t, err) |
| _, err = c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| want = []string{"WATCH", "MULTI", "PING", "DISCARD"} |
| if !reflect.DeepEqual(d.commands, want) { |
| t.Errorf("got commands %v, want %v", d.commands, want) |
| } |
| d.commands = nil |
| |
| c = p.Get() |
| _, err = c.Do("WATCH", "key") |
| require.NoError(t, err) |
| _, err = c.Do("MULTI") |
| require.NoError(t, err) |
| _, err = c.Do("DISCARD") |
| require.NoError(t, err) |
| _, err = c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| want = []string{"WATCH", "MULTI", "DISCARD", "PING"} |
| if !reflect.DeepEqual(d.commands, want) { |
| t.Errorf("got commands %v, want %v", d.commands, want) |
| } |
| d.commands = nil |
| |
| c = p.Get() |
| _, err = c.Do("WATCH", "key") |
| require.NoError(t, err) |
| _, err = c.Do("MULTI") |
| require.NoError(t, err) |
| _, err = c.Do("EXEC") |
| require.NoError(t, err) |
| _, err = c.Do("PING") |
| require.NoError(t, err) |
| c.Close() |
| |
| want = []string{"WATCH", "MULTI", "EXEC", "PING"} |
| if !reflect.DeepEqual(d.commands, want) { |
| t.Errorf("got commands %v, want %v", d.commands, want) |
| } |
| d.commands = nil |
| } |
| |
| func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error { |
| errs := make(chan error, testGoRoutines) |
| for i := 0; i < cap(errs); i++ { |
| go func() { |
| c := p.Get() |
| _, err := c.Do(cmd, args...) |
| c.Close() |
| errs <- err |
| }() |
| } |
| |
| return errs |
| } |
| |
| func TestWaitPool(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| start := time.Now() |
| errs := startGoroutines(p, "PING") |
| d.check("before close", p, 1, 1, 1) |
| c.Close() |
| timeout := time.After(2 * time.Second) |
| for i := 0; i < cap(errs); i++ { |
| select { |
| case err := <-errs: |
| if err != nil { |
| t.Fatal(err) |
| } |
| case <-timeout: |
| t.Fatalf("timeout waiting for blocked goroutine %d", i) |
| } |
| } |
| d.checkAll("done", p, 1, 1, 0, testGoRoutines, time.Since(start)*testGoRoutines) |
| } |
| |
| func TestWaitPoolClose(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| if _, err := c.Do("PING"); err != nil { |
| t.Fatal(err) |
| } |
| start := time.Now() |
| errs := startGoroutines(p, "PING") |
| d.check("before close", p, 1, 1, 1) |
| p.Close() |
| timeout := time.After(2 * time.Second) |
| for i := 0; i < cap(errs); i++ { |
| select { |
| case err := <-errs: |
| switch err { |
| case nil: |
| t.Fatal("blocked goroutine did not get error") |
| case redis.ErrPoolExhausted: |
| t.Fatal("blocked goroutine got pool exhausted error") |
| } |
| case <-timeout: |
| t.Fatal("timeout waiting for blocked goroutine") |
| } |
| } |
| c.Close() |
| d.checkAll("done", p, 1, 0, 0, testGoRoutines, time.Since(start)*testGoRoutines) |
| } |
| |
| func TestWaitPoolCommandError(t *testing.T) { |
| testErr := errors.New("test") |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| start := time.Now() |
| errs := startGoroutines(p, "ERR", testErr) |
| d.check("before close", p, 1, 1, 1) |
| c.Close() |
| timeout := time.After(2 * time.Second) |
| for i := 0; i < cap(errs); i++ { |
| select { |
| case err := <-errs: |
| if err != nil { |
| t.Fatal(err) |
| } |
| case <-timeout: |
| t.Fatalf("timeout waiting for blocked goroutine %d", i) |
| } |
| } |
| d.checkAll("done", p, cap(errs), 0, 0, testGoRoutines, time.Since(start)*testGoRoutines) |
| } |
| |
| func TestWaitPoolDialError(t *testing.T) { |
| testErr := errors.New("test") |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| |
| c := p.Get() |
| start := time.Now() |
| errs := startGoroutines(p, "ERR", testErr) |
| d.check("before close", p, 1, 1, 1) |
| |
| d.dialErr = errors.New("dial") |
| c.Close() |
| |
| nilCount := 0 |
| errCount := 0 |
| timeout := time.After(2 * time.Second) |
| for i := 0; i < cap(errs); i++ { |
| select { |
| case err := <-errs: |
| switch err { |
| case nil: |
| nilCount++ |
| case d.dialErr: |
| errCount++ |
| default: |
| t.Fatalf("expected dial error or nil, got %v", err) |
| } |
| case <-timeout: |
| t.Fatalf("timeout waiting for blocked goroutine %d", i) |
| } |
| } |
| if nilCount != 1 { |
| t.Errorf("expected one nil error, got %d", nilCount) |
| } |
| if errCount != cap(errs)-1 { |
| t.Errorf("expected %d dial errors, got %d", cap(errs)-1, errCount) |
| } |
| d.checkAll("done", p, cap(errs), 0, 0, testGoRoutines, time.Since(start)*testGoRoutines) |
| } |
| |
| // Borrowing requires us to iterate over the idle connections, unlock the pool, |
| // and perform a blocking operation to check the connection still works. If |
| // TestOnBorrow fails, we must reacquire the lock and continue iteration. This |
| // test ensures that iteration will work correctly if multiple threads are |
| // iterating simultaneously. |
| func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { |
| const count = 100 |
| |
| // First we'll Create a pool where the pilfering of idle connections fails. |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: count, |
| MaxActive: count, |
| Dial: d.dial, |
| TestOnBorrow: func(c redis.Conn, t time.Time) error { |
| return errors.New("No way back into the real world.") |
| }, |
| } |
| defer p.Close() |
| |
| // Fill the pool with idle connections. |
| conns := make([]redis.Conn, count) |
| for i := range conns { |
| conns[i] = p.Get() |
| } |
| for i := range conns { |
| conns[i].Close() |
| } |
| |
| // Spawn a bunch of goroutines to thrash the pool. |
| var wg sync.WaitGroup |
| wg.Add(count) |
| for i := 0; i < count; i++ { |
| go func() { |
| c := p.Get() |
| if c.Err() != nil { |
| t.Errorf("pool get failed: %v", c.Err()) |
| } |
| c.Close() |
| wg.Done() |
| }() |
| } |
| wg.Wait() |
| if d.dialed != count*2 { |
| t.Errorf("Expected %d dials, got %d", count*2, d.dialed) |
| } |
| } |
| |
| func BenchmarkPoolGet(b *testing.B) { |
| b.StopTimer() |
| p := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2} |
| c := p.Get() |
| if err := c.Err(); err != nil { |
| b.Fatal(err) |
| } |
| c.Close() |
| defer p.Close() |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| c = p.Get() |
| c.Close() |
| } |
| } |
| |
| func BenchmarkPoolGetErr(b *testing.B) { |
| b.StopTimer() |
| p := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2} |
| c := p.Get() |
| if err := c.Err(); err != nil { |
| b.Fatal(err) |
| } |
| c.Close() |
| defer p.Close() |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| c = p.Get() |
| if err := c.Err(); err != nil { |
| b.Fatal(err) |
| } |
| c.Close() |
| } |
| } |
| |
| func BenchmarkPoolGetPing(b *testing.B) { |
| b.StopTimer() |
| p := redis.Pool{Dial: func() (redis.Conn, error) { return redis.DialDefaultServer() }, MaxIdle: 2} |
| c := p.Get() |
| if err := c.Err(); err != nil { |
| b.Fatal(err) |
| } |
| c.Close() |
| defer p.Close() |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| c = p.Get() |
| if _, err := c.Do("PING"); err != nil { |
| b.Fatal(err) |
| } |
| c.Close() |
| } |
| } |
| |
| func TestWaitPoolGetContext(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| c, err := p.GetContext(context.Background()) |
| if err != nil { |
| t.Fatalf("GetContext returned %v", err) |
| } |
| defer c.Close() |
| } |
| |
| func TestWaitPoolGetContextIssue520(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| ctx1, cancel1 := context.WithTimeout(context.Background(), 1*time.Nanosecond) |
| defer cancel1() |
| c, err := p.GetContext(ctx1) |
| if err != context.DeadlineExceeded { |
| t.Fatalf("GetContext returned %v", err) |
| } |
| defer c.Close() |
| |
| ctx2, cancel2 := context.WithCancel(context.Background()) |
| defer cancel2() |
| c2, err := p.GetContext(ctx2) |
| if err != nil { |
| t.Fatalf("Get context returned %v", err) |
| } |
| defer c2.Close() |
| } |
| |
| func TestWaitPoolGetContextWithDialContext(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| DialContext: d.dialContext, |
| Wait: true, |
| } |
| defer p.Close() |
| c, err := p.GetContext(context.Background()) |
| if err != nil { |
| t.Fatalf("GetContext returned %v", err) |
| } |
| defer c.Close() |
| } |
| |
| func TestPoolGetContext_DialContext(t *testing.T) { |
| var isPassed bool |
| f := func(ctx context.Context, network, addr string) (net.Conn, error) { |
| isPassed = true |
| return &testConn{}, nil |
| } |
| |
| p := &redis.Pool{ |
| DialContext: func(ctx context.Context) (redis.Conn, error) { |
| return redis.DialContext(ctx, "", "", redis.DialContextFunc(f)) |
| }, |
| } |
| defer p.Close() |
| |
| if _, err := p.GetContext(context.Background()); err != nil { |
| t.Fatalf("GetContext returned %v", err) |
| } |
| |
| if !isPassed { |
| t.Fatal("DialContextFunc not passed") |
| } |
| } |
| |
| func TestPoolGetContext_DialContext_CanceledContext(t *testing.T) { |
| addr, err := redis.DefaultServerAddr() |
| if err != nil { |
| t.Fatalf("redis.DefaultServerAddr returned %v", err) |
| } |
| |
| p := &redis.Pool{ |
| DialContext: func(ctx context.Context) (redis.Conn, error) { return redis.DialContext(ctx, "tcp", addr) }, |
| } |
| defer p.Close() |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| cancel() |
| |
| if _, err := p.GetContext(ctx); err == nil { |
| t.Fatalf("GetContext returned nil, expect error") |
| } |
| } |
| |
| func TestWaitPoolGetAfterClose(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| p.Close() |
| _, err := p.GetContext(context.Background()) |
| if err == nil { |
| t.Fatal("expected error") |
| } |
| } |
| |
| func TestWaitPoolGetCanceledContext(t *testing.T) { |
| t.Run("without vacant connection in the pool", func(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| ctx, cancel := context.WithCancel(context.Background()) |
| cancel() |
| c := p.Get() |
| defer c.Close() |
| _, err := p.GetContext(ctx) |
| if err != context.Canceled { |
| t.Fatalf("got error %v, want %v", err, context.Canceled) |
| } |
| }) |
| t.Run("with vacant connection in the pool", func(t *testing.T) { |
| d := poolDialer{t: t} |
| p := &redis.Pool{ |
| MaxIdle: 1, |
| MaxActive: 1, |
| Dial: d.dial, |
| Wait: true, |
| } |
| defer p.Close() |
| ctx, cancel := context.WithCancel(context.Background()) |
| cancel() |
| _, err := p.GetContext(ctx) |
| if err != context.Canceled { |
| t.Fatalf("got error %v, want %v", err, context.Canceled) |
| } |
| }) |
| } |