Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

dealing with some edge cases and documenting the new WATCH functionality #34

Merged
merged 1 commit into from

2 participants

@dgvncsz0f

This patch deals with the following scenarios, which would ended up
cleaning the inTransaction prematurely:

  1. UNWATCH after MULTI

    t = multi
    t.unwatch

  2. WATCH, MULTI (with keys)

    t = watch
    t.multi("foobar")
    t.unwatch

3 . MULTI + WATCH

t = multi
t.watch # this is invalid, but
will change the _unwatch_cc
to a function that would clear
the inTransaction state

I've also dropped a couple more tests, notably testing WATCH with ConnectionPool.

@dgvncsz0f dgvncsz0f dealing with some edge cases and documenting the new WATCH functionality
This patch deals with the following scenarios, which would ended up
cleaning the inTransaction prematurely:

1. UNWATCH after MULTI

  t = multi
  t.unwatch

2. WATCH, MULTI (with keys)

  t = watch
  t.multi("foobar")
  t.unwatch

3 . MULTI + WATCH

  t = multi
  t.watch   # this is invalid, but
              will change the _unwatch_cc
              to a function that would clear
              the inTransaction state

I've also dropped a couple more tests, notably testing WATCH with ConnectionPool.
34b3adb
@dgvncsz0f

Probably these cases will never show up in production code, but better fix them before ruining someones day :-)

@gleicon gleicon merged commit 20b55ff into from
@gleicon
Collaborator

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 21, 2012
  1. @dgvncsz0f

    dealing with some edge cases and documenting the new WATCH functionality

    dgvncsz0f authored
    This patch deals with the following scenarios, which would ended up
    cleaning the inTransaction prematurely:
    
    1. UNWATCH after MULTI
    
      t = multi
      t.unwatch
    
    2. WATCH, MULTI (with keys)
    
      t = watch
      t.multi("foobar")
      t.unwatch
    
    3 . MULTI + WATCH
    
      t = multi
      t.watch   # this is invalid, but
                  will change the _unwatch_cc
                  to a function that would clear
                  the inTransaction state
    
    I've also dropped a couple more tests, notably testing WATCH with ConnectionPool.
This page is out of date. Refresh to see the latest.
Showing with 117 additions and 4 deletions.
  1. +54 −0 README.md
  2. +58 −0 tests/test_transactions.py
  3. +5 −4 txredisapi.py
