methodmissing_hiredis 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,2 @@
1
+ require "hiredis/version"
2
+ require "hiredis/connection"
@@ -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,2 @@
1
+ require "hiredis/ext/hiredis_ext"
2
+ require "hiredis/version"
@@ -0,0 +1,10 @@
1
+ module Hiredis
2
+ begin
3
+ require "hiredis/ext/reader"
4
+ Reader = Ext::Reader
5
+ rescue LoadError
6
+ warn "WARNING: could not load hiredis extension, using (slower) pure Ruby implementation."
7
+ require "hiredis/ruby/reader"
8
+ Reader = Ruby::Reader
9
+ end
10
+ 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