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.
@@ -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