From 39c42d4389278b3a8c68f12f4948a9ef346ac830 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Sun, 29 Sep 2013 12:41:04 +0300 Subject: [PATCH] Add ErrTxFailed. Small tweaks. --- v2/command.go | 6 ++++++ v2/example_test.go | 3 +-- v2/multi.go | 33 ++++++++++++++++++++++++--------- v2/parser.go | 5 ++++- v2/pipeline.go | 11 +++++------ v2/redis.go | 2 +- v2/redis_test.go | 20 +++++++++++--------- 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/v2/command.go b/v2/command.go index 8ac0ee26c..cf434afd1 100644 --- a/v2/command.go +++ b/v2/command.go @@ -19,6 +19,12 @@ type Cmder interface { Err() error } +func setCmdsErr(cmds []Cmder, e error) { + for _, cmd := range cmds { + cmd.setErr(e) + } +} + //------------------------------------------------------------------------------ type baseCmd struct { diff --git a/v2/example_test.go b/v2/example_test.go index 0aec57000..dd6273b78 100644 --- a/v2/example_test.go +++ b/v2/example_test.go @@ -108,8 +108,7 @@ func ExampleMulti() { for { cmds, err := incr(tx) - if err == redis.Nil { - // Transaction failed. Repeat. + if err == redis.TxFailedErr { continue } else if err != nil { panic(err) diff --git a/v2/multi.go b/v2/multi.go index d61ceb6f5..7c539d87a 100644 --- a/v2/multi.go +++ b/v2/multi.go @@ -50,6 +50,9 @@ func (c *Multi) Discard() error { return nil } +// Exec always returns list of commands. If transaction fails +// TxFailedErr is returned. Otherwise Exec returns error of the first +// failed command or nil. func (c *Multi) Exec(f func()) ([]Cmder, error) { c.cmds = []Cmder{NewStatusCmd("MULTI")} f() @@ -64,35 +67,38 @@ func (c *Multi) Exec(f func()) ([]Cmder, error) { cn, err := c.conn() if err != nil { - return nil, err + setCmdsErr(cmds[1:len(cmds)-1], err) + return cmds[1 : len(cmds)-1], err } // Synchronize writes and reads to the connection using mutex. - err = c.execCmds(cmds, cn) + err = c.execCmds(cn, cmds) if err != nil { - c.removeConn(cn) - return nil, err + c.freeConn(cn, err) + return cmds[1 : len(cmds)-1], err } c.putConn(cn) return cmds[1 : len(cmds)-1], nil } -func (c *Multi) execCmds(cmds []Cmder, cn *conn) error { +func (c *Multi) execCmds(cn *conn, cmds []Cmder) error { err := c.writeCmd(cn, cmds...) if err != nil { + setCmdsErr(cmds[1:len(cmds)-1], err) return err } statusCmd := NewStatusCmd() - // Omit last cmduest (EXEC). + // Omit last command (EXEC). cmdsLen := len(cmds) - 1 // Parse queued replies. for i := 0; i < cmdsLen; i++ { _, err = statusCmd.parseReply(cn.rd) if err != nil { + setCmdsErr(cmds[1:len(cmds)-1], err) return err } } @@ -100,15 +106,21 @@ func (c *Multi) execCmds(cmds []Cmder, cn *conn) error { // Parse number of replies. line, err := readLine(cn.rd) if err != nil { + setCmdsErr(cmds[1:len(cmds)-1], err) return err } if line[0] != '*' { - return fmt.Errorf("redis: expected '*', but got line %q", line) + err := fmt.Errorf("redis: expected '*', but got line %q", line) + setCmdsErr(cmds[1:len(cmds)-1], err) + return err } if len(line) == 3 && line[1] == '-' && line[2] == '1' { - return Nil + setCmdsErr(cmds[1:len(cmds)-1], TxFailedErr) + return TxFailedErr } + var firstCmdErr error + // Parse replies. // Loop starts from 1 to omit first cmduest (MULTI). for i := 1; i < cmdsLen; i++ { @@ -116,10 +128,13 @@ func (c *Multi) execCmds(cmds []Cmder, cn *conn) error { val, err := cmd.parseReply(cn.rd) if err != nil { cmd.setErr(err) + if firstCmdErr == nil { + firstCmdErr = err + } } else { cmd.setVal(val) } } - return nil + return firstCmdErr } diff --git a/v2/parser.go b/v2/parser.go index 43095d258..4d20dd679 100644 --- a/v2/parser.go +++ b/v2/parser.go @@ -18,9 +18,12 @@ const ( stringFloatMap ) -// Represents Redis nil reply. +// Redis nil reply. var Nil = errors.New("(nil)") +// Redis transaction failed. +var TxFailedErr = errors.New("redis: transaction failed") + var ( errReaderTooSmall = errors.New("redis: reader is too small") errValNotSet = errors.New("redis: value is not set") diff --git a/v2/pipeline.go b/v2/pipeline.go index 6922b3367..a491b7d8a 100644 --- a/v2/pipeline.go +++ b/v2/pipeline.go @@ -35,7 +35,7 @@ func (c *Pipeline) Discard() error { return nil } -// Always returns list of commands and error of the first failed +// Exec always returns list of commands and error of the first failed // command if any. func (c *Pipeline) Exec() ([]Cmder, error) { cmds := c.cmds @@ -47,10 +47,11 @@ func (c *Pipeline) Exec() ([]Cmder, error) { cn, err := c.conn() if err != nil { + setCmdsErr(cmds, err) return cmds, err } - if err := c.execCmds(cmds, cn); err != nil { + if err := c.execCmds(cn, cmds); err != nil { c.freeConn(cn, err) return cmds, err } @@ -59,12 +60,10 @@ func (c *Pipeline) Exec() ([]Cmder, error) { return cmds, nil } -func (c *Pipeline) execCmds(cmds []Cmder, cn *conn) error { +func (c *Pipeline) execCmds(cn *conn, cmds []Cmder) error { err := c.writeCmd(cn, cmds...) if err != nil { - for _, cmd := range cmds { - cmd.setErr(err) - } + setCmdsErr(cmds, err) return err } diff --git a/v2/redis.go b/v2/redis.go index 10a5eb7b3..f56c1849a 100644 --- a/v2/redis.go +++ b/v2/redis.go @@ -73,7 +73,7 @@ func (c *baseClient) init(cn *conn, password string, db int64) error { } func (c *baseClient) freeConn(cn *conn, err error) { - if err == Nil { + if err == Nil || err == TxFailedErr { c.putConn(cn) } else { c.removeConn(cn) diff --git a/v2/redis_test.go b/v2/redis_test.go index a25709b7f..ed0bf34b3 100644 --- a/v2/redis_test.go +++ b/v2/redis_test.go @@ -2643,13 +2643,9 @@ func (t *RedisTest) transactionalIncr(c *C) ([]redis.Cmder, error) { v, err := strconv.ParseInt(get.Val(), 10, 64) c.Assert(err, IsNil) - cmds, err := multi.Exec(func() { + return multi.Exec(func() { multi.Set("key", strconv.FormatInt(v+1, 10)) }) - if err == redis.Nil { - return t.transactionalIncr(c) - } - return cmds, err } func (t *RedisTest) TestWatchUnwatch(c *C) { @@ -2661,10 +2657,16 @@ func (t *RedisTest) TestWatchUnwatch(c *C) { for i := 0; i < 1000; i++ { wg.Add(1) go func() { - cmds, err := t.transactionalIncr(c) - c.Assert(cmds, HasLen, 1) - c.Assert(err, IsNil) - c.Assert(cmds[0].Err(), IsNil) + for { + cmds, err := t.transactionalIncr(c) + if err == redis.TxFailedErr { + continue + } + c.Assert(err, IsNil) + c.Assert(cmds, HasLen, 1) + c.Assert(cmds[0].Err(), IsNil) + break + } wg.Done() }() }