methodmissing_hiredis 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +28 -0
- data/Rakefile +45 -0
- data/ext/hiredis_ext/connection.c +472 -0
- data/ext/hiredis_ext/extconf.rb +17 -0
- data/ext/hiredis_ext/hiredis_ext.c +13 -0
- data/ext/hiredis_ext/hiredis_ext.h +37 -0
- data/ext/hiredis_ext/reader.c +114 -0
- data/lib/hiredis.rb +2 -0
- data/lib/hiredis/connection.rb +10 -0
- data/lib/hiredis/ext/connection.rb +29 -0
- data/lib/hiredis/ext/reader.rb +2 -0
- data/lib/hiredis/reader.rb +10 -0
- data/lib/hiredis/ruby/connection.rb +298 -0
- data/lib/hiredis/ruby/reader.rb +183 -0
- data/lib/hiredis/version.rb +3 -0
- data/vendor/hiredis/COPYING +29 -0
- data/vendor/hiredis/Makefile +148 -0
- data/vendor/hiredis/async.c +647 -0
- data/vendor/hiredis/async.h +125 -0
- data/vendor/hiredis/dict.c +338 -0
- data/vendor/hiredis/dict.h +126 -0
- data/vendor/hiredis/fmacros.h +16 -0
- data/vendor/hiredis/hiredis.c +1315 -0
- data/vendor/hiredis/hiredis.h +210 -0
- data/vendor/hiredis/net.c +292 -0
- data/vendor/hiredis/net.h +47 -0
- data/vendor/hiredis/sds.c +605 -0
- data/vendor/hiredis/sds.h +88 -0
- data/vendor/hiredis/test.c +686 -0
- metadata +110 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
4
|
+
|
5
|
+
hiredis_dir = File.expand_path(File.join(File.dirname(__FILE__), %w{.. .. vendor hiredis}))
|
6
|
+
unless File.directory?(hiredis_dir)
|
7
|
+
STDERR.puts "vendor/hiredis missing, please checkout its submodule..."
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
# Make sure hiredis is built...
|
12
|
+
system("cd #{hiredis_dir} && make static")
|
13
|
+
|
14
|
+
# Statically link to hiredis (mkmf can't do this for us)
|
15
|
+
$CFLAGS << " -I#{hiredis_dir}"
|
16
|
+
$LDFLAGS << " #{hiredis_dir}/libhiredis.a"
|
17
|
+
create_makefile('hiredis/ext/hiredis_ext')
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#include <stdlib.h>
|
2
|
+
#include <string.h>
|
3
|
+
#include <assert.h>
|
4
|
+
#include "hiredis_ext.h"
|
5
|
+
|
6
|
+
VALUE mod_hiredis;
|
7
|
+
VALUE mod_ext;
|
8
|
+
void Init_hiredis_ext() {
|
9
|
+
mod_hiredis = rb_define_module("Hiredis");
|
10
|
+
mod_ext = rb_define_module_under(mod_hiredis,"Ext");
|
11
|
+
InitReader(mod_ext);
|
12
|
+
InitConnection(mod_ext);
|
13
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#ifndef __HIREDIS_EXT_H
|
2
|
+
#define __HIREDIS_EXT_H
|
3
|
+
|
4
|
+
#include "hiredis.h"
|
5
|
+
#include "ruby.h"
|
6
|
+
|
7
|
+
/* Defined in hiredis_ext.c */
|
8
|
+
extern VALUE mod_hiredis;
|
9
|
+
|
10
|
+
/* Defined in reader.c */
|
11
|
+
extern redisReplyObjectFunctions redisExtReplyObjectFunctions;
|
12
|
+
extern VALUE klass_reader;
|
13
|
+
extern void InitReader(VALUE module);
|
14
|
+
|
15
|
+
/* Defined in connection.c */
|
16
|
+
extern VALUE klass_connection;
|
17
|
+
extern VALUE error_eof;
|
18
|
+
extern void InitConnection(VALUE module);
|
19
|
+
|
20
|
+
/* Borrowed from Nokogiri */
|
21
|
+
#ifndef RSTRING_PTR
|
22
|
+
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
23
|
+
#endif
|
24
|
+
|
25
|
+
#ifndef RSTRING_LEN
|
26
|
+
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
27
|
+
#endif
|
28
|
+
|
29
|
+
#ifndef RARRAY_PTR
|
30
|
+
#define RARRAY_PTR(a) RARRAY(a)->ptr
|
31
|
+
#endif
|
32
|
+
|
33
|
+
#ifndef RARRAY_LEN
|
34
|
+
#define RARRAY_LEN(a) RARRAY(a)->len
|
35
|
+
#endif
|
36
|
+
|
37
|
+
#endif
|
@@ -0,0 +1,114 @@
|
|
1
|
+
#include <assert.h>
|
2
|
+
#include "hiredis_ext.h"
|
3
|
+
|
4
|
+
/* Force encoding on new strings? */
|
5
|
+
static VALUE enc_klass;
|
6
|
+
static ID enc_default_external = 0;
|
7
|
+
static ID str_force_encoding = 0;
|
8
|
+
|
9
|
+
/* Add VALUE to parent when the redisReadTask has a parent.
|
10
|
+
* Note that the parent should always be of type T_ARRAY. */
|
11
|
+
static void *tryParentize(const redisReadTask *task, VALUE v) {
|
12
|
+
if (task && task->parent != NULL) {
|
13
|
+
VALUE parent = (VALUE)task->parent->obj;
|
14
|
+
assert(TYPE(parent) == T_ARRAY);
|
15
|
+
rb_ary_store(parent,task->idx,v);
|
16
|
+
}
|
17
|
+
return (void*)v;
|
18
|
+
}
|
19
|
+
|
20
|
+
static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
|
21
|
+
VALUE v, enc;
|
22
|
+
v = rb_str_new(str,len);
|
23
|
+
|
24
|
+
/* Force default external encoding if possible. */
|
25
|
+
if (enc_default_external) {
|
26
|
+
enc = rb_funcall(enc_klass,enc_default_external,0);
|
27
|
+
v = rb_funcall(v,str_force_encoding,1,enc);
|
28
|
+
}
|
29
|
+
|
30
|
+
if (task->type == REDIS_REPLY_ERROR) {
|
31
|
+
v = rb_funcall(rb_eRuntimeError,rb_intern("new"),1,v);
|
32
|
+
}
|
33
|
+
|
34
|
+
return tryParentize(task,v);
|
35
|
+
}
|
36
|
+
|
37
|
+
static void *createArrayObject(const redisReadTask *task, int elements) {
|
38
|
+
VALUE v = rb_ary_new2(elements);
|
39
|
+
return tryParentize(task,v);
|
40
|
+
}
|
41
|
+
|
42
|
+
static void *createIntegerObject(const redisReadTask *task, long long value) {
|
43
|
+
VALUE v = LL2NUM(value);
|
44
|
+
return tryParentize(task,v);
|
45
|
+
}
|
46
|
+
|
47
|
+
static void *createNilObject(const redisReadTask *task) {
|
48
|
+
return tryParentize(task,Qnil);
|
49
|
+
}
|
50
|
+
|
51
|
+
static void freeObject(void *ptr) {
|
52
|
+
/* Garbage collection will clean things up. */
|
53
|
+
}
|
54
|
+
|
55
|
+
/* Declare our set of reply object functions only once. */
|
56
|
+
redisReplyObjectFunctions redisExtReplyObjectFunctions = {
|
57
|
+
createStringObject,
|
58
|
+
createArrayObject,
|
59
|
+
createIntegerObject,
|
60
|
+
createNilObject,
|
61
|
+
freeObject
|
62
|
+
};
|
63
|
+
|
64
|
+
static void reader_mark(redisReader *reader) {
|
65
|
+
VALUE root = (VALUE)reader->reply;
|
66
|
+
if (root != 0 && TYPE(root) == T_ARRAY) rb_gc_mark(root);
|
67
|
+
}
|
68
|
+
|
69
|
+
static VALUE reader_allocate(VALUE klass) {
|
70
|
+
redisReader *reader = redisReaderCreate();
|
71
|
+
reader->fn = &redisExtReplyObjectFunctions;
|
72
|
+
return Data_Wrap_Struct(klass, reader_mark, redisReaderFree, reader);
|
73
|
+
}
|
74
|
+
|
75
|
+
static VALUE reader_feed(VALUE klass, VALUE str) {
|
76
|
+
redisReader *reader;
|
77
|
+
|
78
|
+
if (TYPE(str) != T_STRING)
|
79
|
+
rb_raise(rb_eTypeError, "not a string");
|
80
|
+
|
81
|
+
Data_Get_Struct(klass, redisReader, reader);
|
82
|
+
redisReaderFeed(reader,RSTRING_PTR(str),(size_t)RSTRING_LEN(str));
|
83
|
+
return INT2NUM(0);
|
84
|
+
}
|
85
|
+
|
86
|
+
static VALUE reader_gets(VALUE klass) {
|
87
|
+
redisReader *reader;
|
88
|
+
VALUE reply;
|
89
|
+
|
90
|
+
Data_Get_Struct(klass, redisReader, reader);
|
91
|
+
if (redisReaderGetReply(reader,(void**)&reply) != REDIS_OK)
|
92
|
+
rb_raise(rb_eRuntimeError,"%s",reader->errstr);
|
93
|
+
|
94
|
+
return reply;
|
95
|
+
}
|
96
|
+
|
97
|
+
VALUE klass_reader;
|
98
|
+
void InitReader(VALUE mod) {
|
99
|
+
klass_reader = rb_define_class_under(mod, "Reader", rb_cObject);
|
100
|
+
rb_define_alloc_func(klass_reader, reader_allocate);
|
101
|
+
rb_define_method(klass_reader, "feed", reader_feed, 1);
|
102
|
+
rb_define_method(klass_reader, "gets", reader_gets, 0);
|
103
|
+
|
104
|
+
/* If the Encoding class is present, #default_external should be used to
|
105
|
+
* determine the encoding for new strings. The "enc_default_external"
|
106
|
+
* ID is non-zero when encoding should be set on new strings. */
|
107
|
+
if (rb_const_defined(rb_cObject, rb_intern("Encoding"))) {
|
108
|
+
enc_klass = rb_const_get(rb_cObject, rb_intern("Encoding"));
|
109
|
+
enc_default_external = rb_intern("default_external");
|
110
|
+
str_force_encoding = rb_intern("force_encoding");
|
111
|
+
} else {
|
112
|
+
enc_default_external = 0;
|
113
|
+
}
|
114
|
+
}
|
data/lib/hiredis.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module Hiredis
|
2
|
+
begin
|
3
|
+
require "hiredis/ext/connection"
|
4
|
+
Connection = Ext::Connection
|
5
|
+
rescue LoadError
|
6
|
+
warn "WARNING: could not load hiredis extension, using (slower) pure Ruby implementation."
|
7
|
+
require "hiredis/ruby/connection"
|
8
|
+
Connection = Ruby::Connection
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "hiredis/ext/hiredis_ext"
|
2
|
+
require "hiredis/version"
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Hiredis
|
6
|
+
module Ext
|
7
|
+
class Connection
|
8
|
+
alias :_disconnect :disconnect
|
9
|
+
|
10
|
+
def disconnect
|
11
|
+
_disconnect
|
12
|
+
ensure
|
13
|
+
@sock = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
alias :_read :read
|
17
|
+
|
18
|
+
def read
|
19
|
+
_read
|
20
|
+
rescue ::EOFError
|
21
|
+
raise Errno::ECONNRESET
|
22
|
+
end
|
23
|
+
|
24
|
+
def sock
|
25
|
+
@sock ||= Socket.for_fd(fileno)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "hiredis/ruby/reader"
|
3
|
+
require "hiredis/version"
|
4
|
+
|
5
|
+
module Hiredis
|
6
|
+
module Ruby
|
7
|
+
class Connection
|
8
|
+
|
9
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
|
10
|
+
|
11
|
+
def self.errno_to_class
|
12
|
+
Errno::Mapping
|
13
|
+
end
|
14
|
+
|
15
|
+
else
|
16
|
+
|
17
|
+
def self.errno_to_class
|
18
|
+
@mapping ||= Hash[Errno.constants.map do |name|
|
19
|
+
klass = Errno.const_get(name)
|
20
|
+
[klass.const_get("Errno"), klass]
|
21
|
+
end]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
27
|
+
|
28
|
+
require "timeout"
|
29
|
+
|
30
|
+
def _connect(host, port, timeout)
|
31
|
+
sock = nil
|
32
|
+
|
33
|
+
begin
|
34
|
+
Timeout.timeout(timeout) do
|
35
|
+
sock = TCPSocket.new(host, port)
|
36
|
+
end
|
37
|
+
rescue SocketError => se
|
38
|
+
raise se.message
|
39
|
+
rescue Timeout::Error
|
40
|
+
raise Errno::ETIMEDOUT
|
41
|
+
end
|
42
|
+
|
43
|
+
sock
|
44
|
+
end
|
45
|
+
|
46
|
+
def _connect_unix(path, timeout)
|
47
|
+
sock = nil
|
48
|
+
|
49
|
+
begin
|
50
|
+
Timeout.timeout(timeout) do
|
51
|
+
sock = UNIXSocket.new(path)
|
52
|
+
end
|
53
|
+
rescue SocketError => se
|
54
|
+
raise se.message
|
55
|
+
rescue Timeout::Error
|
56
|
+
raise Errno::ETIMEDOUT
|
57
|
+
end
|
58
|
+
|
59
|
+
sock
|
60
|
+
end
|
61
|
+
|
62
|
+
def _write(sock, data, timeout)
|
63
|
+
begin
|
64
|
+
Timeout.timeout(timeout) do
|
65
|
+
sock.write(data)
|
66
|
+
end
|
67
|
+
rescue Timeout::Error
|
68
|
+
raise Errno::EAGAIN
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
else
|
73
|
+
|
74
|
+
def _connect(host, port, timeout)
|
75
|
+
error = nil
|
76
|
+
sock = nil
|
77
|
+
|
78
|
+
# Resolve address
|
79
|
+
begin
|
80
|
+
addrinfo = Socket.getaddrinfo(host, port, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
|
81
|
+
rescue SocketError => se
|
82
|
+
raise se.message
|
83
|
+
end
|
84
|
+
|
85
|
+
addrinfo.each do |_, port, name, addr, af|
|
86
|
+
begin
|
87
|
+
sockaddr = Socket.pack_sockaddr_in(port, addr)
|
88
|
+
sock = _connect_sockaddr(af, sockaddr, timeout)
|
89
|
+
rescue => aux
|
90
|
+
case aux
|
91
|
+
when Errno::EAFNOSUPPORT, Errno::ECONNREFUSED
|
92
|
+
error = aux
|
93
|
+
next
|
94
|
+
else
|
95
|
+
# Re-raise
|
96
|
+
raise
|
97
|
+
end
|
98
|
+
else
|
99
|
+
# No errors, awesome!
|
100
|
+
break
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
unless sock
|
105
|
+
# Re-raise last error since the last try obviously failed
|
106
|
+
raise error if error
|
107
|
+
|
108
|
+
# This code path should not happen: getaddrinfo should always return
|
109
|
+
# at least one record, which should either succeed or fail and leave
|
110
|
+
# and error to raise.
|
111
|
+
raise
|
112
|
+
end
|
113
|
+
|
114
|
+
sock
|
115
|
+
end
|
116
|
+
|
117
|
+
def _connect_unix(path, timeout)
|
118
|
+
sockaddr = Socket.pack_sockaddr_un(path)
|
119
|
+
_connect_sockaddr(Socket::AF_UNIX, sockaddr, timeout)
|
120
|
+
end
|
121
|
+
|
122
|
+
def _write(sock, data, timeout)
|
123
|
+
data.force_encoding("binary") if data.respond_to?(:force_encoding)
|
124
|
+
|
125
|
+
begin
|
126
|
+
nwritten = @sock.write_nonblock(data)
|
127
|
+
|
128
|
+
while nwritten < string_size(data)
|
129
|
+
data = data[nwritten..-1]
|
130
|
+
nwritten = @sock.write_nonblock(data)
|
131
|
+
end
|
132
|
+
rescue Errno::EAGAIN
|
133
|
+
if IO.select([], [@sock], [], timeout)
|
134
|
+
# Writable, try again
|
135
|
+
retry
|
136
|
+
else
|
137
|
+
# Timed out, raise
|
138
|
+
raise Errno::EAGAIN
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def _connect_sockaddr(af, sockaddr, timeout)
|
144
|
+
sock = Socket.new(af, Socket::SOCK_STREAM, 0)
|
145
|
+
|
146
|
+
begin
|
147
|
+
sock.connect_nonblock(sockaddr)
|
148
|
+
rescue Errno::EINPROGRESS
|
149
|
+
if IO.select(nil, [sock], nil, timeout)
|
150
|
+
# Writable, check for errors
|
151
|
+
optval = sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR)
|
152
|
+
errno = optval.unpack("i").first
|
153
|
+
|
154
|
+
# Raise socket error if there is any
|
155
|
+
raise self.class.errno_to_class[errno] if errno > 0
|
156
|
+
else
|
157
|
+
# Timeout (TODO: replace with own Timeout class)
|
158
|
+
raise Errno::ETIMEDOUT
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
sock
|
163
|
+
rescue
|
164
|
+
sock.close if sock
|
165
|
+
|
166
|
+
# Re-raise
|
167
|
+
raise
|
168
|
+
end
|
169
|
+
|
170
|
+
private :_connect_sockaddr
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
attr_reader :sock
|
175
|
+
|
176
|
+
def initialize
|
177
|
+
@sock = nil
|
178
|
+
@timeout = nil
|
179
|
+
end
|
180
|
+
|
181
|
+
def connected?
|
182
|
+
!! @sock
|
183
|
+
end
|
184
|
+
|
185
|
+
def connect(host, port, usecs = nil)
|
186
|
+
# Temporarily override timeout on #connect
|
187
|
+
timeout = usecs ? (usecs / 1_000_000.0) : @timeout
|
188
|
+
|
189
|
+
# Optionally disconnect current socket
|
190
|
+
disconnect if connected?
|
191
|
+
|
192
|
+
sock = _connect(host, port, timeout)
|
193
|
+
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
194
|
+
|
195
|
+
@reader = ::Hiredis::Ruby::Reader.new
|
196
|
+
@sock = sock
|
197
|
+
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
|
201
|
+
def connect_unix(path, usecs = 0)
|
202
|
+
# Temporarily override timeout on #connect
|
203
|
+
timeout = usecs ? (usecs / 1_000_000.0) : @timeout
|
204
|
+
|
205
|
+
# Optionally disconnect current socket
|
206
|
+
disconnect if connected?
|
207
|
+
|
208
|
+
sock = _connect_unix(path, timeout)
|
209
|
+
|
210
|
+
@reader = ::Hiredis::Ruby::Reader.new
|
211
|
+
@sock = sock
|
212
|
+
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
|
216
|
+
def disconnect
|
217
|
+
@sock.close
|
218
|
+
rescue
|
219
|
+
ensure
|
220
|
+
@sock = nil
|
221
|
+
end
|
222
|
+
|
223
|
+
def timeout=(usecs)
|
224
|
+
raise ArgumentError.new("timeout cannot be negative") if usecs < 0
|
225
|
+
|
226
|
+
if usecs == 0
|
227
|
+
@timeout = nil
|
228
|
+
else
|
229
|
+
@timeout = usecs / 1_000_000.0
|
230
|
+
end
|
231
|
+
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
def fileno
|
236
|
+
raise "not connected" unless connected?
|
237
|
+
|
238
|
+
@sock.fileno
|
239
|
+
end
|
240
|
+
|
241
|
+
COMMAND_DELIMITER = "\r\n".freeze
|
242
|
+
|
243
|
+
def write(args)
|
244
|
+
command = []
|
245
|
+
command << "*#{args.size}"
|
246
|
+
args.each do |arg|
|
247
|
+
arg = arg.to_s
|
248
|
+
command << "$#{string_size arg}"
|
249
|
+
command << arg
|
250
|
+
end
|
251
|
+
|
252
|
+
data = command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
253
|
+
|
254
|
+
_write(@sock, data, @timeout)
|
255
|
+
|
256
|
+
nil
|
257
|
+
end
|
258
|
+
|
259
|
+
# No-op for now..
|
260
|
+
def flush
|
261
|
+
end
|
262
|
+
|
263
|
+
def read
|
264
|
+
raise "not connected" unless connected?
|
265
|
+
|
266
|
+
while (reply = @reader.gets) == false
|
267
|
+
begin
|
268
|
+
@reader.feed @sock.read_nonblock(1024)
|
269
|
+
rescue Errno::EAGAIN
|
270
|
+
if IO.select([@sock], [], [], @timeout)
|
271
|
+
# Readable, try again
|
272
|
+
retry
|
273
|
+
else
|
274
|
+
# Timed out, raise
|
275
|
+
raise Errno::EAGAIN
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
reply
|
281
|
+
rescue ::EOFError
|
282
|
+
raise Errno::ECONNRESET
|
283
|
+
end
|
284
|
+
|
285
|
+
protected
|
286
|
+
|
287
|
+
if "".respond_to?(:bytesize)
|
288
|
+
def string_size(string)
|
289
|
+
string.to_s.bytesize
|
290
|
+
end
|
291
|
+
else
|
292
|
+
def string_size(string)
|
293
|
+
string.to_s.size
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|