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()
}()
}