require 'em-hiredis' class EM::Hiredis::Client class Transaction include EM::Deferrable def initialize(redis) @redis = redis end def prep(&block) @prep = block end def multiexec(&block) # Raise if called more than once @multiexec = block end def run(retries = 3) df = EM::DefaultDeferrable.new @prep.call(@redis, df) df.callback { |prep_results| @redis.multi @multiexec.call(@redis, *prep_results) df2 = @redis.exec { |results| if results.nil? if retries > 0 EM.next_tick { run(retries - 1) } else # Transaction failed # TODO: Clear the watches self.fail() end else self.succeed(*results) end } } df.errback { # Decided not to excecute the multi-exec # TODO: Clear the watches } end end def atomically(retries = 3) transaction = Transaction.new(self) yield transaction transaction.run(retries) return transaction end end EM.run { redis = EM::Hiredis.connect df = redis.atomically do |transaction| transaction.prep { |r, df| r.watch('foo') EM.add_timer(3) { r.get('foo') { |value| df.succeed(value) } } } transaction.multiexec { |r, foo| p "setting bar to #{foo}" r.set('foo', rand) r.set('bar', foo) } end df.callback { |set1, set2| p [:transaction_result, set1, set2] } }