hiredis-futureproof 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +28 -0
  3. data/Rakefile +53 -0
  4. data/ext/hiredis_ext/connection.c +611 -0
  5. data/ext/hiredis_ext/extconf.rb +48 -0
  6. data/ext/hiredis_ext/hiredis_ext.c +15 -0
  7. data/ext/hiredis_ext/hiredis_ext.h +44 -0
  8. data/ext/hiredis_ext/reader.c +124 -0
  9. data/lib/hiredis/connection.rb +10 -0
  10. data/lib/hiredis/ext/connection.rb +29 -0
  11. data/lib/hiredis/ext/reader.rb +2 -0
  12. data/lib/hiredis/reader.rb +10 -0
  13. data/lib/hiredis/ruby/connection.rb +316 -0
  14. data/lib/hiredis/ruby/reader.rb +183 -0
  15. data/lib/hiredis/version.rb +3 -0
  16. data/lib/hiredis.rb +2 -0
  17. data/vendor/hiredis/COPYING +29 -0
  18. data/vendor/hiredis/Makefile +308 -0
  19. data/vendor/hiredis/alloc.c +86 -0
  20. data/vendor/hiredis/alloc.h +91 -0
  21. data/vendor/hiredis/async.c +892 -0
  22. data/vendor/hiredis/async.h +147 -0
  23. data/vendor/hiredis/async_private.h +75 -0
  24. data/vendor/hiredis/dict.c +352 -0
  25. data/vendor/hiredis/dict.h +126 -0
  26. data/vendor/hiredis/fmacros.h +12 -0
  27. data/vendor/hiredis/hiredis.c +1173 -0
  28. data/vendor/hiredis/hiredis.h +336 -0
  29. data/vendor/hiredis/hiredis_ssl.h +127 -0
  30. data/vendor/hiredis/net.c +612 -0
  31. data/vendor/hiredis/net.h +56 -0
  32. data/vendor/hiredis/read.c +739 -0
  33. data/vendor/hiredis/read.h +129 -0
  34. data/vendor/hiredis/sds.c +1289 -0
  35. data/vendor/hiredis/sds.h +278 -0
  36. data/vendor/hiredis/sdsalloc.h +44 -0
  37. data/vendor/hiredis/sockcompat.c +248 -0
  38. data/vendor/hiredis/sockcompat.h +92 -0
  39. data/vendor/hiredis/ssl.c +526 -0
  40. data/vendor/hiredis/test.c +1387 -0
  41. data/vendor/hiredis/win32.h +56 -0
  42. metadata +128 -0
