tx.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package redis
  2. import (
  3. "context"
  4. "github.com/go-redis/redis/v8/internal/pool"
  5. "github.com/go-redis/redis/v8/internal/proto"
  6. )
  7. // TxFailedErr transaction redis failed.
  8. const TxFailedErr = proto.RedisError("redis: transaction failed")
  9. // Tx implements Redis transactions as described in
  10. // http://redis.io/topics/transactions. It's NOT safe for concurrent use
  11. // by multiple goroutines, because Exec resets list of watched keys.
  12. //
  13. // If you don't need WATCH, use Pipeline instead.
  14. type Tx struct {
  15. baseClient
  16. cmdable
  17. statefulCmdable
  18. hooks
  19. ctx context.Context
  20. }
  21. func (c *Client) newTx(ctx context.Context) *Tx {
  22. tx := Tx{
  23. baseClient: baseClient{
  24. opt: c.opt,
  25. connPool: pool.NewStickyConnPool(c.connPool),
  26. },
  27. hooks: c.hooks.clone(),
  28. ctx: ctx,
  29. }
  30. tx.init()
  31. return &tx
  32. }
  33. func (c *Tx) init() {
  34. c.cmdable = c.Process
  35. c.statefulCmdable = c.Process
  36. }
  37. func (c *Tx) Context() context.Context {
  38. return c.ctx
  39. }
  40. func (c *Tx) WithContext(ctx context.Context) *Tx {
  41. if ctx == nil {
  42. panic("nil context")
  43. }
  44. clone := *c
  45. clone.init()
  46. clone.hooks.lock()
  47. clone.ctx = ctx
  48. return &clone
  49. }
  50. func (c *Tx) Process(ctx context.Context, cmd Cmder) error {
  51. return c.hooks.process(ctx, cmd, c.baseClient.process)
  52. }
  53. // Watch prepares a transaction and marks the keys to be watched
  54. // for conditional execution if there are any keys.
  55. //
  56. // The transaction is automatically closed when fn exits.
  57. func (c *Client) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error {
  58. tx := c.newTx(ctx)
  59. defer tx.Close(ctx)
  60. if len(keys) > 0 {
  61. if err := tx.Watch(ctx, keys...).Err(); err != nil {
  62. return err
  63. }
  64. }
  65. return fn(tx)
  66. }
  67. // Close closes the transaction, releasing any open resources.
  68. func (c *Tx) Close(ctx context.Context) error {
  69. _ = c.Unwatch(ctx).Err()
  70. return c.baseClient.Close()
  71. }
  72. // Watch marks the keys to be watched for conditional execution
  73. // of a transaction.
  74. func (c *Tx) Watch(ctx context.Context, keys ...string) *StatusCmd {
  75. args := make([]interface{}, 1+len(keys))
  76. args[0] = "watch"
  77. for i, key := range keys {
  78. args[1+i] = key
  79. }
  80. cmd := NewStatusCmd(ctx, args...)
  81. _ = c.Process(ctx, cmd)
  82. return cmd
  83. }
  84. // Unwatch flushes all the previously watched keys for a transaction.
  85. func (c *Tx) Unwatch(ctx context.Context, keys ...string) *StatusCmd {
  86. args := make([]interface{}, 1+len(keys))
  87. args[0] = "unwatch"
  88. for i, key := range keys {
  89. args[1+i] = key
  90. }
  91. cmd := NewStatusCmd(ctx, args...)
  92. _ = c.Process(ctx, cmd)
  93. return cmd
  94. }
  95. // Pipeline creates a pipeline. Usually it is more convenient to use Pipelined.
  96. func (c *Tx) Pipeline() Pipeliner {
  97. pipe := Pipeline{
  98. ctx: c.ctx,
  99. exec: func(ctx context.Context, cmds []Cmder) error {
  100. return c.hooks.processPipeline(ctx, cmds, c.baseClient.processPipeline)
  101. },
  102. }
  103. pipe.init()
  104. return &pipe
  105. }
  106. // Pipelined executes commands queued in the fn outside of the transaction.
  107. // Use TxPipelined if you need transactional behavior.
  108. func (c *Tx) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
  109. return c.Pipeline().Pipelined(ctx, fn)
  110. }
  111. // TxPipelined executes commands queued in the fn in the transaction.
  112. //
  113. // When using WATCH, EXEC will execute commands only if the watched keys
  114. // were not modified, allowing for a check-and-set mechanism.
  115. //
  116. // Exec always returns list of commands. If transaction fails
  117. // TxFailedErr is returned. Otherwise Exec returns an error of the first
  118. // failed command or nil.
  119. func (c *Tx) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
  120. return c.TxPipeline().Pipelined(ctx, fn)
  121. }
  122. // TxPipeline creates a pipeline. Usually it is more convenient to use TxPipelined.
  123. func (c *Tx) TxPipeline() Pipeliner {
  124. pipe := Pipeline{
  125. ctx: c.ctx,
  126. exec: func(ctx context.Context, cmds []Cmder) error {
  127. return c.hooks.processTxPipeline(ctx, cmds, c.baseClient.processTxPipeline)
  128. },
  129. }
  130. pipe.init()
  131. return &pipe
  132. }