hiredis-futureproof 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +28 -0
- data/Rakefile +53 -0
- data/ext/hiredis_ext/connection.c +611 -0
- data/ext/hiredis_ext/extconf.rb +48 -0
- data/ext/hiredis_ext/hiredis_ext.c +15 -0
- data/ext/hiredis_ext/hiredis_ext.h +44 -0
- data/ext/hiredis_ext/reader.c +124 -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 +316 -0
- data/lib/hiredis/ruby/reader.rb +183 -0
- data/lib/hiredis/version.rb +3 -0
- data/lib/hiredis.rb +2 -0
- data/vendor/hiredis/COPYING +29 -0
- data/vendor/hiredis/Makefile +308 -0
- data/vendor/hiredis/alloc.c +86 -0
- data/vendor/hiredis/alloc.h +91 -0
- data/vendor/hiredis/async.c +892 -0
- data/vendor/hiredis/async.h +147 -0
- data/vendor/hiredis/async_private.h +75 -0
- data/vendor/hiredis/dict.c +352 -0
- data/vendor/hiredis/dict.h +126 -0
- data/vendor/hiredis/fmacros.h +12 -0
- data/vendor/hiredis/hiredis.c +1173 -0
- data/vendor/hiredis/hiredis.h +336 -0
- data/vendor/hiredis/hiredis_ssl.h +127 -0
- data/vendor/hiredis/net.c +612 -0
- data/vendor/hiredis/net.h +56 -0
- data/vendor/hiredis/read.c +739 -0
- data/vendor/hiredis/read.h +129 -0
- data/vendor/hiredis/sds.c +1289 -0
- data/vendor/hiredis/sds.h +278 -0
- data/vendor/hiredis/sdsalloc.h +44 -0
- data/vendor/hiredis/sockcompat.c +248 -0
- data/vendor/hiredis/sockcompat.h +92 -0
- data/vendor/hiredis/ssl.c +526 -0
- data/vendor/hiredis/test.c +1387 -0
- data/vendor/hiredis/win32.h +56 -0
- 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,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
|