methodmissing_hiredis 0.4.6
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/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
|