jodosha-redis-store 0.3.0 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +4 -4
- data/Rakefile +1 -1
- data/lib/redis-store.rb +1 -0
- data/lib/redis/server.rb +225 -0
- data/redis-store.gemspec +4 -4
- data/spec/config/single.conf +1 -1
- data/spec/redis/server_spec.rb +25 -0
- metadata +5 -2
data/README.textile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
h1.
|
1
|
+
h1. Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks
|
2
2
|
|
3
3
|
h2. Installation
|
4
4
|
|
5
|
-
Download and install Redis from http://code.google.com/p/redis/
|
5
|
+
Download and install Redis from "http://code.google.com/p/redis/":http://code.google.com/p/redis/
|
6
6
|
|
7
7
|
wget "http://redis.googlecode.com/files/redis-0.094.tar.gz":http://redis.googlecode.com/files/redis-0.094.tar.gz
|
8
8
|
tar -zxvf redis-0.094.tar.gz
|
@@ -26,7 +26,7 @@ h3. How to use with Rails
|
|
26
26
|
|
27
27
|
h3. How to use with Merb
|
28
28
|
|
29
|
-
dependency "jodosha-redis-store", "0.3.
|
29
|
+
dependency "jodosha-redis-store", "0.3.5"
|
30
30
|
dependency("merb-cache", merb_gems_version) do
|
31
31
|
Merb::Cache.setup do
|
32
32
|
register(:redis, Merb::Cache::RedisStore, :servers => ["127.0.0.1:6379"])
|
@@ -65,7 +65,7 @@ h3. How to use with Rails
|
|
65
65
|
|
66
66
|
h3. How to use with Merb
|
67
67
|
|
68
|
-
dependency "jodosha-redis-store", "0.3.
|
68
|
+
dependency "jodosha-redis-store", "0.3.5"
|
69
69
|
Merb::Config.use do |c|
|
70
70
|
c[:session_store] = 'redis'
|
71
71
|
end
|
data/Rakefile
CHANGED
data/lib/redis-store.rb
CHANGED
data/lib/redis/server.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
##
|
3
|
+
# This class represents a redis server instance.
|
4
|
+
|
5
|
+
class Server
|
6
|
+
|
7
|
+
##
|
8
|
+
# The host the redis server is running on.
|
9
|
+
|
10
|
+
attr_reader :host
|
11
|
+
|
12
|
+
##
|
13
|
+
# The port the redis server is listening on.
|
14
|
+
|
15
|
+
attr_reader :port
|
16
|
+
|
17
|
+
##
|
18
|
+
#
|
19
|
+
|
20
|
+
attr_reader :replica
|
21
|
+
|
22
|
+
##
|
23
|
+
# The time of next retry if the connection is dead.
|
24
|
+
|
25
|
+
attr_reader :retry
|
26
|
+
|
27
|
+
##
|
28
|
+
# A text status string describing the state of the server.
|
29
|
+
|
30
|
+
attr_reader :status
|
31
|
+
|
32
|
+
##
|
33
|
+
# Create a new Redis::Server object for the redis instance
|
34
|
+
# listening on the given host and port.
|
35
|
+
|
36
|
+
def initialize(host, port = DEFAULT_PORT, timeout = 10, size = 5)
|
37
|
+
raise ArgumentError, "No host specified" if host.nil? or host.empty?
|
38
|
+
raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
|
39
|
+
|
40
|
+
@host = host
|
41
|
+
@port = port.to_i
|
42
|
+
|
43
|
+
@retry = nil
|
44
|
+
@status = 'NOT CONNECTED'
|
45
|
+
@timeout = timeout
|
46
|
+
@size = size
|
47
|
+
|
48
|
+
@reserved_sockets = {}
|
49
|
+
|
50
|
+
@mutex = Monitor.new
|
51
|
+
@queue = @mutex.new_cond
|
52
|
+
|
53
|
+
@sockets = []
|
54
|
+
@checked_out = []
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Return a string representation of the server object.
|
59
|
+
def inspect
|
60
|
+
"<Redis::Server: %s:%d (%s)>" % [@host, @port, @status]
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Try to connect to the redis server targeted by this object.
|
65
|
+
# Returns the connected socket object on success or nil on failure.
|
66
|
+
|
67
|
+
def socket
|
68
|
+
if socket = @reserved_sockets[current_connection_id]
|
69
|
+
socket
|
70
|
+
else
|
71
|
+
@reserved_sockets[current_connection_id] = checkout
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def connect_to(host, port, timeout=nil)
|
76
|
+
addrs = Socket.getaddrinfo(host, nil)
|
77
|
+
addr = addrs.detect { |ad| ad[0] == 'AF_INET' }
|
78
|
+
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
79
|
+
if timeout
|
80
|
+
secs = Integer(timeout)
|
81
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
82
|
+
optval = [secs, usecs].pack("l_2")
|
83
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
84
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
85
|
+
end
|
86
|
+
sock.connect(Socket.pack_sockaddr_in(port, addr[3]))
|
87
|
+
sock
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Close the connection to the redis server targeted by this
|
92
|
+
# object. The server is not considered dead.
|
93
|
+
|
94
|
+
def close
|
95
|
+
@reserved_sockets.each do |name,sock|
|
96
|
+
checkin sock
|
97
|
+
end
|
98
|
+
@reserved_sockets = {}
|
99
|
+
@sockets.each do |sock|
|
100
|
+
sock.close
|
101
|
+
end
|
102
|
+
@sockets = []
|
103
|
+
@status = "NOT CONNECTED"
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Mark the server as dead and close its socket.
|
108
|
+
def mark_dead(sock, error)
|
109
|
+
sock.close if sock && !sock.closed?
|
110
|
+
sock = nil
|
111
|
+
|
112
|
+
reason = "#{error.class.name}: #{error.message}"
|
113
|
+
@status = sprintf "%s:%s DEAD (%s)", @host, @port, reason
|
114
|
+
puts @status
|
115
|
+
end
|
116
|
+
|
117
|
+
protected
|
118
|
+
def new_socket
|
119
|
+
sock = nil
|
120
|
+
begin
|
121
|
+
sock = connect_to(@host, @port, @timeout)
|
122
|
+
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
123
|
+
@status = 'CONNECTED'
|
124
|
+
rescue Errno::EPIPE, Errno::ECONNREFUSED => e
|
125
|
+
if sock
|
126
|
+
puts "Socket died... socket: #{sock.inspect}\n" if $debug
|
127
|
+
sock.close
|
128
|
+
end
|
129
|
+
rescue SocketError, SystemCallError, IOError => err
|
130
|
+
puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
|
131
|
+
mark_dead sock, err
|
132
|
+
end
|
133
|
+
|
134
|
+
return sock
|
135
|
+
end
|
136
|
+
|
137
|
+
def checkout
|
138
|
+
@mutex.synchronize do
|
139
|
+
loop do
|
140
|
+
socket = if @checked_out.size < @sockets.size
|
141
|
+
checkout_existing_socket
|
142
|
+
elsif @sockets.size < @size
|
143
|
+
checkout_new_socket
|
144
|
+
end
|
145
|
+
return socket if socket
|
146
|
+
# No sockets available; wait for one
|
147
|
+
if @queue.wait(@timeout)
|
148
|
+
next
|
149
|
+
else
|
150
|
+
# try looting dead threads
|
151
|
+
clear_stale_cached_sockets!
|
152
|
+
if @size == @checked_out.size
|
153
|
+
raise RedisError, "could not obtain a socket connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it."
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def checkin(socket)
|
161
|
+
@mutex.synchronize do
|
162
|
+
@checked_out.delete socket
|
163
|
+
@queue.signal
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def checkout_new_socket
|
168
|
+
s = new_socket
|
169
|
+
@sockets << s
|
170
|
+
checkout_and_verify(s)
|
171
|
+
end
|
172
|
+
|
173
|
+
def checkout_existing_socket
|
174
|
+
s = (@sockets - @checked_out).first
|
175
|
+
checkout_and_verify(s)
|
176
|
+
end
|
177
|
+
|
178
|
+
def clear_stale_cached_sockets!
|
179
|
+
remove_stale_cached_threads!(@reserved_sockets) do |name, socket|
|
180
|
+
checkin socket
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def remove_stale_cached_threads!(cache, &block)
|
185
|
+
keys = Set.new(cache.keys)
|
186
|
+
|
187
|
+
Thread.list.each do |thread|
|
188
|
+
keys.delete(thread.object_id) if thread.alive?
|
189
|
+
end
|
190
|
+
keys.each do |key|
|
191
|
+
next unless cache.has_key?(key)
|
192
|
+
block.call(key, cache[key])
|
193
|
+
cache.delete(key)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
def current_connection_id #:nodoc:
|
199
|
+
Thread.current.object_id
|
200
|
+
end
|
201
|
+
|
202
|
+
def checkout_and_verify(s)
|
203
|
+
s = verify!(s)
|
204
|
+
@checked_out << s
|
205
|
+
s
|
206
|
+
end
|
207
|
+
|
208
|
+
def verify!(s)
|
209
|
+
reconnect!(s) unless active?(s)
|
210
|
+
end
|
211
|
+
|
212
|
+
def reconnect!(s)
|
213
|
+
s.close
|
214
|
+
connect_to(@host, @port, @timeout)
|
215
|
+
end
|
216
|
+
|
217
|
+
def active?(s)
|
218
|
+
begin
|
219
|
+
s.write("\0")
|
220
|
+
Timeout.timeout(0.1){ s.read }
|
221
|
+
rescue Exception
|
222
|
+
false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
data/redis-store.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "redis-store"
|
3
|
-
s.version = "0.3.
|
4
|
-
s.date = "2009-05-
|
3
|
+
s.version = "0.3.5"
|
4
|
+
s.date = "2009-05-07"
|
5
5
|
s.summary = "Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks."
|
6
6
|
s.author = "Luca Guidi"
|
7
7
|
s.email = "guidi.luca@gmail.com"
|
8
8
|
s.homepage = "http://lucaguidi.com"
|
9
9
|
s.description = "Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks."
|
10
10
|
s.has_rdoc = true
|
11
|
-
s.files = ["MIT-LICENSE", "README.textile", "Rakefile", "lib/cache/merb/redis_store.rb", "lib/cache/rails/redis_store.rb", "lib/cache/sinatra/redis_store.rb", "lib/rack/cache/redis_entitystore.rb", "lib/rack/cache/redis_metastore.rb", "lib/rack/session/merb.rb", "lib/rack/session/redis.rb", "lib/redis-store.rb", "lib/redis/distributed_marshaled_redis.rb", "lib/redis/marshaled_redis.rb", "lib/redis/redis.rb", "lib/redis/redis_factory.rb", "redis-store.gemspec", "spec/cache/merb/redis_store_spec.rb", "spec/cache/rails/redis_store_spec.rb", "spec/cache/sinatra/redis_store_spec.rb", "spec/config/master.conf", "spec/config/single.conf", "spec/config/slave.conf", "spec/rack/cache/entitystore/pony.jpg", "spec/rack/cache/entitystore/redis_spec.rb", "spec/rack/cache/metastore/redis_spec.rb", "spec/rack/session/redis_spec.rb", "spec/redis/distributed_marshaled_redis_spec.rb", "spec/redis/marshaled_redis_spec.rb", "spec/redis/redis_factory_spec.rb", "spec/spec_helper.rb"]
|
12
|
-
s.test_files = ["spec/cache/merb/redis_store_spec.rb", "spec/cache/rails/redis_store_spec.rb", "spec/cache/sinatra/redis_store_spec.rb", "spec/rack/cache/entitystore/redis_spec.rb", "spec/rack/cache/metastore/redis_spec.rb", "spec/rack/session/redis_spec.rb", "spec/redis/distributed_marshaled_redis_spec.rb", "spec/redis/marshaled_redis_spec.rb", "spec/redis/redis_factory_spec.rb"]
|
11
|
+
s.files = ["MIT-LICENSE", "README.textile", "Rakefile", "lib/cache/merb/redis_store.rb", "lib/cache/rails/redis_store.rb", "lib/cache/sinatra/redis_store.rb", "lib/rack/cache/redis_entitystore.rb", "lib/rack/cache/redis_metastore.rb", "lib/rack/session/merb.rb", "lib/rack/session/redis.rb", "lib/redis-store.rb", "lib/redis/distributed_marshaled_redis.rb", "lib/redis/marshaled_redis.rb", "lib/redis/redis.rb", "lib/redis/redis_factory.rb", "lib/redis/server.rb", "redis-store.gemspec", "spec/cache/merb/redis_store_spec.rb", "spec/cache/rails/redis_store_spec.rb", "spec/cache/sinatra/redis_store_spec.rb", "spec/config/master.conf", "spec/config/single.conf", "spec/config/slave.conf", "spec/rack/cache/entitystore/pony.jpg", "spec/rack/cache/entitystore/redis_spec.rb", "spec/rack/cache/metastore/redis_spec.rb", "spec/rack/session/redis_spec.rb", "spec/redis/distributed_marshaled_redis_spec.rb", "spec/redis/marshaled_redis_spec.rb", "spec/redis/redis_factory_spec.rb", "spec/redis/server_spec.rb", "spec/spec_helper.rb"]
|
12
|
+
s.test_files = ["spec/cache/merb/redis_store_spec.rb", "spec/cache/rails/redis_store_spec.rb", "spec/cache/sinatra/redis_store_spec.rb", "spec/rack/cache/entitystore/redis_spec.rb", "spec/rack/cache/metastore/redis_spec.rb", "spec/rack/session/redis_spec.rb", "spec/redis/distributed_marshaled_redis_spec.rb", "spec/redis/marshaled_redis_spec.rb", "spec/redis/redis_factory_spec.rb", "spec/redis/server_spec.rb"]
|
13
13
|
s.extra_rdoc_files = ["README.textile"]
|
14
14
|
end
|
data/spec/config/single.conf
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "/../spec_helper")
|
2
|
+
|
3
|
+
describe "Redis::Server" do
|
4
|
+
before(:each) do
|
5
|
+
@server = Server.new 'localhost', '6379'
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should have a connection pool" do
|
9
|
+
@server.instance_variable_get(:@sockets).should_not be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should checkout active connections" do
|
13
|
+
threads = []
|
14
|
+
100.times do
|
15
|
+
threads << Thread.new do
|
16
|
+
lambda {
|
17
|
+
sleep 6 # redis will close idle connections in the meanwhile
|
18
|
+
socket = @server.socket
|
19
|
+
socket.write("INFO\r\n")
|
20
|
+
socket.read(1)
|
21
|
+
}.should_not raise_error(Exception)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jodosha-redis-store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-07 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -37,6 +37,7 @@ files:
|
|
37
37
|
- lib/redis/marshaled_redis.rb
|
38
38
|
- lib/redis/redis.rb
|
39
39
|
- lib/redis/redis_factory.rb
|
40
|
+
- lib/redis/server.rb
|
40
41
|
- redis-store.gemspec
|
41
42
|
- spec/cache/merb/redis_store_spec.rb
|
42
43
|
- spec/cache/rails/redis_store_spec.rb
|
@@ -51,6 +52,7 @@ files:
|
|
51
52
|
- spec/redis/distributed_marshaled_redis_spec.rb
|
52
53
|
- spec/redis/marshaled_redis_spec.rb
|
53
54
|
- spec/redis/redis_factory_spec.rb
|
55
|
+
- spec/redis/server_spec.rb
|
54
56
|
- spec/spec_helper.rb
|
55
57
|
has_rdoc: true
|
56
58
|
homepage: http://lucaguidi.com
|
@@ -88,3 +90,4 @@ test_files:
|
|
88
90
|
- spec/redis/distributed_marshaled_redis_spec.rb
|
89
91
|
- spec/redis/marshaled_redis_spec.rb
|
90
92
|
- spec/redis/redis_factory_spec.rb
|
93
|
+
- spec/redis/server_spec.rb
|