hiredis-futureproof 0.6.3

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