em-hiredis-0.2.1/0000755000175000017500000000000012135210520013711 5ustar avtobiffavtobiffem-hiredis-0.2.1/.rspec0000644000175000017500000000001112135210520015016 0ustar avtobiffavtobiff--colour em-hiredis-0.2.1/CHANGELOG.md0000644000175000017500000000160012135210520015517 0ustar avtobiffavtobiff# Changelog ## 0.2.1 (2013-04-22) [NEW] Support for connecting to redis on a unix socket. [CHANGED] Redis error reply message now used as message for RedisError. ## 0.2.0 (2013-04-05) [NEW] Richer interface for pubsub (accessible via `client.pubsub`). See example in `examples/pubsub.rb`. [NEW] Better failure handling: * Clients now emit the following events: connected, reconnected, disconnected, reconnect_failed (passes the number of consecutive failures) * Client is considered failed after 4 consecutive failures * Fails all queued commands when client failed * Can now reconfiure and reconnect an exising client * Reconnect timeout can be configured (defaults to 0.5s) [NEW] Added `EM::Hiredis::Lock` and `EM::Hiredis::PersistentLock` [CHANGE] When a redis command fails, the errback is now always passed an `EM::Hiredis::Error`. [FIX] Fixed info parsing for Redis 2.6 em-hiredis-0.2.1/README.md0000644000175000017500000001162212135210520015172 0ustar avtobiffavtobiff# em-hiredis ## What A Redis client for EventMachine designed to be fast and simple. ## Why I wanted a client which: * used the C hiredis library to parse redis replies * had a convenient API for pubsub * exposed the state of the underlying redis connections so that custom failover logic could be written outside the library Also, is no longer maintained. ## Getting started Connect to redis: require 'em-hiredis' redis = EM::Hiredis.connect Or, connect to redis with a redis URL (for a different host, port, password, DB) redis = EM::Hiredis.connect("redis://:secretpassword@example.com:9000/4") Commands may be sent immediately. Any commands sent while connecting to redis will be queued. All redis commands are available without any remapping of names, and return a deferrable redis.set('foo', 'bar').callback { redis.get('foo').callback { |value| p [:returned, value] } } If redis replies with an error (for example you called a hash operation against a set or the database is full), or if the redis connection disconnects before the command returns, the deferrable will fail. redis.sadd('aset', 'member').callback { response_deferrable = redis.hget('aset', 'member') response_deferrable.errback { |e| p e # => # p e.redis_error # => # } } As a shortcut, if you're only interested in binding to the success case you can simply provide a block to any command redis.get('foo') { |value| p [:returned, value] } ## Understanding the state of the connection When a connection to redis server closes, a `:disconnected` event will be emitted and the connection will be immediately reconnect. If the connection reconnects a `:connected` event will be emitted. If a reconnect fails to connect, a `:reconnect_failed` event will be emitted (rather than `:disconnected`) with the number of consecutive failures, and the connection will be retried after a timeout (defaults to 0.5s, can be set via `EM::Hiredis.reconnect_timeout=`). If a client fails to reconnect 4 consecutive times then a `:failed` event will be emitted, and any queued redis commands will be failed (otherwise they would be queued forever waiting for a reconnect). ## Pubsub The way pubsub works in redis is that once a subscribe has been made on a connection, it's only possible to send (p)subscribe or (p)unsubscribe commands on that connection. The connection will also receive messages which are not replies to commands. The regular `EM::Hiredis::Client` no longer understands pubsub messages - this logic has been moved to `EM::Hiredis::PubsubClient`. The pubsub client can either be initialized directly (see code) or you can get one connected to the same redis server by calling `#pubsub` on an existing `EM::Hiredis::Client` instance. Pubsub can either be used in em-hiredis in a close-to-the-metal fashion, or you can use the convenience functionality for binding blocks to subscriptions if you prefer (recommended). ### Close to the metal Basically just bind to `:message` and `:pmessage` events: # Create two connections, one will be used for subscribing redis = EM::Hiredis.connect pubsub = redis.pubsub pubsub.subscribe('bar.0').callback { puts "Subscribed" } pubsub.psubscribe('bar.*') pubsub.on(:message) { |channel, message| p [:message, channel, message] } pubsub.on(:pmessage) { |key, channel, message| p [:pmessage, key, channel, message] } EM.add_periodic_timer(1) { redis.publish("bar.#{rand(2)}", "hello").errback { |e| p [:publisherror, e] } } ### Richer interface to pubsub If you pass a block to `subscribe` or `psubscribe`, the passed block will be called whenever a message arrives on that subscription: redis = EM::Hiredis.connect puts "Subscribing" redis.pubsub.subscribe("foo") { |msg| p [:sub1, msg] } redis.pubsub.psubscribe("f*") { |msg| p [:sub2, msg] } EM.add_periodic_timer(1) { redis.publish("foo", "Hello") } EM.add_timer(5) { puts "Unsubscribing sub1" redis.pubsub.unsubscribe("foo") } It's possible to subscribe to the same channel multiple time and just unsubscribe a single callback using `unsubscribe_proc` or `punsubscribe_proc`. ## Developing Hacking on em-hiredis is pretty simple, make sure you have Bundler installed: gem install bundler bundle In order to run the tests you need to have a local redis server running on port 6379. Run all the tests: # WARNING: The tests call flushdb on db 9 - this clears all keys! bundle exec rake To run an individual test: bundle exec rspec spec/redis_commands_spec.rb Many thanks to the em-redis gem for getting this gem bootstrapped with some tests. em-hiredis-0.2.1/em-hiredis.gemspec0000644000175000017500000000171112135210520017304 0ustar avtobiffavtobiff# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "em-hiredis/version" Gem::Specification.new do |s| s.name = "em-hiredis" s.version = EventMachine::Hiredis::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Martyn Loughran"] s.email = ["me@mloughran.com"] s.homepage = "http://github.com/mloughran/em-hiredis" s.summary = %q{Eventmachine redis client} s.description = %q{Eventmachine redis client using hiredis native parser} s.add_dependency 'hiredis', '~> 0.4.0' s.add_development_dependency 'em-spec', '~> 0.2.5' s.add_development_dependency 'rspec', '~> 2.6.0' s.add_development_dependency 'rake' s.rubyforge_project = "em-hiredis" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] end em-hiredis-0.2.1/lib/0000755000175000017500000000000012135210520014457 5ustar avtobiffavtobiffem-hiredis-0.2.1/lib/em-hiredis.rb0000644000175000017500000000326112135210520017034 0ustar avtobiffavtobiffrequire 'eventmachine' module EventMachine module Hiredis # All em-hiredis errors should descend from EM::Hiredis::Error class Error < RuntimeError; end # An error reply from Redis. The actual error retuned by ::Hiredis will be # wrapped in the redis_error accessor. class RedisError < Error attr_accessor :redis_error end class << self attr_accessor :reconnect_timeout end self.reconnect_timeout = 0.5 def self.setup(uri = nil) uri = uri || ENV["REDIS_URL"] || "redis://127.0.0.1:6379/0" client = Client.new client.configure(uri) client end # Connects to redis and returns a client instance # # Will connect in preference order to the provided uri, the REDIS_URL # environment variable, or localhost:6379 # # TCP connections are supported via redis://:password@host:port/db (only # host and port components are required) # # Unix socket uris are supported, e.g. unix:///tmp/redis.sock, however # it's not possible to set the db or password - use initialize instead in # this case def self.connect(uri = nil) client = setup(uri) client.connect client end def self.logger=(logger) @@logger = logger end def self.logger @@logger ||= begin require 'logger' log = Logger.new(STDOUT) log.level = Logger::WARN log end end autoload :Lock, 'em-hiredis/lock' autoload :PersistentLock, 'em-hiredis/persistent_lock' end end require 'em-hiredis/event_emitter' require 'em-hiredis/connection' require 'em-hiredis/base_client' require 'em-hiredis/client' require 'em-hiredis/pubsub_client' em-hiredis-0.2.1/lib/em-hiredis/0000755000175000017500000000000012135210520016505 5ustar avtobiffavtobiffem-hiredis-0.2.1/lib/em-hiredis/persistent_lock.rb0000644000175000017500000000424712135210520022251 0ustar avtobiffavtobiffmodule EM::Hiredis # A lock that automatically re-acquires a lock before it loses it # # The lock is configured with the following two parameters # # :lock_timeout - Specifies how long each lock is acquired for. Setting # this low means that locks need to be re-acquired very often, but a long # timout means that a process that fails without cleaning up after itself # (i.e. without releasing it's underlying lock) will block the anther # process from picking up this lock # replaced for a long while # :retry_interval - Specifies how frequently to retry acquiring the lock in # the case that the lock is held by another process, or there's an error # communicating with redis # class PersistentLock def onlocked(&blk); @onlocked = blk; self; end def onunlocked(&blk); @onunlocked = blk; self; end def initialize(redis, key, options = {}) @redis, @key = redis, key @timeout = options[:lock_timeout] || 100 @retry_timeout = options[:retry_interval] || 60 @lock = EM::Hiredis::Lock.new(redis, key, @timeout) @lock.onexpire { # When the lock is about to expire, extend (called 1s before expiry) acquire() } @locked = false EM.next_tick { @running = true acquire } end # Acquire the lock (called automatically by initialize) def acquire return unless @running @lock.acquire.callback { if !@locked @onlocked.call if @onlocked @locked = true end }.errback { |e| if @locked # We were previously locked @onunlocked.call if @onunlocked @locked = false end if e.kind_of?(EM::Hiredis::RedisError) err = e.redis_error EM::Hiredis.logger.warn "Unexpected error acquiring #{@lock} #{err}" end EM.add_timer(@retry_timeout) { acquire() unless @locked } } end def stop @running = false if @locked # We were previously locked @onunlocked.call if @onunlocked @locked = false end @lock.unlock end def locked? @locked end end end em-hiredis-0.2.1/lib/em-hiredis/version.rb0000644000175000017500000000010512135210520020513 0ustar avtobiffavtobiffmodule EventMachine module Hiredis VERSION = "0.2.1" end end em-hiredis-0.2.1/lib/em-hiredis/base_client.rb0000644000175000017500000001263512135210520021311 0ustar avtobiffavtobiffrequire 'uri' module EventMachine::Hiredis # Emits the following events # # * :connected - on successful connection or reconnection # * :reconnected - on successful reconnection # * :disconnected - no longer connected, when previously in connected state # * :reconnect_failed(failure_number) - a reconnect attempt failed # This event is passed number of failures so far (1,2,3...) # * :monitor # class BaseClient include EventEmitter include EM::Deferrable attr_reader :host, :port, :password, :db def initialize(host = 'localhost', port = 6379, password = nil, db = nil) @host, @port, @password, @db = host, port, password, db @defs = [] @command_queue = [] @closing_connection = false @reconnect_failed_count = 0 @reconnect_timer = nil @failed = false self.on(:failed) { @failed = true @command_queue.each do |df, _, _| df.fail(Error.new("Redis connection in failed state")) end @command_queue = [] } end # Configure the redis connection to use # # In usual operation, the uri should be passed to initialize. This method # is useful for example when failing over to a slave connection at runtime # def configure(uri_string) uri = URI(uri_string) if uri.scheme == "unix" @host = uri.path @port = nil else @host = uri.host @port = uri.port @password = uri.password path = uri.path[1..-1] @db = path.to_i # Empty path => 0 end end def connect @connection = EM.connect(@host, @port, Connection, @host, @port) @connection.on(:closed) do if @connected @defs.each { |d| d.fail(Error.new("Redis disconnected")) } @defs = [] @deferred_status = nil @connected = false unless @closing_connection # Next tick avoids reconnecting after for example EM.stop EM.next_tick { reconnect } end emit(:disconnected) EM::Hiredis.logger.info("#{@connection} Disconnected") else unless @closing_connection @reconnect_failed_count += 1 @reconnect_timer = EM.add_timer(EM::Hiredis.reconnect_timeout) { @reconnect_timer = nil reconnect } emit(:reconnect_failed, @reconnect_failed_count) EM::Hiredis.logger.info("#{@connection} Reconnect failed") if @reconnect_failed_count >= 4 emit(:failed) self.fail(Error.new("Could not connect after 4 attempts")) end end end end @connection.on(:connected) do @connected = true @reconnect_failed_count = 0 @failed = false select(@db) unless @db == 0 auth(@password) if @password @command_queue.each do |df, command, args| @connection.send_command(command, args) @defs.push(df) end @command_queue = [] emit(:connected) EM::Hiredis.logger.info("#{@connection} Connected") succeed if @reconnecting @reconnecting = false emit(:reconnected) end end @connection.on(:message) do |reply| if RuntimeError === reply raise "Replies out of sync: #{reply.inspect}" if @defs.empty? deferred = @defs.shift error = RedisError.new(reply.message) error.redis_error = reply deferred.fail(error) if deferred else handle_reply(reply) end end @connected = false @reconnecting = false return self end # Indicates that commands have been sent to redis but a reply has not yet # been received # # This can be useful for example to avoid stopping the # eventmachine reactor while there are outstanding commands # def pending_commands? @connected && @defs.size > 0 end def connected? @connected end def select(db, &blk) @db = db method_missing(:select, db, &blk) end def auth(password, &blk) @password = password method_missing(:auth, password, &blk) end def close_connection EM.cancel_timer(@reconnect_timer) if @reconnect_timer @closing_connection = true @connection.close_connection_after_writing end def reconnect_connection EM.cancel_timer(@reconnect_timer) if @reconnect_timer reconnect end private def method_missing(sym, *args) deferred = EM::DefaultDeferrable.new # Shortcut for defining the callback case with just a block deferred.callback { |result| yield(result) } if block_given? if @connected @connection.send_command(sym, args) @defs.push(deferred) elsif @failed deferred.fail(Error.new("Redis connection in failed state")) else @command_queue << [deferred, sym, args] end deferred end def reconnect @reconnecting = true @connection.reconnect @host, @port EM::Hiredis.logger.info("#{@connection} Reconnecting") end def handle_reply(reply) if @defs.empty? if @monitoring emit(:monitor, reply) else raise "Replies out of sync: #{reply.inspect}" end else deferred = @defs.shift deferred.succeed(reply) if deferred end end end end em-hiredis-0.2.1/lib/em-hiredis/connection.rb0000644000175000017500000000236512135210520021177 0ustar avtobiffavtobiffrequire 'hiredis/reader' module EventMachine::Hiredis class Connection < EM::Connection include EventMachine::Hiredis::EventEmitter def initialize(host, port) super @host, @port = host, port @name = "[em-hiredis #{@host}:#{@port}]" end def reconnect(host, port) super @host, @port = host, port end def connection_completed @reader = ::Hiredis::Reader.new emit(:connected) end def receive_data(data) @reader.feed(data) until (reply = @reader.gets) == false emit(:message, reply) end end def unbind emit(:closed) end def send_command(command, args) send_data(command(command, *args)) end def to_s @name end protected COMMAND_DELIMITER = "\r\n" def command(*args) command = [] command << "*#{args.size}" args.each do |arg| arg = arg.to_s command << "$#{string_size arg}" command << arg end command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER end if "".respond_to?(:bytesize) def string_size(string) string.to_s.bytesize end else def string_size(string) string.to_s.size end end end end em-hiredis-0.2.1/lib/em-hiredis/event_emitter.rb0000644000175000017500000000103312135210520021701 0ustar avtobiffavtobiffmodule EventMachine::Hiredis module EventEmitter def on(event, &listener) _listeners[event] << listener end def emit(event, *args) _listeners[event].each { |l| l.call(*args) } end def remove_listener(event, &listener) _listeners[event].delete(listener) end def remove_all_listeners(event) _listeners.delete(event) end def listeners(event) _listeners[event] end private def _listeners @_listeners ||= Hash.new { |h,k| h[k] = [] } end end end em-hiredis-0.2.1/lib/em-hiredis/lock.rb0000644000175000017500000000602612135210520017766 0ustar avtobiffavtobiffmodule EM::Hiredis # Distributed lock built on redis class Lock # Register an callback which will be called 1s before the lock expires def onexpire(&blk); @onexpire = blk; end def initialize(redis, key, timeout) @redis, @key, @timeout = redis, key, timeout @locked = false @expiry = nil end # Acquire the lock # # It is ok to call acquire again before the lock expires, which will attempt to extend the existing lock. # # Returns a deferrable which either succeeds if the lock can be acquired, or fails if it cannot. In both cases the expiry timestamp is returned (for the new lock or for the expired one respectively) def acquire df = EM::DefaultDeferrable.new expiry = new_expiry @redis.setnx(@key, expiry).callback { |setnx| if setnx == 1 lock_acquired(expiry) EM::Hiredis.logger.debug "#{to_s} Acquired new lock" df.succeed(expiry) else attempt_to_acquire_existing_lock(df) end }.errback { |e| df.fail(e) } return df end # Release the lock # # Returns a deferrable def unlock EM.cancel_timer(@expire_timer) if @expire_timer unless active df = EM::DefaultDeferrable.new df.fail Error.new("Cannot unlock, lock not active") return df end @redis.del(@key) end # Lock has been acquired and we're within it's expiry time def active @locked && Time.now.to_i < @expiry end # This should not be used in normal operation - force clear def clear @redis.del(@key) end def to_s "[lock #{@key}]" end private def attempt_to_acquire_existing_lock(df) @redis.get(@key) { |expiry_1| expiry_1 = expiry_1.to_i if expiry_1 == @expiry || expiry_1 < Time.now.to_i # Either the lock was ours or the lock has already expired expiry = new_expiry @redis.getset(@key, expiry) { |expiry_2| expiry_2 = expiry_2.to_i if expiry_2 == @expiry || expiry_2 < Time.now.to_i lock_acquired(expiry) EM::Hiredis.logger.debug "#{to_s} Acquired existing lock" df.succeed(expiry) else # Another client got there first EM::Hiredis.logger.debug "#{to_s} Could not acquire - another process acquired while we were in the process of acquiring" df.fail(expiry_2) end } else # Someone else has an active lock EM::Hiredis.logger.debug "#{to_s} Could not acquire - held by another process" df.fail(expiry_1) end } end def new_expiry Time.now.to_i + @timeout + 1 end def lock_acquired(expiry) @locked = true @expiry = expiry EM.cancel_timer(@expire_timer) if @expire_timer @expire_timer = EM.add_timer(@timeout) { EM::Hiredis.logger.debug "#{to_s} Expires in 1s" @onexpire.call if @onexpire } end end end em-hiredis-0.2.1/lib/em-hiredis/client.rb0000644000175000017500000000315712135210520020316 0ustar avtobiffavtobiffmodule EventMachine::Hiredis class Client < BaseClient def self.connect(host = 'localhost', port = 6379) new(host, port).connect end def monitor(&blk) @monitoring = true method_missing(:monitor, &blk) end def info df = method_missing(:info) df.callback { |response| info = {} response.each_line do |line| key, value = line.split(":", 2) info[key.to_sym] = value.chomp if value end df.succeed(info) } df.callback { |info| yield info } if block_given? df end def info_commandstats(&blk) hash_processor = lambda do |response| commands = {} response.each_line do |line| command, data = line.split(':') if data c = commands[command.sub('cmdstat_', '').to_sym] = {} data.split(',').each do |d| k, v = d.split('=') c[k.to_sym] = v =~ /\./ ? v.to_f : v.to_i end end end blk.call(commands) end method_missing(:info, 'commandstats', &hash_processor) end # Gives access to a richer interface for pubsub subscriptions on a # separate redis connection # def pubsub @pubsub ||= begin PubsubClient.new(@host, @port, @password, @db).connect end end def subscribe(*channels) raise "Use pubsub client" end def unsubscribe(*channels) raise "Use pubsub client" end def psubscribe(channel) raise "Use pubsub client" end def punsubscribe(channel) raise "Use pubsub client" end end end em-hiredis-0.2.1/lib/em-hiredis/pubsub_client.rb0000644000175000017500000001331512135210520021673 0ustar avtobiffavtobiffmodule EventMachine::Hiredis class PubsubClient < BaseClient PUBSUB_MESSAGES = %w{message pmessage subscribe unsubscribe psubscribe punsubscribe}.freeze def initialize(host='localhost', port='6379', password=nil, db=nil) @subs, @psubs = [], [] @pubsub_defs = Hash.new { |h,k| h[k] = [] } super end def connect @sub_callbacks = Hash.new { |h, k| h[k] = [] } @psub_callbacks = Hash.new { |h, k| h[k] = [] } # Resubsubscribe to channels on reconnect on(:reconnected) { raw_send_command(:subscribe, @subs) if @subs.any? raw_send_command(:psubscribe, @psubs) if @psubs.any? } super end # Subscribe to a pubsub channel # # If an optional proc / block is provided then it will be called when a # message is received on this channel # # @return [Deferrable] Redis subscribe call # def subscribe(channel, proc = nil, &block) if cb = proc || block @sub_callbacks[channel] << cb end @subs << channel raw_send_command(:subscribe, [channel]) return pubsub_deferrable(channel) end # Unsubscribe all callbacks for a given channel # # @return [Deferrable] Redis unsubscribe call # def unsubscribe(channel) @sub_callbacks.delete(channel) @subs.delete(channel) raw_send_command(:unsubscribe, [channel]) return pubsub_deferrable(channel) end # Unsubscribe a given callback from a channel. Will unsubscribe from redis # if there are no remaining subscriptions on this channel # # @return [Deferrable] Succeeds when the unsubscribe has completed or # fails if callback could not be found. Note that success may happen # immediately in the case that there are other callbacks for the same # channel (and therefore no unsubscription from redis is necessary) # def unsubscribe_proc(channel, proc) df = EM::DefaultDeferrable.new if @sub_callbacks[channel].delete(proc) if @sub_callbacks[channel].any? # Succeed deferrable immediately - no need to unsubscribe df.succeed else unsubscribe(channel).callback { |_| df.succeed } end else df.fail end return df end # Pattern subscribe to a pubsub channel # # If an optional proc / block is provided then it will be called (with the # channel name and message) when a message is received on a matching # channel # # @return [Deferrable] Redis psubscribe call # def psubscribe(pattern, proc = nil, &block) if cb = proc || block @psub_callbacks[pattern] << cb end @psubs << pattern raw_send_command(:psubscribe, [pattern]) return pubsub_deferrable(pattern) end # Pattern unsubscribe all callbacks for a given pattern # # @return [Deferrable] Redis punsubscribe call # def punsubscribe(pattern) @psub_callbacks.delete(pattern) @psubs.delete(pattern) raw_send_command(:punsubscribe, [pattern]) return pubsub_deferrable(pattern) end # Unsubscribe a given callback from a pattern. Will unsubscribe from redis # if there are no remaining subscriptions on this pattern # # @return [Deferrable] Succeeds when the punsubscribe has completed or # fails if callback could not be found. Note that success may happen # immediately in the case that there are other callbacks for the same # pattern (and therefore no punsubscription from redis is necessary) # def punsubscribe_proc(pattern, proc) df = EM::DefaultDeferrable.new if @psub_callbacks[pattern].delete(proc) if @psub_callbacks[pattern].any? # Succeed deferrable immediately - no need to punsubscribe df.succeed else punsubscribe(pattern).callback { |_| df.succeed } end else df.fail end return df end private # Send a command to redis without adding a deferrable for it. This is # useful for commands for which replies work or need to be treated # differently def raw_send_command(sym, args) if @connected @connection.send_command(sym, args) else callback do @connection.send_command(sym, args) end end return nil end def pubsub_deferrable(channel) df = EM::DefaultDeferrable.new @pubsub_defs[channel].push(df) df end def handle_reply(reply) if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil # Note: pmessage is the only message with 4 arguments kind, subscription, d1, d2 = *reply case kind.to_sym when :message if @sub_callbacks.has_key?(subscription) @sub_callbacks[subscription].each { |cb| cb.call(d1) } end # Arguments are channel, message payload emit(:message, subscription, d1) when :pmessage if @psub_callbacks.has_key?(subscription) @psub_callbacks[subscription].each { |cb| cb.call(d1, d2) } end # Arguments are original pattern, channel, message payload emit(:pmessage, subscription, d1, d2) else if @pubsub_defs[subscription].any? df = @pubsub_defs[subscription].shift df.succeed(d1) # Cleanup empty arrays if @pubsub_defs[subscription].empty? @pubsub_defs.delete(subscription) end end # Also emit the event, as an alternative to using the deferrables emit(kind.to_sym, subscription, d1) end else super end end end end em-hiredis-0.2.1/spec/0000755000175000017500000000000012135210520014643 5ustar avtobiffavtobiffem-hiredis-0.2.1/spec/base_client_spec.rb0000644000175000017500000000613412135210520020456 0ustar avtobiffavtobiffrequire 'spec_helper' describe EM::Hiredis::BaseClient do it "should be able to connect to redis (required for all tests!)" do em { redis = EM::Hiredis.connect redis.callback { done } redis.errback { puts "CHECK THAT THE REDIS SERVER IS RUNNING ON PORT 6379" fail } } end it "should emit an event on reconnect failure, with the retry count" do # Assumes there is no redis server on 9999 connect(1, "redis://localhost:9999/") do |redis| expected = 1 redis.on(:reconnect_failed) { |count| count.should == expected expected += 1 done if expected == 3 } end end it "should emit disconnected when the connection closes" do connect do |redis| redis.on(:disconnected) { done } redis.close_connection end end it "should fail the client deferrable after 4 unsuccessful attempts" do connect(1, "redis://localhost:9999/") do |redis| events = [] redis.on(:reconnect_failed) { |count| events << count } redis.errback { |error| error.class.should == EM::Hiredis::Error error.message.should == 'Could not connect after 4 attempts' events.should == [1,2,3,4] done } end end it "should fail commands immediately when in failed state" do connect(1, "redis://localhost:9999/") do |redis| redis.fail redis.get('foo').errback { |error| error.class.should == EM::Hiredis::Error error.message.should == 'Redis connection in failed state' done } end end it "should fail queued commands when entering failed state" do connect(1, "redis://localhost:9999/") do |redis| redis.get('foo').errback { |error| error.class.should == EM::Hiredis::Error error.message.should == 'Redis connection in failed state' done } redis.fail end end it "should allow reconfiguring the client at runtime" do connect(1, "redis://localhost:9999/") do |redis| redis.on(:reconnect_failed) { redis.configure("redis://localhost:6379/9") redis.info { done } } end end it "should allow connection to be reconnected" do connect do |redis| redis.on(:reconnected) { done } # Wait for first connection to complete redis.callback { redis.reconnect_connection } end end it "should wrap error responses returned by redis" do connect do |redis| redis.sadd('foo', 'bar') { df = redis.get('foo') df.callback { fail "Should have received error response from redis" } df.errback { |e| e.class.should == EM::Hiredis::RedisError e.should be_kind_of(EM::Hiredis::Error) msg = "ERR Operation against a key holding the wrong kind of value" e.message.should == msg # This is the wrapped error from redis: e.redis_error.class.should == RuntimeError e.redis_error.message.should == msg done } } end end end em-hiredis-0.2.1/spec/spec_helper.rb0000644000175000017500000000065112135210520017463 0ustar avtobiffavtobiff$:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib") require 'em-hiredis' require 'rspec' require 'em-spec/rspec' require 'support/connection_helper' require 'support/redis_mock' require 'stringio' RSpec.configure do |config| config.include ConnectionHelper config.include EventMachine::SpecHelper config.include RedisMock::Helper end # This speeds the tests up a bit EM::Hiredis.reconnect_timeout = 0.01 em-hiredis-0.2.1/spec/support/0000755000175000017500000000000012135210520016357 5ustar avtobiffavtobiffem-hiredis-0.2.1/spec/support/redis_mock.rb0000644000175000017500000000272512135210520021031 0ustar avtobiffavtobiff# nabbed from redis-rb, thanks! require "socket" module RedisMock def self.start(port = 6380) server = TCPServer.new("127.0.0.1", port) loop do session = server.accept while line = session.gets parts = Array.new(line[1..-3].to_i) do bytes = session.gets[1..-3].to_i argument = session.read(bytes) session.read(2) # Discard \r\n argument end response = yield(*parts) if response.nil? session.shutdown(Socket::SHUT_RDWR) break else session.write(response) session.write("\r\n") end end end end module Helper # Forks the current process and starts a new mock Redis server on # port 6380. # # The server will reply with a `+OK` to all commands, but you can # customize it by providing a hash. For example: # # redis_mock(:ping => lambda { "+PONG" }) do # assert_equal "PONG", Redis.new(:port => 6380).ping # end # def redis_mock(replies = {}) begin pid = fork do trap("TERM") { exit } RedisMock.start do |command, *args| (replies[command.to_sym] || lambda { |*_| "+OK" }).call(*args) end end sleep 1 # Give time for the socket to start listening. yield ensure if pid Process.kill("TERM", pid) Process.wait(pid) end end end end end em-hiredis-0.2.1/spec/support/connection_helper.rb0000644000175000017500000000052612135210520022405 0ustar avtobiffavtobiffmodule ConnectionHelper # Use db 9 for tests to avoid flushing the main db # It would be nice if there was a standard db number for testing... def connect(timeout = 1, url = "redis://localhost:6379/9", &blk) em(timeout) do redis = EventMachine::Hiredis.connect(url) redis.flushdb blk.call(redis) end end end em-hiredis-0.2.1/spec/url_param_spec.rb0000644000175000017500000000206512135210520020167 0ustar avtobiffavtobiff# adapted from redis-rb require 'spec_helper' describe EventMachine::Hiredis, "URL parsing" do it "defaults URL defaults to 127.0.0.1:6379" do redis = EventMachine::Hiredis.setup redis.host.should == "127.0.0.1" redis.port.should == 6379 redis.db.should == 0 redis.password.should == nil end it "allows to pass in a URL" do redis = EventMachine::Hiredis.setup "redis://:secr3t@foo.com:999/2" redis.host.should == "foo.com" redis.port.should == 999 redis.db.should == 2 redis.password.should == "secr3t" end it "does not modify the passed options" do options = "redis://:secr3t@foo.com:999/2" redis = EventMachine::Hiredis.setup(options) options.should == "redis://:secr3t@foo.com:999/2" end it "uses REDIS_URL over default if available" do ENV["REDIS_URL"] = "redis://:secr3t@foo.com:999/2" redis = EventMachine::Hiredis.setup redis.host.should == "foo.com" redis.port.should == 999 redis.db.should == 2 redis.password.should == "secr3t" ENV.delete("REDIS_URL") end end em-hiredis-0.2.1/spec/redis_commands_spec.rb0000644000175000017500000006414212135210520021200 0ustar avtobiffavtobiffrequire 'spec_helper' describe EventMachine::Hiredis, "commands" do it "pings" do connect do |redis| redis.ping { |r| r.should == 'PONG'; done } end end it "SETs and GETs a key" do connect do |redis| redis.set('foo', 'nik') redis.get('foo') { |r| r.should == 'nik'; done } end end it "handles trailing newline characters" do connect do |redis| redis.set('foo', "bar\n") redis.get('foo') { |r| r.should == "bar\n"; done } end end it "stores and retrieves all possible characters at the beginning and the end of a string" do connect do |redis| (0..255).each do |char_idx| string = "#{char_idx.chr}---#{char_idx.chr}" if RUBY_VERSION > "1.9" string.force_encoding("UTF-8") end redis.set('foo', string) redis.get('foo') { |r| r.should == string } end redis.ping { done } end end it "SETs a key with an expiry" do connect do |redis| timeout(3) redis.setex('foo', 1, 'bar') redis.get('foo') { |r| r.should == 'bar' } EventMachine.add_timer(2) do redis.get('foo') { |r| r.should == nil } redis.ping { done } end end end it "gets TTL for a key" do connect do |redis| redis.setex('foo', 1, 'bar') redis.ttl('foo') { |r| r.should == 1; done } end end it "can SETNX" do connect do |redis| redis.set('foo', 'nik') redis.get('foo') { |r| r.should == 'nik' } redis.setnx 'foo', 'bar' redis.get('foo') { |r| r.should == 'nik' } redis.ping { done } end end it "can GETSET" do connect do |redis| redis.set('foo', 'bar') redis.getset('foo', 'baz') { |r| r.should == 'bar' } redis.get('foo') { |r| r.should == 'baz'; done } end end it "can INCR a key" do connect do |redis| redis.del('counter') redis.incr('counter') { |r| r.should == 1 } redis.incr('counter') { |r| r.should == 2 } redis.incr('counter') { |r| r.should == 3 } redis.ping { done } end end it "can INCRBY a key" do connect do |redis| redis.del('counter') redis.incrby('counter', 1) { |r| r.should == 1 } redis.incrby('counter', 2) { |r| r.should == 3 } redis.incrby('counter', 3) { |r| r.should == 6 } redis.ping { done } end end it "can DECR a key" do connect do |redis| redis.del('counter') redis.incr('counter') { |r| r.should == 1 } redis.incr('counter') { |r| r.should == 2 } redis.incr('counter') { |r| r.should == 3 } redis.decr('counter') { |r| r.should == 2 } redis.decrby('counter', 2) { |r| r.should == 0; done } end end it "can RANDOMKEY" do connect do |redis| redis.set('foo', 'bar') redis.randomkey { |r| r.should_not == nil; done } end end it "can RENAME a key" do connect do |redis| redis.del 'foo' redis.del 'bar' redis.set('foo', 'hi') redis.rename 'foo', 'bar' redis.get('bar') { |r| r.should == 'hi' ; done } end end it "can RENAMENX a key" do connect do |redis| redis.del 'foo' redis.del 'bar' redis.set('foo', 'hi') redis.set('bar', 'ohai') redis.renamenx 'foo', 'bar' redis.get('bar') { |r| r.should == 'ohai' ; done } end end it "can get DBSIZE of the database" do connect do |redis| redis.set('foo1', 'bar') redis.set('foo2', 'baz') redis.set('foo3', 'bat') redis.dbsize do |r| r.should == 3 done end end end it "can EXPIRE a key" do connect do |redis| timeout(3) redis.set('foo', 'bar') redis.expire 'foo', 1 redis.get('foo') { |r| r.should == "bar" } EventMachine.add_timer(2) do redis.get('foo') { |r| r.should == nil } redis.ping { done } end end end it "can check if a key EXISTS" do connect do |redis| redis.set 'foo', 'nik' redis.exists('foo') { |r| r.should == 1 } redis.del 'foo' redis.exists('foo') { |r| r.should == 0 ; done } end end it "can list KEYS" do connect do |redis| redis.keys("f*") { |keys| keys.each { |key| @r.del key } } redis.set('f', 'nik') redis.set('fo', 'nak') redis.set('foo', 'qux') redis.keys("f*") { |r| r.sort.should == ['f', 'fo', 'foo'].sort } redis.ping { done } end end it "returns a random key (RANDOMKEY)" do connect do |redis| redis.set("foo", "bar") redis.randomkey do |r| redis.exists(r) do |e| e.should == 1 done end end end end it "should be able to check the TYPE of a key" do connect do |redis| redis.set('foo', 'nik') redis.type('foo') { |r| r.should == "string" } redis.del 'foo' redis.type('foo') { |r| r.should == "none" ; done } end end it "pushes to the head of a list (LPUSH)" do connect do |redis| redis.lpush "list", 'hello' redis.lpush "list", 42 redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 2 } redis.lpop('list') { |r| r.should == '42'; done } end end it "pushes to the tail of a list (RPUSH)" do connect do |redis| redis.rpush "list", 'hello' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 1 ; done } end end it "pops the tail of a list (RPOP)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush"list", 'goodbye' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 2 } redis.rpop('list') { |r| r.should == 'goodbye'; done } end end it "pop the head of a list (LPOP)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush "list", 'goodbye' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 2 } redis.lpop('list') { |r| r.should == 'hello'; done } end end it "gets the length of a list (LLEN)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush "list", 'goodbye' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 2 ; done } end end it "gets a range of values from a list (LRANGE)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush "list", 'goodbye' redis.rpush "list", '1' redis.rpush "list", '2' redis.rpush "list", '3' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 5 } redis.lrange('list', 2, -1) { |r| r.should == ['1', '2', '3']; done } end end it "trims a list (LTRIM)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush "list", 'goodbye' redis.rpush "list", '1' redis.rpush "list", '2' redis.rpush "list", '3' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 5 } redis.ltrim 'list', 0, 1 redis.llen('list') { |r| r.should == 2 } redis.lrange('list', 0, -1) { |r| r.should == ['hello', 'goodbye']; done } end end it "gets a value by indexing into a list (LINDEX)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush "list", 'goodbye' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 2 } redis.lindex('list', 1) { |r| r.should == 'goodbye'; done } end end it "sets a value by indexing into a list (LSET)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush "list", 'hello' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 2 } redis.lset('list', 1, 'goodbye') { |r| r.should == 'OK' } redis.lindex('list', 1) { |r| r.should == 'goodbye'; done } end end it "removes values from a list (LREM)" do connect do |redis| redis.rpush "list", 'hello' redis.rpush "list", 'goodbye' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 2 } redis.lrem('list', 1, 'hello') { |r| r.should == 1 } redis.lrange('list', 0, -1) { |r| r.should == ['goodbye']; done } end end it "pops values from a list and push them onto a temp list(RPOPLPUSH)" do connect do |redis| redis.rpush "list", 'one' redis.rpush "list", 'two' redis.rpush "list", 'three' redis.type('list') { |r| r.should == "list" } redis.llen('list') { |r| r.should == 3 } redis.lrange('list', 0, -1) { |r| r.should == ['one', 'two', 'three'] } redis.lrange('tmp', 0, -1) { |r| r.should == [] } redis.rpoplpush('list', 'tmp') { |r| r.should == 'three' } redis.lrange('tmp', 0, -1) { |r| r.should == ['three'] } redis.rpoplpush('list', 'tmp') { |r| r.should == 'two' } redis.lrange('tmp', 0, -1) { |r| r.should == ['two', 'three'] } redis.rpoplpush('list', 'tmp') { |r| r.should == 'one' } redis.lrange('tmp', 0, -1) { |r| r.should == ['one', 'two', 'three']; done } end end it "adds members to a set (SADD)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.type('set') { |r| r.should == "set" } redis.scard('set') { |r| r.should == 2 } redis.smembers('set') { |r| r.sort.should == ['key1', 'key2'].sort; done } end end it "deletes members to a set (SREM)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.type('set') { |r| r.should == "set" } redis.scard('set') { |r| r.should == 2 } redis.smembers('set') { |r| r.sort.should == ['key1', 'key2'].sort } redis.srem('set', 'key1') redis.scard('set') { |r| r.should == 1 } redis.smembers('set') { |r| r.should == ['key2']; done } end end it "returns and remove random key from set (SPOP)" do connect do |redis| redis.sadd "set_pop", "key1" redis.sadd "set_pop", "key2" redis.spop("set_pop") { |r| r.should_not == nil } redis.scard("set_pop") { |r| r.should == 1; done } end end it "returns random key without delete the key from a set (SRANDMEMBER)" do connect do |redis| redis.sadd "set_srandmember", "key1" redis.sadd "set_srandmember", "key2" redis.srandmember("set_srandmember") { |r| r.should_not == nil } redis.scard("set_srandmember") { |r| r.should == 2; done } end end it "counts the members of a set (SCARD)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.type('set') { |r| r.should == "set" } redis.scard('set') { |r| r.should == 2; done } end end it "tests for set membership (SISMEMBER)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.type('set') { |r| r.should == "set" } redis.scard('set') { |r| r.should == 2 } redis.sismember('set', 'key1') { |r| r.should == 1 } redis.sismember('set', 'key2') { |r| r.should == 1 } redis.sismember('set', 'notthere') { |r| r.should == 0; done } end end it "intersects sets (SINTER)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.sadd "set2", 'key2' redis.sinter('set', 'set2') { |r| r.should == ['key2']; done } end end it "intersects set and stores the results in a key (SINTERSTORE)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.sadd "set2", 'key2' redis.sinterstore('newone', 'set', 'set2') { |r| r.should == 1 } redis.smembers('newone') { |r| r.should == ['key2']; done } end end it "performs set unions (SUNION)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.sadd "set2", 'key2' redis.sadd "set2", 'key3' redis.sunion('set', 'set2') { |r| r.sort.should == ['key1','key2','key3'].sort; done } end end it "performs a set union and store the results in a key (SUNIONSTORE)" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.sadd "set2", 'key2' redis.sadd "set2", 'key3' redis.sunionstore('newone', 'set', 'set2') { |r| r.should == 3 } redis.smembers('newone') { |r| r.sort.should == ['key1','key2','key3'].sort; done } end end it "takes a set difference (SDIFF)" do connect do |redis| redis.sadd "set", 'a' redis.sadd "set", 'b' redis.sadd "set2", 'b' redis.sadd "set2", 'c' redis.sdiff('set', 'set2') { |r| r.should == ['a']; done } end end it "takes set difference and store the results in a key (SDIFFSTORE)" do connect do |redis| redis.sadd "set", 'a' redis.sadd "set", 'b' redis.sadd "set2", 'b' redis.sadd "set2", 'c' redis.sdiffstore('newone', 'set', 'set2') redis.smembers('newone') { |r| r.should == ['a']; done } end end it "moves elements from one set to another (SMOVE)" do connect do |redis| redis.sadd 'set1', 'a' redis.sadd 'set1', 'b' redis.sadd 'set2', 'x' redis.smove('set1', 'set2', 'a') { |r| r.should == 1 } redis.sismember('set2', 'a') { |r| r.should == 1 } redis.del('set1') { done } end end it "counts the members of a zset" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.zadd 'zset', 1, 'set' redis.zcount('zset') { |r| r.should == 1 } redis.del('set') redis.del('zset') { done } end end it "adds members to a zset" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.zadd 'zset', 1, 'set' redis.zrange('zset', 0, 1) { |r| r.should == ['set'] } redis.zcount('zset') { |r| r.should == 1 } redis.del('set') redis.del('zset') { done } end end it "deletes members to a zset" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.type?('set') { |r| r.should == "set" } redis.sadd "set2", 'key3' redis.sadd "set2", 'key4' redis.type?('set2') { |r| r.should == "set" } redis.zadd 'zset', 1, 'set' redis.zcount('zset') { |r| r.should == 1 } redis.zadd 'zset', 2, 'set2' redis.zcount('zset') { |r| r.should == 2 } redis.zset_delete 'zset', 'set' redis.zcount('zset') { |r| r.should == 1 } redis.del('set') redis.del('set2') redis.del('zset') { done } end end it "gets a range of values from a zset" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.sadd "set2", 'key3' redis.sadd "set2", 'key4' redis.sadd "set3", 'key1' redis.type?('set') { |r| r.should == 'set' } redis.type?('set2') { |r| r.should == 'set' } redis.type?('set3') { |r| r.should == 'set' } redis.zadd 'zset', 1, 'set' redis.zadd 'zset', 2, 'set2' redis.zadd 'zset', 3, 'set3' redis.zcount('zset') { |r| r.should == 3 } redis.zrange('zset', 0, 3) { |r| r.should == ['set', 'set2', 'set3'] } redis.del('set') redis.del('set2') redis.del('set3') redis.del('zset') { done } end end it "gets a reverse range of values from a zset" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.sadd "set2", 'key3' redis.sadd "set2", 'key4' redis.sadd "set3", 'key1' redis.type?('set') { |r| r.should == 'set' } redis.type?('set2') { |r| r.should == 'set' } redis.type?('set3') { |r| r.should == 'set' } redis.zadd 'zset', 1, 'set' redis.zadd 'zset', 2, 'set2' redis.zadd 'zset', 3, 'set3' redis.zcount('zset') { |r| r.should == 3 } redis.zrevrange('zset', 0, 3) { |r| r.should == ['set3', 'set2', 'set'] } redis.del('set') redis.del('set2') redis.del('set3') redis.del('zset') { done } end end it "gets a range by score of values from a zset" do connect do |redis| redis.sadd "set", 'key1' redis.sadd "set", 'key2' redis.sadd "set2", 'key3' redis.sadd "set2", 'key4' redis.sadd "set3", 'key1' redis.sadd "set4", 'key4' redis.zadd 'zset', 1, 'set' redis.zadd 'zset', 2, 'set2' redis.zadd 'zset', 3, 'set3' redis.zadd 'zset', 4, 'set4' redis.zcount('zset') { |r| r.should == 4 } redis.zrangebyscore('zset', 2, 3) { |r| r.should == ['set2', 'set3'] } redis.del('set') redis.del('set2') redis.del('set3') redis.del('set4') redis.del('zset') { done } end end it "gets a score for a specific value in a zset (ZSCORE)" do connect do |redis| redis.zadd "zset", 23, "value" redis.zscore("zset", "value") { |r| r.should == "23" } redis.zscore("zset", "value2") { |r| r.should == nil } redis.zscore("unknown_zset", "value") { |r| r.should == nil } redis.del("zset") { done } end end it "increments a range score of a zset (ZINCRBY)" do connect do |redis| # create a new zset redis.zincrby "hackers", 1965, "Yukihiro Matsumoto" redis.zscore("hackers", "Yukihiro Matsumoto") { |r| r.should == "1965" } # add a new element redis.zincrby "hackers", 1912, "Alan Turing" redis.zscore("hackers", "Alan Turing") { |r| r.should == "1912" } # update the score redis.zincrby "hackers", 100, "Alan Turing" # yeah, we are making Turing a bit younger redis.zscore("hackers", "Alan Turing") { |r| r.should == "2012" } # attempt to update a key that's not a zset redis.set("i_am_not_a_zet", "value") # shouldn't raise error anymore redis.zincrby("i_am_not_a_zet", 23, "element") { |r| r.should == nil } redis.del("hackers") redis.del("i_am_not_a_zet") { done } end end it "provides info (INFO)" do connect do |redis| redis.info do |r| [:redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days].each do |x| r.keys.include?(x).should == true end done end end end it "provides commandstats (INFO COMMANDSTATS)" do connect do |redis| redis.info_commandstats do |r| r[:get][:calls].should be_a_kind_of(Integer) r[:get][:usec].should be_a_kind_of(Integer) r[:get][:usec_per_call].should be_a_kind_of(Float) done end end end it "flushes the database (FLUSHDB)" do connect do |redis| redis.set('key1', 'keyone') redis.set('key2', 'keytwo') redis.keys('*') { |r| r.sort.should == ['key1', 'key2'].sort } redis.flushdb redis.keys('*') { |r| r.should == []; done } end end it "SELECTs database" do connect do |redis| redis.set("foo", "bar") do |set_response| redis.select("10") do |select_response| redis.get("foo") do |get_response| get_response.should == nil; done end end end end end it "SELECTs database without a callback" do connect do |redis| redis.select("9") redis.incr("foo") do |response| response.should == 1 done end end end it "provides the last save time (LASTSAVE)" do connect do |redis| redis.lastsave do |savetime| Time.at(savetime).class.should == Time Time.at(savetime).should <= Time.now done end end end it "can MGET keys" do connect do |redis| redis.set('foo', 1000) redis.set('bar', 2000) redis.mget('foo', 'bar') { |r| r.should == ['1000', '2000'] } redis.mget('foo', 'bar', 'baz') { |r| r.should == ['1000', '2000', nil] } redis.ping { done } end end it "can MSET values" do connect do |redis| redis.mset "key1", "value1", "key2", "value2" redis.get('key1') { |r| r.should == "value1" } redis.get('key2') { |r| r.should == "value2"; done } end end it "can MSETNX values" do connect do |redis| redis.msetnx "keynx1", "valuenx1", "keynx2", "valuenx2" redis.mget('keynx1', 'keynx2') { |r| r.should == ["valuenx1", "valuenx2"] } redis.set("keynx1", "value1") redis.set("keynx2", "value2") redis.msetnx "keynx1", "valuenx1", "keynx2", "valuenx2" redis.mget('keynx1', 'keynx2') { |r| r.should == ["value1", "value2"]; done } end end it "can BGSAVE" do connect do |redis| redis.bgsave do |r| ['OK', 'Background saving started'].include?(r).should == true done end end end it "can ECHO" do connect do |redis| redis.echo("message in a bottle\n") { |r| r.should == "message in a bottle\n"; done } end end it "runs MULTI without a block" do connect do |redis| redis.multi redis.get("key1") { |r| r.should == "QUEUED" } redis.discard { done } end end it "runs MULTI/EXEC" do connect do |redis| redis.multi redis.set "key1", "value1" redis.exec redis.get("key1") { |r| r.should == "value1" } begin redis.multi redis.set "key2", "value2" raise "Some error" redis.set "key3", "value3" redis.exec rescue redis.discard end redis.get("key2") { |r| r.should == nil } redis.get("key3") { |r| r.should == nil; done} end end it "sets and get hash values" do connect do |redis| redis.hset("rush", "signals", "1982") { |r| r.should == 1 } redis.hexists("rush", "signals") { |r| r.should == 1 } redis.hget("rush", "signals") { |r| r.should == "1982"; done } end end it "deletes hash values" do connect do |redis| redis.hset("rush", "YYZ", "1981") redis.hdel("rush", "YYZ") { |r| r.should == 1 } redis.hexists("rush", "YYZ") { |r| r.should == 0; done } end end end describe EventMachine::Hiredis, "with hash values" do def set(&blk) connect do |redis| redis.hset("rush", "permanent waves", "1980") redis.hset("rush", "moving pictures", "1981") redis.hset("rush", "signals", "1982") blk.call(redis) end end it "gets the length of the hash" do set do |redis| redis.hlen("rush") { |r| r.should == 3 } redis.hlen("yyz") { |r| r.should == 0; done } end end it "gets the keys and values of the hash" do set do |redis| redis.hkeys("rush") { |r| r.should == ["permanent waves", "moving pictures", "signals"] } redis.hvals("rush") { |r| r.should == %w[1980 1981 1982] } redis.hvals("yyz") { |r| r.should == []; done } end end it "returns all hash values" do set do |redis| redis.hgetall("rush") do |r| r.should == [ "permanent waves", "1980", "moving pictures", "1981", "signals" , "1982" ] end redis.hgetall("yyz") { |r| r.should == []; done } end end end describe EventMachine::Hiredis, "with nested multi-bulk response" do def set(&blk) connect do |redis| redis.set 'user:one:id', 'id-one' redis.set 'user:two:id', 'id-two' redis.sadd "user:one:interests", "first-interest" redis.sadd "user:one:interests", "second-interest" redis.sadd "user:two:interests", "third-interest" blk.call(redis) end end it "returns array of arrays" do set do |redis| redis.multi redis.smembers "user:one:interests" redis.smembers "user:two:interests" redis.exec do |interests_one, interests_two| interests_one.sort.should == ["first-interest", "second-interest"] interests_two.should == ['third-interest'] end redis.mget("user:one:id", "user:two:id") do |user_ids| user_ids.should == ['id-one', 'id-two'] done end end end end describe EventMachine::Hiredis, "monitor" do it "returns monitored commands" do connect do |redis| # 1. Create 2nd connection to send traffic to monitor redis2 = EventMachine::Hiredis.connect("redis://localhost:6379/") redis2.callback { # 2. Monitor after command has connected redis.monitor do |reply| reply.should == "OK" # 3. Command which should show up in monitor output redis2.get('foo') end } redis.on(:monitor) do |line| line.should =~ /foo/ done end end end end describe EventMachine::Hiredis, "sorting" do context "with some simple sorting data" do def set(&blk) connect do |redis| redis.set('dog_1', 'louie') redis.rpush 'Dogs', 1 redis.set('dog_2', 'lucy') redis.rpush 'Dogs', 2 redis.set('dog_3', 'max') redis.rpush 'Dogs', 3 redis.set('dog_4', 'taj') redis.rpush 'Dogs', 4 blk.call(redis) end end it "sorts with a limit" do set do |redis| redis.sort('Dogs', "GET", 'dog_*', "LIMIT", "0", "1") do |r| r.should == ['louie'] done end end end it "sorts with a limit and order" do set do |redis| redis.sort('Dogs', "GET", 'dog_*', "LIMIT", "0", "1", "desc", "alpha") do |r| r.should == ['taj'] done end end end end context "with more complex sorting data" do def set(&blk) connect do |redis| redis.set('dog:1:name', 'louie') redis.set('dog:1:breed', 'mutt') redis.rpush 'dogs', 1 redis.set('dog:2:name', 'lucy') redis.set('dog:2:breed', 'poodle') redis.rpush 'dogs', 2 redis.set('dog:3:name', 'max') redis.set('dog:3:breed', 'hound') redis.rpush 'dogs', 3 redis.set('dog:4:name', 'taj') redis.set('dog:4:breed', 'terrier') redis.rpush 'dogs', 4 blk.call(redis) end end it "handles multiple GETs" do set do |redis| redis.sort('dogs', 'GET', 'dog:*:name', 'GET', 'dog:*:breed', 'LIMIT', '0', '1') do |r| r.should == ['louie', 'mutt'] done end end end it "handles multiple GETs with an order" do set do |redis| redis.sort('dogs', 'GET', 'dog:*:name', 'GET', 'dog:*:breed', 'LIMIT', '0', '1', 'desc', 'alpha') do |r| r.should == ['taj', 'terrier'] done end end end end end em-hiredis-0.2.1/spec/connection_spec.rb0000644000175000017500000000255012135210520020343 0ustar avtobiffavtobiffrequire 'spec_helper' describe EventMachine::Hiredis, "connecting" do let(:replies) do # shove db number into PING reply since redis has no way # of exposing the currently selected DB replies = { :select => lambda { |db| $db = db; "+OK" }, :ping => lambda { "+PONG #{$db}" }, :auth => lambda { |password| $auth = password; "+OK" }, :get => lambda { |key| $auth == "secret" ? "$3\r\nbar" : "$-1" }, } end def connect_to_mock(url, &blk) redis_mock(replies) do connect(1, url, &blk) end end it "doesn't call select by default" do connect_to_mock("redis://localhost:6380/") do |redis| redis.ping do |response| response.should == "PONG " done end end end it "selects the right db" do connect_to_mock("redis://localhost:6380/9") do |redis| redis.ping do |response| response.should == "PONG 9" done end end end it "authenticates with a password" do connect_to_mock("redis://:secret@localhost:6380/9") do |redis| redis.get("foo") do |response| response.should == "bar" done end end end it "rejects a bad password" do connect_to_mock("redis://:failboat@localhost:6380/9") do |redis| redis.get("foo") do |response| response.should be_nil done end end end end em-hiredis-0.2.1/spec/live_redis_protocol_spec.rb0000644000175000017500000002425512135210520022260 0ustar avtobiffavtobiffrequire 'spec_helper' describe EventMachine::Hiredis, "connected to an empty db" do it "sets a string value" do connect do |redis| redis.set("foo", "bar") do |r| r.should == "OK" done end end end it "increments the value of a string" do connect do |redis| redis.incr "foo" do |r| r.should == 1 redis.incr "foo" do |r| r.should == 2 done end end end end it "increments the value of a string by an amount" do connect do |redis| redis.incrby "foo", 10 do |r| r.should == 10 done end end end it "decrements the value of a string" do connect do |redis| redis.incr "foo" do |r| r.should == 1 redis.decr "foo" do |r| r.should == 0 done end end end end it "decrement the value of a string by an amount" do connect do |redis| redis.incrby "foo", 20 do |r| r.should == 20 redis.decrby "foo", 10 do |r| r.should == 10 done end end end end it "can 'lpush' to a nonexistent list" do connect do |redis| redis.lpush("foo", "bar") do |r| r.should == 1 done end end end it "can 'rpush' to a nonexistent list" do connect do |redis| redis.rpush("foo", "bar") do |r| r.should == 1 done end end end it "gets the size of the database" do connect do |redis| redis.dbsize do |r| r.should == 0 done end end end it "adds a member to a nonexistent set" do connect do |redis| redis.sadd("set_foo", "bar") do |r| r.should == 1 done end end end it "reads info about the db" do connect do |redis| redis.info do |info| info[:redis_version].should_not be_nil done end end end it "can save the db" do connect do |redis| redis.save do |r| r.should == "OK" done end end end it "can save the db in the background" do connect do |redis| redis.bgsave do |r| r.should == "Background saving started" done end end end end describe EventMachine::Hiredis, "connected to a db containing some simple string-valued keys" do def set(&blk) connect do |redis| redis.flushdb redis.set "a", "b" redis.set "x", "y" blk.call(redis) end end it "fetches the values of multiple keys" do set do |redis| redis.mget "a", "x" do |r| r.should == ["b", "y"] done end end end it "fetches all the keys" do set do |redis| redis.keys "*" do |r| r.sort.should == ["a", "x"] done end end end it "sets a value if a key doesn't exist" do set do |redis| redis.setnx "a", "foo" do |r| r.should == 0 redis.setnx "zzz", "foo" do |r| r.should == 1 done end end end end it "tests for the existence of a key" do set do |redis| redis.exists "a" do |r| r.should == 1 redis.exists "zzz" do |r| r.should == 0 done end end end end it "deletes a key" do set do |redis| redis.del "a" do |r| r.should == 1 redis.exists "a" do |r| r.should == 0 redis.del "a" do |r| r.should == 0 done end end end end end it "detects the type of a key, existing or not" do set do |redis| redis.type "a" do |r| r.should == "string" redis.type "zzz" do |r| r.should == "none" done end end end end it "renames a key" do set do |redis| redis.rename "a", "x" do |r| redis.get "x" do |r| r.should == "b" done end end end end it "renames a key unless it exists" do set do |redis| redis.renamenx "a", "x" do |r| r.should == 0 redis.renamenx "a", "zzz" do |r| r.should == 1 redis.get "zzz" do |r| r.should == "b" done end end end end end end describe EventMachine::Hiredis, "connected to a db containing a list" do def set(&blk) connect do |redis| redis.flushdb redis.lpush "foo", "c" redis.lpush "foo", "b" redis.lpush "foo", "a" blk.call(redis) end end it "sets a list member and 'lindex' to retrieve it" do set do |redis| redis.lset("foo", 1, "bar") do |r| redis.lindex("foo", 1) do |r| r.should == "bar" done end end end end it "pushes onto tail of the list" do set do |redis| redis.rpush "foo", "d" do |r| r.should == 4 redis.rpop "foo" do |r| r.should == "d" done end end end end it "pushes onto the head of the list" do set do |redis| redis.lpush "foo", "d" do |r| r.should == 4 redis.lpop "foo" do |r| r.should == "d" done end end end end it "pops off the tail of the list" do set do |redis| redis.rpop("foo") do |r| r.should == "c" done end end end it "pops off the tail of the list" do set do |redis| redis.lpop("foo") do |r| r.should == "a" done end end end it "gets a range of values from a list" do set do |redis| redis.lrange("foo", 0, 1) do |r| r.should == ["a", "b"] done end end end it "trims a list" do set do |redis| redis.ltrim("foo", 0, 1) do |r| r.should == "OK" redis.llen("foo") do |r| r.should == 2 done end end end end it "removes a list element" do set do |redis| redis.lrem("foo", 0, "a") do |r| r.should == 1 redis.llen("foo") do |r| r.should == 2 done end end end end it "detects the type of a list" do set do |redis| redis.type "foo" do |r| r.should == "list" done end end end end describe EventMachine::Hiredis, "connected to a db containing two sets" do def set(&blk) connect do |redis| redis.flushdb redis.sadd "foo", "a" redis.sadd "foo", "b" redis.sadd "foo", "c" redis.sadd "bar", "c" redis.sadd "bar", "d" redis.sadd "bar", "e" blk.call(redis) end end it "finds a set's cardinality" do set do |redis| redis.scard("foo") do |r| r.should == 3 done end end end it "adds a new member to a set unless it is a duplicate" do set do |redis| redis.sadd("foo", "d") do |r| r.should == 1 # success redis.sadd("foo", "a") do |r| r.should == 0 # failure redis.scard("foo") do |r| r.should == 4 done end end end end end it "removes a set member if it exists" do set do |redis| redis.srem("foo", "a") do |r| r.should == 1 redis.srem("foo", "z") do |r| r.should == 0 redis.scard("foo") do |r| r.should == 2 done end end end end end it "retrieves a set's members" do set do |redis| redis.smembers("foo") do |r| r.sort.should == ["a", "b", "c"] done end end end it "detects set membership" do set do |redis| redis.sismember("foo", "a") do |r| r.should == 1 redis.sismember("foo", "z") do |r| r.should == 0 done end end end end it "finds the sets' intersection" do set do |redis| redis.sinter("foo", "bar") do |r| r.should == ["c"] done end end end it "finds and stores the sets' intersection" do set do |redis| redis.sinterstore("baz", "foo", "bar") do |r| r.should == 1 redis.smembers("baz") do |r| r.should == ["c"] done end end end end it "finds the sets' union" do set do |redis| redis.sunion("foo", "bar") do |r| r.sort.should == ["a","b","c","d","e"] done end end end it "finds and stores the sets' union" do set do |redis| redis.sunionstore("baz", "foo", "bar") do |r| r.should == 5 redis.smembers("baz") do |r| r.sort.should == ["a","b","c","d","e"] done end end end end it "detects the type of a set" do set do |redis| redis.type "foo" do |r| r.should == "set" done end end end end describe EventMachine::Hiredis, "connected to a db containing three linked lists" do def set(&blk) connect do |redis| redis.flushdb redis.rpush "foo", "a" redis.rpush "foo", "b" redis.set "a_sort", "2" redis.set "b_sort", "1" redis.set "a_data", "foo" redis.set "b_data", "bar" blk.call(redis) end end it "collates a sorted set of data" do set do |redis| redis.sort("foo", "BY", "*_sort", "GET", "*_data") do |r| r.should == ["bar", "foo"] done end end end it "gets keys selectively" do set do |redis| redis.keys "a_*" do |r| r.sort.should == ["a_sort", "a_data"].sort done end end end end describe EventMachine::Hiredis, "when reconnecting" do it "select previously selected dataset" do connect(3) do |redis| #simulate disconnect redis.set('foo', 'a') { redis.close_connection_after_writing } EventMachine.add_timer(2) do redis.get('foo') do |r| r.should == 'a' redis.get('non_existing') do |r| r.should == nil done end end end end end end describe EventMachine::Hiredis, "when closing_connection" do it "should fail deferred commands" do errored = false connect do |redis| op = redis.blpop 'empty_list' op.callback { fail } op.errback { EM.stop } redis.close_connection EM.add_timer(1) { fail } end end end em-hiredis-0.2.1/spec/pubsub_spec.rb0000644000175000017500000002312512135210520017505 0ustar avtobiffavtobiffrequire 'spec_helper' describe EventMachine::Hiredis::PubsubClient, '(un)subscribe' do describe "subscribing" do it "should return deferrable which succeeds with subscribe call result" do connect do |redis| df = redis.pubsub.subscribe("channel") { } df.should be_kind_of(EventMachine::DefaultDeferrable) df.callback { |subscription_count| # Subscribe response from redis - indicates that subscription has # succeeded and that the current connection has a single # subscription subscription_count.should == 1 done } end end it "should run the passed block when message received" do connect do |redis| redis.pubsub.subscribe("channel") { |message| message.should == 'hello' done }.callback { redis.publish('channel', 'hello') } end end it "should run the passed proc when message received on channel" do connect do |redis| proc = Proc.new { |message| message.should == 'hello' done } redis.pubsub.subscribe("channel", proc).callback { redis.publish('channel', 'hello') } end end end describe "unsubscribing" do it "should allow unsubscribing a single callback without unsubscribing from redis" do connect do |redis| proc1 = Proc.new { |message| fail } proc2 = Proc.new { |message| message.should == 'hello' done } redis.pubsub.subscribe("channel", proc1) redis.pubsub.subscribe("channel", proc2).callback { redis.pubsub.unsubscribe_proc("channel", proc1) redis.publish("channel", "hello") } end end it "should unsubscribe from redis on last proc unsubscription" do connect do |redis| proc = Proc.new { |message| } redis.pubsub.subscribe("channel", proc).callback { |subs_count| subs_count.should == 1 redis.pubsub.unsubscribe_proc("channel", proc).callback { # Slightly awkward way to check that unsubscribe happened: redis.pubsub.subscribe('channel2').callback { |count| # If count is 1 this implies that channel unsubscribed count.should == 1 done } } } end end it "should allow unsubscribing from redis channel, including all callbacks, and return deferrable for redis unsubscribe" do connect do |redis| # Raw pubsub event redis.pubsub.on('message') { |channel, message| fail } # Block subscription redis.pubsub.subscribe("channel") { |m| fail } # block # Proc example df = redis.pubsub.subscribe("channel", Proc.new { |m| fail }) df.callback { redis.pubsub.unsubscribe("channel").callback { |remaining_subs| remaining_subs.should == 0 redis.publish("channel", "hello") { done } } } end end end it "should expose raw pubsub events from redis" do channel = "channel" callback_count = 0 connect do |redis| redis.pubsub.on(:subscribe) { |channel, subscription_count| # 2. Get subscribe callback callback_count += 1 channel.should == channel subscription_count.should == 1 # 3. Publish on channel redis.publish(channel, 'foo') } redis.pubsub.on(:message) { |channel, message| # 4. Get message callback callback_count += 1 channel.should == channel message.should == 'foo' callback_count.should == 2 done } # 1. Subscribe to channel redis.pubsub.subscribe(channel) end end it "should resubscribe to all channels on reconnect" do callback_count = 0 connect do |redis| # 1. Subscribe to channels redis.pubsub.subscribe('channel1') { callback_count += 1 } redis.pubsub.subscribe('channel2') { callback_count += 1 EM.next_tick { # 4. Success if both messages have been received callback_count.should == 2 done } }.callback { |subscription_count| subscription_count.should == 2 # 2. Subscriptions complete. Now force disconnect redis.pubsub.instance_variable_get(:@connection).close_connection EM.add_timer(0.1) { # 3. After giving time to reconnect publish to both channels redis.publish('channel1', 'foo') redis.publish('channel2', 'bar') } } end end end describe EventMachine::Hiredis::PubsubClient, 'p(un)subscribe' do describe "psubscribing" do it "should return deferrable which succeeds with psubscribe call result" do connect do |redis| df = redis.pubsub.psubscribe("channel") { } df.should be_kind_of(EventMachine::DefaultDeferrable) df.callback { |subscription_count| # Subscribe response from redis - indicates that subscription has # succeeded and that the current connection has a single # subscription subscription_count.should == 1 done } end end it "should run the passed block when message received" do connect do |redis| redis.pubsub.psubscribe("channel:*") { |channel, message| channel.should == 'channel:foo' message.should == 'hello' done }.callback { redis.publish('channel:foo', 'hello') } end end it "should run the passed proc when message received on channel" do connect do |redis| proc = Proc.new { |channel, message| channel.should == 'channel:foo' message.should == 'hello' done } redis.pubsub.psubscribe("channel:*", proc).callback { redis.publish('channel:foo', 'hello') } end end end describe "punsubscribing" do it "should allow punsubscribing a single callback without punsubscribing from redis" do connect do |redis| proc1 = Proc.new { |channel, message| fail } proc2 = Proc.new { |channel, message| channel.should == 'channel:foo' message.should == 'hello' done } redis.pubsub.psubscribe("channel:*", proc1) redis.pubsub.psubscribe("channel:*", proc2).callback { redis.pubsub.punsubscribe_proc("channel:*", proc1) redis.publish("channel:foo", "hello") } end end it "should punsubscribe from redis on last proc punsubscription" do connect do |redis| proc = Proc.new { |message| } redis.pubsub.psubscribe("channel:*", proc).callback { |subs_count| subs_count.should == 1 redis.pubsub.punsubscribe_proc("channel:*", proc).callback { # Slightly awkward way to check that unsubscribe happened: redis.pubsub.psubscribe('channel2').callback { |count| # If count is 1 this implies that channel unsubscribed count.should == 1 done } } } end end it "should allow punsubscribing from redis channel, including all callbacks, and return deferrable for redis punsubscribe" do connect do |redis| # Raw pubsub event redis.pubsub.on('pmessage') { |pattern, channel, message| fail } # Block subscription redis.pubsub.psubscribe("channel") { |c, m| fail } # block # Proc example df = redis.pubsub.psubscribe("channel", Proc.new { |c, m| fail }) df.callback { redis.pubsub.punsubscribe("channel").callback { |remaining_subs| remaining_subs.should == 0 redis.publish("channel", "hello") { done } } } end end end it "should expose raw pattern pubsub events from redis" do callback_count = 0 connect do |redis| redis.pubsub.on(:psubscribe) { |pattern, subscription_count| # 2. Get subscribe callback callback_count += 1 pattern.should == "channel:*" subscription_count.should == 1 # 3. Publish on channel redis.publish('channel:foo', 'foo') } redis.pubsub.on(:pmessage) { |pattern, channel, message| # 4. Get message callback callback_count += 1 pattern.should == 'channel:*' channel.should == 'channel:foo' message.should == 'foo' callback_count.should == 2 done } # 1. Subscribe to channel redis.pubsub.psubscribe('channel:*') end end it "should resubscribe to all pattern subscriptions on reconnect" do callback_count = 0 connect do |redis| # 1. Subscribe to channels redis.pubsub.psubscribe('foo:*') { |channel, message| channel.should == 'foo:a' message.should == 'hello foo' callback_count += 1 } redis.pubsub.psubscribe('bar:*') { |channel, message| channel.should == 'bar:b' message.should == 'hello bar' callback_count += 1 EM.next_tick { # 4. Success if both messages have been received callback_count.should == 2 done } }.callback { |subscription_count| subscription_count.should == 2 # 2. Subscriptions complete. Now force disconnect redis.pubsub.instance_variable_get(:@connection).close_connection EM.add_timer(0.1) { # 3. After giving time to reconnect publish to both channels redis.publish('foo:a', 'hello foo') redis.publish('bar:b', 'hello bar') } } end end end em-hiredis-0.2.1/examples/0000755000175000017500000000000012135210520015527 5ustar avtobiffavtobiffem-hiredis-0.2.1/examples/pubsub_basics.rb0000644000175000017500000000065712135210520020710 0ustar avtobiffavtobiff$:.unshift(File.expand_path('../../lib', __FILE__)) require 'em-hiredis' EM.run { redis = EM::Hiredis.connect puts "Subscribing" redis.pubsub.subscribe("foo") { |msg| p [:sub1, msg] } redis.pubsub.psubscribe("f*") { |msg| p [:sub2, msg] } EM.add_periodic_timer(1) { redis.publish("foo", "Hello") } EM.add_timer(5) { puts "Unsubscribing sub1" redis.pubsub.unsubscribe("foo") } } em-hiredis-0.2.1/examples/getting_started.rb0000644000175000017500000000057112135210520021246 0ustar avtobiffavtobiff$:.unshift(File.expand_path('../../lib', __FILE__)) require 'em-hiredis' EM.run { redis = EM::Hiredis.connect redis.sadd('aset', 'member').callback { response_deferrable = redis.hget('aset', 'member') response_deferrable.errback { |e| p e # => # p e.redis_error } } } em-hiredis-0.2.1/examples/pubsub_raw.rb0000644000175000017500000000112112135210520020220 0ustar avtobiffavtobiff$:.unshift(File.expand_path('../../lib', __FILE__)) require 'em-hiredis' EM.run { # Create two connections, one will be used for subscribing redis = EM::Hiredis.connect pubsub = redis.pubsub pubsub.subscribe('bar.0').callback { puts "Subscribed" } pubsub.psubscribe('bar.*') pubsub.on(:message) { |channel, message| p [:message, channel, message] } pubsub.on(:pmessage) { |key, channel, message| p [:pmessage, key, channel, message] } EM.add_periodic_timer(1) { redis.publish("bar.#{rand(2)}", "hello").errback { |e| p [:publisherror, e] } } } em-hiredis-0.2.1/examples/pubsub_more.rb0000644000175000017500000000271712135210520020405 0ustar avtobiffavtobiff$:.unshift(File.expand_path('../../lib', __FILE__)) require 'em-hiredis' EM.run { redis = EM::Hiredis.connect # If you pass a block to subscribe it will be called whenever a message # is received on this channel redis.pubsub.subscribe('foo') { |message| puts "Block received #{message}" } # You can also pass any other object which responds to call if you wish callback = Proc.new { |message| "Proc received #{message}" } df = redis.pubsub.subscribe('foo', callback) # All calls return a deferrable df.callback { |reply| p [:subscription_succeeded, reply] } # Passing such an object is useful if you want to unsubscribe redis.pubsub.unsubscribe_proc('foo', callback) # Or if you want to call a method on a certain object class Thing def receive_message(message) puts "Thing received #{message}" end end redis.pubsub.subscribe('bar', Thing.new.method(:receive_message)) # You can also get all the following raw events: # message pmessage subscribe unsubscribe psubscribe punsubscribe redis.pubsub.on(:message) { |channel, message| p [:message_received, channel, message] } redis.pubsub.on(:unsubscribe) { |channel, remaining_subscriptions| p [:unsubscribe_happened, channel, remaining_subscriptions] } EM.add_timer(1) { # You can also unsubscribe completely from a channel redis.pubsub.unsubscribe('foo') # Publishing events redis.publish('bar', 'Hello') } } em-hiredis-0.2.1/Gemfile0000644000175000017500000000004612135210520015204 0ustar avtobiffavtobiffsource "http://rubygems.org" gemspec em-hiredis-0.2.1/Rakefile0000644000175000017500000000027512135210520015362 0ustar avtobiffavtobiffrequire 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' desc 'Default: run specs.' task :default => :spec desc "Run specs" RSpec::Core::RakeTask.new do |t| end em-hiredis-0.2.1/LICENCE0000644000175000017500000000204612135210520014700 0ustar avtobiffavtobiffCopyright (C) 2011 by Martyn Loughran Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.