|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684 |
- // 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 (
- "errors"
- "io"
- "reflect"
- "sync"
- "testing"
- "time"
-
- "github.com/garyburd/redigo/redis"
- )
-
- 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) check(message string, p *redis.Pool, dialed, open int) {
- d.mu.Lock()
- 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)
- }
- if active := p.ActiveCount(); active != open {
- d.t.Errorf("%s: active=%d, want %d", message, active, open)
- }
- d.mu.Unlock()
- }
-
- 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()
- c1.Do("PING")
- c2 := p.Get()
- c2.Do("PING")
- c1.Close()
- c2.Close()
- }
-
- d.check("before close", p, 2, 2)
- p.Close()
- d.check("after close", p, 2, 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()
- c1.Do("PING")
- c2 := p.Get()
- c2.Do("PING")
- c3 := p.Get()
- c3.Do("PING")
- c1.Close()
- c2.Close()
- c3.Close()
- }
- d.check("before close", p, 12, 2)
- p.Close()
- d.check("after close", p, 12, 0)
- }
-
- func TestPoolError(t *testing.T) {
- d := poolDialer{t: t}
- p := &redis.Pool{
- MaxIdle: 2,
- Dial: d.dial,
- }
- defer p.Close()
-
- c := p.Get()
- c.Do("ERR", io.EOF)
- if c.Err() == nil {
- t.Errorf("expected c.Err() != nil")
- }
- c.Close()
-
- c = p.Get()
- c.Do("ERR", io.EOF)
- c.Close()
-
- d.check(".", p, 2, 0)
- }
-
- func TestPoolClose(t *testing.T) {
- d := poolDialer{t: t}
- p := &redis.Pool{
- MaxIdle: 2,
- Dial: d.dial,
- }
- defer p.Close()
-
- c1 := p.Get()
- c1.Do("PING")
- c2 := p.Get()
- c2.Do("PING")
- c3 := p.Get()
- c3.Do("PING")
-
- 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)
-
- 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)
-
- c1 = p.Get()
- if _, err := c1.Do("PING"); err == nil {
- t.Errorf("expected error after pool closed")
- }
- }
-
- func TestPoolTimeout(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()
- c.Do("PING")
- c.Close()
-
- d.check("1", p, 1, 1)
-
- now = now.Add(p.IdleTimeout)
-
- c = p.Get()
- c.Do("PING")
- c.Close()
-
- d.check("2", p, 2, 1)
- }
-
- func TestPoolConcurrenSendReceive(t *testing.T) {
- p := &redis.Pool{
- Dial: redis.DialDefaultServer,
- }
- defer p.Close()
-
- c := p.Get()
- done := make(chan error, 1)
- go func() {
- _, err := c.Receive()
- done <- err
- }()
- 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()
- c.Do("PING")
- c.Close()
- }
- d.check("1", p, 10, 1)
- }
-
- 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()
- c1.Do("PING")
- c2 := p.Get()
- c2.Do("PING")
-
- d.check("1", p, 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)
- c2.Close()
- d.check("3", p, 2, 2)
-
- 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)
- }
-
- 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()
- c.Send("MONITOR")
- c.Close()
-
- d.check("", p, 1, 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()
- 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()
- 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()
- c.Do("WATCH", "key")
- c.Do("PING")
- 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()
- c.Do("WATCH", "key")
- c.Do("UNWATCH")
- c.Do("PING")
- 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()
- c.Do("WATCH", "key")
- c.Do("MULTI")
- c.Do("PING")
- 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()
- c.Do("WATCH", "key")
- c.Do("MULTI")
- c.Do("DISCARD")
- c.Do("PING")
- 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()
- c.Do("WATCH", "key")
- c.Do("MULTI")
- c.Do("EXEC")
- c.Do("PING")
- 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, 10)
- for i := 0; i < cap(errs); i++ {
- go func() {
- c := p.Get()
- _, err := c.Do(cmd, args...)
- errs <- err
- c.Close()
- }()
- }
-
- // Wait for goroutines to block.
- time.Sleep(time.Second / 4)
-
- 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()
- errs := startGoroutines(p, "PING")
- d.check("before close", p, 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.check("done", p, 1, 1)
- }
-
- 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)
- }
- errs := startGoroutines(p, "PING")
- d.check("before close", p, 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.check("done", p, 1, 0)
- }
-
- 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()
- errs := startGoroutines(p, "ERR", testErr)
- d.check("before close", p, 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.check("done", p, cap(errs), 0)
- }
-
- 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()
- errs := startGoroutines(p, "ERR", testErr)
- d.check("before close", p, 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 erors, got %d", cap(errs)-1, errCount)
- }
- d.check("done", p, cap(errs), 0)
- }
-
- // 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: 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: 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: 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()
- }
- }
|