@@ -0,0 +1,48 @@
1
+ require 'mkmf'
2
+
3
+ build_hiredis = true
4
+ unless have_header('sys/socket.h')
5
+ puts "Could not find <sys/socket.h> (Likely Windows)."
6
+ puts "Skipping building hiredis. The slower, pure-ruby implementation will be used instead."
7
+ build_hiredis = false
8
+ end
9
+
10
+ RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
11
+
12
+ hiredis_dir = File.join(File.dirname(__FILE__), %w{.. .. vendor hiredis})
13
+ unless File.directory?(hiredis_dir)
14
+ STDERR.puts "vendor/hiredis missing, please checkout its submodule..."
15
+ exit 1
16
+ end
17
+
18
+ RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
19
+ make_program = $1 || ENV['make']
20
+ make_program ||= case RUBY_PLATFORM
21
+ when /mswin/
22
+ 'nmake'
23
+ when /(bsd|solaris)/
24
+ 'gmake'
25
+ else
26
+ 'make'
27
+ end
28
+
29
+ if build_hiredis
30
+ # Make sure hiredis is built...
31
+ Dir.chdir(hiredis_dir) do
32
+ success = system("#{make_program} static")
33
+ raise "Building hiredis failed" if !success
34
+ end
35
+
36
+ # Statically link to hiredis (mkmf can't do this for us)
37
+ $CFLAGS << " -I#{hiredis_dir}"
38
+ $LDFLAGS << " #{hiredis_dir}/libhiredis.a"
39
+
40
+ have_func("rb_thread_fd_select")
41
+ create_makefile('hiredis/ext/hiredis_ext')
42
+ else
43
+ File.open("Makefile", "wb") do |f|
44
+ dummy_makefile(".").each do |line|
45
+ f.puts(line)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,15 @@
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
+ rb_global_variable(&mod_hiredis);
12
+ rb_global_variable(&mod_ext);
13
+ InitReader(mod_ext);
14
+ InitConnection(mod_ext);
15
+ }
@@ -0,0 +1,44 @@
1
+ #ifndef __HIREDIS_EXT_H
2
+ #define __HIREDIS_EXT_H
3
+
4
+ /* Defined for Rubinius. This indicates a char* obtained
5
+ * through RSTRING_PTR is never modified in place. With this
6
+ * define Rubinius can disable the slow copy back mechanisms
7
+ * to make sure strings are updated at the Ruby side.
8
+ */
9
+ #define RSTRING_NOT_MODIFIED
10
+
11
+ #include "hiredis.h"
12
+ #include "ruby.h"
13
+
14
+ /* Defined in hiredis_ext.c */
15
+ extern VALUE mod_hiredis;
16
+
17
+ /* Defined in reader.c */
18
+ extern redisReplyObjectFunctions redisExtReplyObjectFunctions;
19
+ extern VALUE klass_reader;
20
+ extern void InitReader(VALUE module);
21
+
22
+ /* Defined in connection.c */
23
+ extern VALUE klass_connection;
24
+ extern VALUE error_eof;
25
+ extern void InitConnection(VALUE module);
26
+
27
+ /* Borrowed from Nokogiri */
28
+ #ifndef RSTRING_PTR
29
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
30
+ #endif
31
+
32
+ #ifndef RSTRING_LEN
33
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
34
+ #endif
35
+
36
+ #ifndef RARRAY_PTR
37
+ #define RARRAY_PTR(a) RARRAY(a)->ptr
38
+ #endif
39
+
40
+ #ifndef RARRAY_LEN
41
+ #define RARRAY_LEN(a) RARRAY(a)->len
42
+ #endif
43
+
44
+ #endif
@@ -0,0 +1,124 @@
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
+ volatile 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
+ volatile 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, size_t elements) {
38
+ volatile VALUE v = rb_ary_new2(elements);
39
+ return tryParentize(task,v);
40
+ }
41
+
42
+ static void *createIntegerObject(const redisReadTask *task, long long value) {
43
+ volatile 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
+ NULL,
61
+ createNilObject,
62
+ NULL,
63
+ freeObject
64
+ };
65
+
66
+ static void reader_mark(redisReader *reader) {
67
+ // volatile until rb_gc_mark
68
+ volatile VALUE root = (VALUE)reader->reply;
69
+ // FIXME - PCO - checking root for 0 is checkign to see if the value is
70
+ // Qfalse. I suspect that is not what is intended here. Checking the
71
+ // redisReader code might clarify. It would be unfortunate if the reply, a
72
+ // void* was using NULL to indicate not set but that may be the nature of
73
+ // the redisReader library. It is worth checking anyway.
74
+ if (root != 0 && TYPE(root) == T_ARRAY) rb_gc_mark(root);
75
+ }
76
+
77
+ static VALUE reader_allocate(VALUE klass) {
78
+ redisReader *reader = redisReaderCreate();
79
+ reader->fn = &redisExtReplyObjectFunctions;
80
+ return Data_Wrap_Struct(klass, reader_mark, redisReaderFree, reader);
81
+ }
82
+
83
+ static VALUE reader_feed(VALUE klass, VALUE str) {
84
+ redisReader *reader;
85
+
86
+ if (TYPE(str) != T_STRING)
87
+ rb_raise(rb_eTypeError, "not a string");
88
+
89
+ Data_Get_Struct(klass, redisReader, reader);
90
+ redisReaderFeed(reader,RSTRING_PTR(str),(size_t)RSTRING_LEN(str));
91
+ return INT2NUM(0);
92
+ }
93
+
94
+ static VALUE reader_gets(VALUE klass) {
95
+ redisReader *reader;
96
+ volatile VALUE reply;
97
+
98
+ Data_Get_Struct(klass, redisReader, reader);
99
+ if (redisReaderGetReply(reader,(void**)&reply) != REDIS_OK)
100
+ rb_raise(rb_eRuntimeError,"%s",reader->errstr);
101
+
102
+ return reply;
103
+ }
104
+
105
+ VALUE klass_reader;
106
+ void InitReader(VALUE mod) {
107
+ klass_reader = rb_define_class_under(mod, "Reader", rb_cObject);
108
+ rb_global_variable(&klass_reader);
109
+ rb_define_alloc_func(klass_reader, reader_allocate);
110
+ rb_define_method(klass_reader, "feed", reader_feed, 1);
111
+ rb_define_method(klass_reader, "gets", reader_gets, 0);
112
+
113
+ /* If the Encoding class is present, #default_external should be used to
114
+ * determine the encoding for new strings. The "enc_default_external"
115
+ * ID is non-zero when encoding should be set on new strings. */
116
+ if (rb_const_defined(rb_cObject, rb_intern("Encoding"))) {
117
+ enc_klass = rb_const_get(rb_cObject, rb_intern("Encoding"));
118
+ enc_default_external = rb_intern("default_external");
119
+ str_force_encoding = rb_intern("force_encoding");
120
+ rb_global_variable(&enc_klass);
121
+ } else {
122
+ enc_default_external = 0;
123
+ }
124
+ }
@@ -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,316 @@
1
+ require "socket"
2
+ require "hiredis/ruby/reader"
3
+ require "hiredis/version"
4
+
5
+ module Hiredis
6
+ module Ruby
7
+ class Connection
8
+ EMPTY_ARRAY = [].freeze
9
+
10
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
11
+
12
+ def self.errno_to_class
13
+ Errno::Mapping
14
+ end
15
+
16
+ else
17
+
18
+ def self.errno_to_class
19
+ @mapping ||= Hash[Errno.constants.map do |name|
20
+ klass = Errno.const_get(name)
21
+ [klass.const_get("Errno"), klass]
22
+ end]
23
+ end
24
+
25
+ end
26
+
27
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
28
+
29
+ require "timeout"
30
+
31
+ def _connect(host, port, timeout)
32
+ sock = nil
33
+
34
+ begin
35
+ Timeout.timeout(timeout) do
36
+ sock = TCPSocket.new(host, port)
37
+ end
38
+ rescue SocketError => se
39
+ raise se.message
40
+ rescue Timeout::Error
41
+ raise Errno::ETIMEDOUT
42
+ end
43
+
44
+ sock
45
+ end
46
+
47
+ def _connect_unix(path, timeout)
48
+ sock = nil
49
+
50
+ begin
51
+ Timeout.timeout(timeout) do
52
+ sock = UNIXSocket.new(path)
53
+ end
54
+ rescue SocketError => se
55
+ raise se.message
56
+ rescue Timeout::Error
57
+ raise Errno::ETIMEDOUT
58
+ end
59
+
60
+ sock
61
+ end
62
+
63
+ def _write(sock, data, timeout)
64
+ begin
65
+ Timeout.timeout(timeout) do
66
+ sock.write(data)
67
+ end
68
+ rescue Timeout::Error
69
+ raise Errno::EAGAIN
70
+ end
71
+ end
72
+
73
+ else
74
+
75
+ def _connect(host, port, timeout)
76
+ error = nil
77
+ sock = nil
78
+
79
+ # Resolve address
80
+ begin
81
+ addrinfo = Socket.getaddrinfo(host, port, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
82
+ rescue SocketError => se
83
+ raise se.message
84
+ end
85
+
86
+ addrinfo.each do |_, port, name, addr, af|
87
+ begin
88
+ sockaddr = Socket.pack_sockaddr_in(port, addr)
89
+ sock = _connect_sockaddr(af, sockaddr, timeout)
90
+ rescue => aux
91
+ case aux
92
+ when Errno::EAFNOSUPPORT, Errno::ECONNREFUSED
93
+ error = aux
94
+ next
95
+ else
96
+ # Re-raise
97
+ raise
98
+ end
99
+ else
100
+ # No errors, awesome!
101
+ break
102
+ end
103
+ end
104
+
105
+ unless sock
106
+ # Re-raise last error since the last try obviously failed
107
+ raise error if error
108
+
109
+ # This code path should not happen: getaddrinfo should always return
110
+ # at least one record, which should either succeed or fail and leave
111
+ # and error to raise.
112
+ raise
113
+ end
114
+
115
+ sock
116
+ end
117
+
118
+ def _connect_unix(path, timeout)
119
+ sockaddr = Socket.pack_sockaddr_un(path)
120
+ _connect_sockaddr(Socket::AF_UNIX, sockaddr, timeout)
121
+ end
122
+
123
+ def _write(sock, data, timeout)
124
+ data.force_encoding("binary") if data.respond_to?(:force_encoding)
125
+
126
+ begin
127
+ nwritten = sock.write_nonblock(data)
128
+
129
+ while nwritten < string_size(data)
130
+ data = data[nwritten..-1]
131
+ nwritten = sock.write_nonblock(data)
132
+ end
133
+ rescue Errno::EAGAIN
134
+ if _wait_writable(sock, timeout)
135
+ # Writable, try again
136
+ retry
137
+ else
138
+ # Timed out, raise
139
+ raise Errno::EAGAIN
140
+ end
141
+ end
142
+ end
143
+
144
+ def _wait_readable(io, timeout)
145
+ if @fiber_scheduler_supported && Fiber.scheduler
146
+ Fiber.scheduler.io_wait(io, IO::READABLE, timeout)
147
+ else
148
+ IO.select([io], EMPTY_ARRAY, EMPTY_ARRAY, timeout)
149
+ end
150
+ end
151
+
152
+ def _wait_writable(io, timeout)
153
+ if @fiber_scheduler_supported && Fiber.scheduler
154
+ Fiber.scheduler.io_wait(io, IO::WRITABLE, timeout)
155
+ else
156
+ IO.select(EMPTY_ARRAY, [io], EMPTY_ARRAY, timeout)
157
+ end
158
+ end
159
+
160
+ def _connect_sockaddr(af, sockaddr, timeout)
161
+ sock = Socket.new(af, Socket::SOCK_STREAM, 0)
162
+
163
+ begin
164
+ sock.connect_nonblock(sockaddr)
165
+ rescue Errno::EINPROGRESS
166
+ if _wait_writable(sock, timeout)
167
+ # Writable, check for errors
168
+ optval = sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR)
169
+ errno = optval.unpack("i").first
170
+
171
+ # Raise socket error if there is any
172
+ raise self.class.errno_to_class[errno] if errno > 0
173
+ else
174
+ # Timeout (TODO: replace with own Timeout class)
175
+ raise Errno::ETIMEDOUT
176
+ end
177
+ end
178
+
179
+ sock
180
+ rescue
181
+ sock.close if sock
182
+
183
+ # Re-raise
184
+ raise
185
+ end
186
+
187
+ private :_connect_sockaddr
188
+
189
+ end
190
+
191
+ attr_reader :sock
192
+
193
+ def initialize
194
+ @sock = nil
195
+ @timeout = nil
196
+ @fiber_scheduler_supported = defined?(Fiber.scheduler)
197
+ end
198
+
199
+ def connected?
200
+ !! @sock
201
+ end
202
+
203
+ def connect(host, port, usecs = nil)
204
+ # Temporarily override timeout on #connect
205
+ timeout = usecs ? (usecs / 1_000_000.0) : @timeout
206
+
207
+ # Optionally disconnect current socket
208
+ disconnect if connected?
209
+
210
+ sock = _connect(host, port, timeout)
211
+ sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
212
+
213
+ @reader = ::Hiredis::Ruby::Reader.new
214
+ @sock = sock
215
+
216
+ nil
217
+ end
218
+
219
+ def connect_unix(path, usecs = 0)
220
+ # Temporarily override timeout on #connect
221
+ timeout = usecs ? (usecs / 1_000_000.0) : @timeout
222
+
223
+ # Optionally disconnect current socket
224
+ disconnect if connected?
225
+
226
+ sock = _connect_unix(path, timeout)
227
+
228
+ @reader = ::Hiredis::Ruby::Reader.new
229
+ @sock = sock
230
+
231
+ nil
232
+ end
233
+
234
+ def disconnect
235
+ @sock.close
236
+ rescue
237
+ ensure
238
+ @sock = nil
239
+ end
240
+
241
+ def timeout=(usecs)
242
+ raise ArgumentError.new("timeout cannot be negative") if usecs < 0
243
+
244
+ if usecs == 0
245
+ @timeout = nil
246
+ else
247
+ @timeout = usecs / 1_000_000.0
248
+ end
249
+
250
+ nil
251
+ end
252
+
253
+ def fileno
254
+ raise "not connected" unless connected?
255
+
256
+ @sock.fileno
257
+ end
258
+
259
+ COMMAND_DELIMITER = "\r\n".freeze
260
+
261
+ def write(args)
262
+ command = []
263
+ command << "*#{args.size}"
264
+ args.each do |arg|
265
+ arg = arg.to_s
266
+ command << "$#{string_size arg}"
267
+ command << arg
268
+ end
269
+
270
+ data = command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER
271
+
272
+ _write(@sock, data, @timeout)
273
+
274
+ nil
275
+ end
276
+
277
+ # No-op for now..
278
+ def flush
279
+ end
280
+
281
+ def read
282
+ raise "not connected" unless connected?
283
+
284
+ while (reply = @reader.gets) == false
285
+ begin
286
+ @reader.feed @sock.read_nonblock(1024)
287
+ rescue Errno::EAGAIN
288
+ if _wait_readable(@sock, @timeout)
289
+ # Readable, try again
290
+ retry
291
+ else
292
+ # Timed out, raise
293
+ raise Errno::EAGAIN
294
+ end
295
+ end
296
+ end
297
+
298
+ reply
299
+ rescue ::EOFError
300
+ raise Errno::ECONNRESET
301
+ end
302
+
303
+ protected
304
+
305
+ if "".respond_to?(:bytesize)
306
+ def string_size(string)
307
+ string.to_s.bytesize
308
+ end
309
+ else
310
+ def string_size(string)
311
+ string.to_s.size
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end