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