View
54 README.md
@@ -422,6 +422,7 @@ But they work pretty good on normal or lazy connections, and connection pools.
NOTE: redis uses the following methods for transactions:
+- WATCH: synchronization
- MULTI: start the transaction
- EXEC: commit the transaction
- DISCARD: you got it.
@@ -477,6 +478,55 @@ Example:
main().addCallback(lambda ign: reactor.stop())
reactor.run()
+A "COUNTER" example, using WATCH/MULTI:
+
+ #!/usr/bin/env python
+ # coding: utf-8
+
+ import txredisapi as redis
+
+ from twisted.internet import defer
+ from twisted.internet import reactor
+
+
+ @defer.inlineCallbacks
+ def main():
+ rc = yield redis.ConnectionPool()
+
+ # Reset keys
+ yield rc.set("a1", 0)
+
+ # Synchronize and start transaction
+ t = yield rc.watch("a1")
+
+ # Load previous value
+ a1 = yield t.get("a1")
+
+ # start the transactional pipeline
+ yield t.multi()
+
+ # modify and retrieve the new a1 value
+ yield t.set("a1", a1 + 1)
+ yield t.get("a1")
+
+ print "simulating concurrency, this will abort the transaction"
+ yield rc.set("a1", 2)
+
+ try:
+ r = yield t.commit()
+ print "commit=", repr(r)
+ except redis.WatchError, e:
+ a1 = yield rc.get("a1")
+ print "transaction has failed."
+ print "current a1 value: ", a1
+
+ yield rc.disconnect()
+
+
+ if __name__ == "__main__":
+ main().addCallback(lambda ign: reactor.stop())
+ reactor.run()
+
Calling ``commit`` will cause it to return a list with the return of all
commands executed in the transaction. ``discard``, on the other hand, will
@@ -544,3 +594,7 @@ Thanks to (in no particular order):
- Christoph Tavan (@ctavan)
- Idea and test case for nested multi bulk replies, minor command enhancements.
+
+- dgvncsz0f
+
+ - WATCH/UNWATCH commands
View
58 tests/test_transactions.py
@@ -68,3 +68,61 @@ def testRedisWithWatchAndMulti(self):
self.assertFalse(tx.inTransaction)
yield rapi.disconnect()
+
+ # some sort of probabilistic test
+ @defer.inlineCallbacks
+ def testWatchAndPools_1(self):
+ rapi = yield txredisapi.ConnectionPool(redis_host, redis_port, poolsize=2, reconnect=False)
+ tx1 = yield rapi.watch("foobar")
+ tx2 = yield tx1.watch("foobaz")
+ self.assertTrue(id(tx1) == id(tx2))
+ yield rapi.disconnect()
+
+ # some sort of probabilistic test
+ @defer.inlineCallbacks
+ def testWatchAndPools_2(self):
+ rapi = yield txredisapi.ConnectionPool(redis_host, redis_port, poolsize=2, reconnect=False)
+ tx1 = yield rapi.watch("foobar")
+ tx2 = yield rapi.watch("foobaz")
+ self.assertTrue(id(tx1) != id(tx2))
+ yield rapi.disconnect()
+
+ @defer.inlineCallbacks
+ def testWatchEdgeCase_1(self):
+ rapi = yield txredisapi.Connection(redis_host, redis_port)
+
+ tx = yield rapi.multi("foobar")
+ yield tx.unwatch()
+ self.assertTrue(tx.inTransaction)
+ yield tx.discard()
+ self.assertFalse(tx.inTransaction)
+
+ yield rapi.disconnect()
+
+ @defer.inlineCallbacks
+ def testWatchEdgeCase_2(self):
+ rapi = yield txredisapi.Connection(redis_host, redis_port)
+
+ tx = yield rapi.multi()
+ try:
+ yield tx.watch("foobar")
+ except txredisapi.ResponseError:
+ pass
+ yield tx.unwatch()
+ self.assertTrue(tx.inTransaction)
+ yield tx.discard()
+ self.assertFalse(tx.inTransaction)
+ yield rapi.disconnect()
+
+ @defer.inlineCallbacks
+ def testWatchEdgeCase_3(self):
+ rapi = yield txredisapi.Connection(redis_host, redis_port)
+
+ tx = yield rapi.watch("foobar")
+ tx = yield tx.multi("foobaz")
+ yield tx.unwatch()
+ self.assertTrue(tx.inTransaction)
+ yield tx.discard()
+ self.assertFalse(tx.inTransaction)
+
+ yield rapi.disconnect()
View
9 txredisapi.py
@@ -1197,11 +1197,12 @@ def sort(self, key, start=None, end=None, by=None, get=None,
return self.execute_command("SORT", *pieces)
def _clear_txstate(self):
- self.inTransaction = self.transactions != 0
+ self.inTransaction = False
def watch(self, keys):
- self.inTransaction = True
- self.unwatch_cc = self._clear_txstate
+ if not self.inTransaction:
+ self.inTransaction = True
+ self.unwatch_cc = self._clear_txstate
if isinstance(keys, (str, unicode)):
keys = [keys]
d = self.execute_command("WATCH", *keys).addCallback(self._tx_started)
@@ -1217,12 +1218,12 @@ def unwatch(self):
# the transaction. At the end, either exec() or discard()
# must be executed.
def multi(self, keys=None):
+ self.inTransaction = True
self.unwatch_cc = lambda: ()
if keys is not None:
d = self.watch(keys)
d.addCallback(lambda _: self.execute_command("MULTI"))
else:
- self.inTransaction = True
d = self.execute_command("MULTI")
d.addCallback(self._tx_started)
return d
Something went wrong with that request. Please try again.