ruby-redis-portertech 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ require 'logger'
2
+
3
+ module Redis
4
+ class Logger < ::Logger
5
+
6
+ # Redis levels are: DEBUG < INFO < NOTICE < WARNING
7
+ # This logger inserts support for NOTICE
8
+
9
+ def initialize(logdev, *args)
10
+ super
11
+ @raw_logdev = logdev
12
+ @default_formatter = proc { |severity, datetime, progname, msg|
13
+ mark = case severity
14
+ when 'DEBUG' then '.'
15
+ when 'INFO' then '-'
16
+ when 'NOTE' then '*'
17
+ when 'WARN' then '#'
18
+ else '!'
19
+ end
20
+ "[#{Process.pid}] #{datetime.strftime '%d %b %H:%H:%S'} #{mark} #{msg}\n"
21
+ }
22
+ end
23
+
24
+ def flush
25
+ @raw_logdev.flush if @raw_logdev.respond_to? :flush
26
+ end
27
+
28
+ module Severity
29
+ # logger.rb says "max 5 char" for labels
30
+ SEV_LABEL = %w(DEBUG INFO NOTE WARN ERROR FATAL ANY)
31
+ DEBUG = 0
32
+ INFO = 1
33
+ NOTICE = 2
34
+ WARN = 3
35
+ ERROR = 4
36
+ FATAL = 5
37
+ UNKNOWN = 6
38
+ end
39
+ include Severity
40
+
41
+ def notice(progname = nil, &block)
42
+ add(NOTICE, nil, progname, &block)
43
+ end
44
+ def warn(progname = nil, &block)
45
+ add(WARN, nil, progname, &block)
46
+ end
47
+ def error(progname = nil, &block)
48
+ add(ERROR, nil, progname, &block)
49
+ end
50
+ def fatal(progname = nil, &block)
51
+ add(FATAL, nil, progname, &block)
52
+ end
53
+ def unknown(progname = nil, &block)
54
+ add(UNKNOWN, nil, progname, &block)
55
+ end
56
+
57
+ def notice?; @level <= NOTICE; end
58
+ def warn?; @level <= WARN; end
59
+ def error?; @level <= ERROR; end
60
+ def fatal?; @level <= FATAL; end
61
+
62
+ private
63
+
64
+ def format_severity(severity)
65
+ SEV_LABEL[severity] || 'ANY'
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,107 @@
1
+ module Redis
2
+ module Protocol
3
+
4
+ def initialize *args
5
+ @reader = Reader.new
6
+ @multi = nil
7
+ @deferred = nil
8
+ @watcher = nil
9
+ super
10
+ end
11
+
12
+ def unbind
13
+ @deferred.unbind if @deferred
14
+ @watcher.unbind if @watcher
15
+ super
16
+ end
17
+
18
+ # Process incoming redis protocol
19
+ def receive_data data
20
+ return unless @reader
21
+ @reader.feed data
22
+ until (strings = @reader.gets) == false
23
+ if @multi and !%w{WATCH DISCARD MULTI EXEC DEBUG}.include?(strings[0].upcase)
24
+ @multi << strings
25
+ send_redis :'+QUEUED'
26
+ else
27
+ result = __send__ "redis_#{strings[0].upcase}", *strings[1..-1]
28
+ if Integer === result
29
+ send_data ":#{result}\r\n"
30
+ elsif EventMachine::Deferrable === result
31
+ @deferred.unbind if @deferred and @deferred != result
32
+ @deferred = result
33
+ elsif result == :quit
34
+ @reader = nil
35
+ close_connection_after_writing
36
+ break
37
+ elsif result != :exec
38
+ send_redis result
39
+ end
40
+ end
41
+ end
42
+ rescue StandardError, LoadError, SyntaxError => e
43
+ # This sometimes comes in handy for the TCL tests
44
+ # @logger.warn "#{e.class}:/#{e.backtrace[0]} #{e.message}"
45
+ # e.backtrace[1..-1].each {|bt| @logger.warn bt}
46
+ send_data "-ERR #{e.class.name}: #{e.message}\r\n"
47
+ end
48
+
49
+ def redis_WATCH *keys
50
+ raise 'WATCH inside MULTI is not allowed' if @multi
51
+ @watcher ||= Database::Watcher.new
52
+ @watcher.bind @database, *keys
53
+ :'+OK'
54
+ end
55
+
56
+ def redis_UNWATCH
57
+ if @watcher
58
+ @watcher.unbind
59
+ @watcher = nil
60
+ end
61
+ :'+OK'
62
+ end
63
+
64
+ def redis_MULTI
65
+ raise 'MULTI nesting not allowed' if @multi
66
+ @multi = []
67
+ :'+OK'
68
+ end
69
+
70
+ def redis_DISCARD
71
+ redis_UNWATCH
72
+ @multi = nil
73
+ :'+OK'
74
+ end
75
+
76
+ def redis_EXEC
77
+ if @watcher
78
+ still_bound = @watcher.bound
79
+ redis_UNWATCH
80
+ unless still_bound
81
+ @multi = nil
82
+ return :'*-1'
83
+ end
84
+ end
85
+ send_data "*#{@multi.size}\r\n"
86
+ response = []
87
+ @multi.each do |strings|
88
+ begin
89
+ result = __send__ "redis_#{strings[0].upcase}", *strings[1..-1]
90
+ rescue StandardError, LoadError, SyntaxError => e
91
+ result = e
92
+ end
93
+ if EventMachine::Deferrable === result
94
+ result.unbind
95
+ send_redis nil
96
+ elsif Exception === result
97
+ send_data "-ERR #{result.class.name}: #{result.message}"
98
+ else
99
+ send_redis result
100
+ end
101
+ end
102
+ @multi = nil
103
+ :exec
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,150 @@
1
+ require 'eventmachine'
2
+
3
+ module Redis
4
+ module PubSub
5
+
6
+ class Subscription
7
+ include EventMachine::Deferrable
8
+
9
+ @@channels ||= {}
10
+ @@pchannels ||= {}
11
+
12
+ def self.publish channel, message
13
+ subs = @@channels[channel] || []
14
+ subs.each do |sub|
15
+ sub.call channel, message
16
+ end
17
+ sent = subs.size
18
+ (@@pchannels || []).each do |pchannel, subs|
19
+ next unless File.fnmatch(pchannel, channel)
20
+ subs.each do |sub|
21
+ sub.pcall pchannel, channel, message
22
+ end
23
+ sent += subs.size
24
+ end
25
+ sent
26
+ end
27
+
28
+ def initialize connection
29
+ @connection = connection
30
+ @subs = []
31
+ @psubs = []
32
+ end
33
+
34
+ def bound
35
+ size > 0
36
+ end
37
+
38
+ def size
39
+ @subs.size + @psubs.size
40
+ end
41
+
42
+ def unbind
43
+ unbind_channels
44
+ unbind_patterns
45
+ end
46
+
47
+ def unbind_channels
48
+ @subs.each do |channel|
49
+ (@@channels[channel] || []).delete self
50
+ end
51
+ @subs.clear
52
+ end
53
+
54
+ def unbind_patterns
55
+ @psubs.each do |channel|
56
+ (@@pchannels[channel] || []).delete self
57
+ end
58
+ @psubs.clear
59
+ end
60
+
61
+ def subscribe channel
62
+ c = (@@channels[channel] ||= [])
63
+ unless c.include? self
64
+ c << self
65
+ @subs << channel
66
+ end
67
+ @connection.send_redis ['subscribe', channel, size]
68
+ end
69
+
70
+ def psubscribe channel
71
+ c = (@@pchannels[channel] ||= [])
72
+ unless c.include? self
73
+ c << self
74
+ @psubs << channel
75
+ end
76
+ @connection.send_redis ['psubscribe', channel, size]
77
+ end
78
+
79
+ def unsubscribe channel
80
+ c = (@@channels[channel] || [])
81
+ @subs.delete channel if c.delete self
82
+ @connection.send_redis ['unsubscribe', channel, size]
83
+ end
84
+
85
+ def punsubscribe channel
86
+ c = (@@pchannels[channel] || [])
87
+ @psubs.delete channel if c.delete self
88
+ @connection.send_redis ['punsubscribe', channel, size]
89
+ end
90
+
91
+ def call channel, message
92
+ @connection.send_redis ['message', channel, message]
93
+ end
94
+
95
+ def pcall pchannel, channel, message
96
+ @connection.send_redis ['pmessage', pchannel, channel, message]
97
+ end
98
+
99
+ end
100
+
101
+ def redis_SUBSCRIBE *channels
102
+ sub = @deferred
103
+ sub = Subscription.new self unless Subscription === sub
104
+ channels.each do |channel|
105
+ sub.subscribe channel
106
+ end
107
+ sub
108
+ end
109
+
110
+ def redis_UNSUBSCRIBE *channels
111
+ sub = @deferred
112
+ sub = Subscription.new self unless Subscription === sub
113
+ if channels.empty?
114
+ sub.unbind_channels
115
+ else
116
+ channels.each do |channel|
117
+ sub.unsubscribe channel
118
+ end
119
+ end
120
+ sub
121
+ end
122
+
123
+ def redis_PSUBSCRIBE *channels
124
+ sub = @deferred
125
+ sub = Subscription.new self unless Subscription === sub
126
+ channels.each do |channel|
127
+ sub.psubscribe channel
128
+ end
129
+ sub
130
+ end
131
+
132
+ def redis_PUNSUBSCRIBE *channels
133
+ sub = @deferred
134
+ sub = Subscription.new self unless Subscription === sub
135
+ if channels.empty?
136
+ sub.unbind_patterns
137
+ else
138
+ channels.each do |channel|
139
+ sub.punsubscribe channel
140
+ end
141
+ end
142
+ sub
143
+ end
144
+
145
+ def redis_PUBLISH channel, message
146
+ Subscription.publish channel, message
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,175 @@
1
+ module Redis
2
+ class Reader < Array
3
+
4
+ # Minimize the amount of memory copying. The primary
5
+ # performance trick is to String#split and work with that.
6
+ # This is almost as fast as hiredis/reader plus it supports servers
7
+
8
+ def initialize options={}
9
+ super()
10
+ @multibulk_limit = options[:multibulk_limit] || 1024*1024
11
+ @bulk_limit = options[:bulk_limit] || 512*1024*1024
12
+ @buffer_overflow = false
13
+ flush
14
+ end
15
+
16
+ def feed data
17
+ return if @buffer_overflow
18
+ if reduce(0){|t,e|t+e.bytesize} > @bulk_limit + 2
19
+ @buffer_overflow = true
20
+ flush
21
+ return
22
+ end
23
+ push data
24
+ end
25
+
26
+ def gets
27
+ raise 'buffer overflow' if @buffer_overflow
28
+ if @completed.empty?
29
+ unshift_split if @split
30
+ frame do |str|
31
+ @elements << str
32
+ if @remaining != 0
33
+ if @remaining < 0
34
+ @remaining = -@remaining
35
+ @elements = str
36
+ end
37
+ @remaining -= 1
38
+ if @remaining == 0 and !@stack.empty?
39
+ elements = @elements
40
+ @elements, @remaining = @stack.pop
41
+ @elements << elements
42
+ @remaining -= 1
43
+ end
44
+ next unless @remaining == 0
45
+ @completed << @elements
46
+ elsif !@elements.empty?
47
+ @completed << @elements[0]
48
+ end
49
+ @elements = []
50
+ @remaining = 0
51
+ end
52
+ end
53
+ return false if @completed.empty?
54
+ @completed.shift
55
+ end
56
+
57
+ private
58
+
59
+ def flush
60
+ @split = nil
61
+ @pending = nil
62
+ @binary_size = nil
63
+ @remaining = 0
64
+ @elements = []
65
+ @stack = []
66
+ @completed = []
67
+ clear
68
+ end
69
+
70
+ def unshift_split
71
+ unshift @split.join "\n"
72
+ @split = nil
73
+ end
74
+
75
+ # yields redis data until no more found in buffer
76
+ def frame
77
+ while true
78
+ if @binary_size
79
+ s = read_binary @binary_size
80
+ break unless s
81
+ @binary_size = nil
82
+ yield s
83
+ else
84
+ line = read_line
85
+ break unless line
86
+ case line[0..0]
87
+ when '-'
88
+ yield RuntimeError.new line[1..-1]
89
+ when '+'
90
+ yield line[1..-1]
91
+ when ':'
92
+ yield line[1..-1].to_i
93
+ when '*'
94
+ prev_remaining = @remaining
95
+ @remaining = line[1..-1].to_i
96
+ if @remaining == 0
97
+ @remaining = -1
98
+ yield []
99
+ elsif @remaining == -1
100
+ yield nil
101
+ elsif @remaining > @multibulk_limit
102
+ flush
103
+ raise 'invalid multibulk length'
104
+ elsif prev_remaining > 0
105
+ @stack << [@elements, prev_remaining]
106
+ @elements = []
107
+ end
108
+ when '$'
109
+ @binary_size = line[1..-1].to_i
110
+ if @binary_size == -1
111
+ @binary_size = nil
112
+ yield nil
113
+ elsif (@binary_size == 0 and line[1..1] != '0') or @binary_size < 0 or @binary_size > @bulk_limit
114
+ flush
115
+ raise 'invalid bulk length'
116
+ end
117
+ else
118
+ if @remaining > 0
119
+ flush
120
+ raise "expected \"$\", got #{line[0].chr.dump}"
121
+ end
122
+ parts = line.split(' ')
123
+ @remaining = parts.size
124
+ parts.each {|l| yield l}
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # Read a binary redis token, nil if none available
131
+ def read_binary length
132
+ if @split
133
+ if @split.first.size >= length
134
+ result = @split.shift[0...length]
135
+ unshift_split if @split.size == 1
136
+ return result
137
+ end
138
+ unshift_split
139
+ end
140
+ unless @pending
141
+ size = reduce(0){|x,y|x+y.size}
142
+ return nil unless size >= length
143
+ @pending = dup
144
+ clear
145
+ remainder = size - length
146
+ if remainder > 0
147
+ last_string = @pending[-1]
148
+ @pending[-1] = last_string[0...-remainder]
149
+ push last_string[-remainder..-1]
150
+ end
151
+ end
152
+ # eat newline
153
+ return nil unless read_line
154
+ result = @pending.join
155
+ @pending = nil
156
+ result
157
+ end
158
+
159
+ # Read a newline terminated redis token, nil if none available
160
+ def read_line
161
+ unless @split
162
+ @split = join.split "\n", -1
163
+ clear
164
+ end
165
+ if @split.size > 1
166
+ result = @split.shift.chomp "\n"
167
+ else
168
+ result = nil
169
+ end
170
+ unshift_split if @split.size == 1
171
+ result
172
+ end
173
+
174
+ end
175
+ end