hiredis 0.3.2 → 0.4.1
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.
- data/Rakefile +32 -18
- data/ext/hiredis_ext/connection.c +248 -77
- data/lib/hiredis/ext/connection.rb +15 -2
- data/lib/hiredis/ruby/connection.rb +222 -37
- data/lib/hiredis/version.rb +1 -1
- metadata +27 -36
data/Rakefile
CHANGED
@@ -1,28 +1,42 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require "bundler"
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require "rake/testtask"
|
3
5
|
require "rake/extensiontask"
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
8
|
+
|
9
|
+
Rake::ExtensionTask.new('hiredis_ext') do |task|
|
10
|
+
# Pass --with-foo-config args to extconf.rb
|
11
|
+
task.config_options = ARGV[1..-1] || []
|
12
|
+
task.lib_dir = File.join(*['lib', 'hiredis', 'ext'])
|
13
|
+
end
|
10
14
|
|
11
|
-
namespace :hiredis do
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
namespace :hiredis do
|
16
|
+
task :clean do
|
17
|
+
# Fetch hiredis if not present
|
18
|
+
if !File.directory?("vendor/hiredis/.git")
|
19
|
+
system("git submodule update --init")
|
20
|
+
end
|
21
|
+
system("cd vendor/hiredis && make clean")
|
16
22
|
end
|
17
|
-
system("cd vendor/hiredis && make clean")
|
18
23
|
end
|
19
|
-
end
|
20
24
|
|
21
|
-
# "rake clean" should also clean bundled hiredis
|
22
|
-
Rake::Task[:clean].enhance(['hiredis:clean'])
|
25
|
+
# "rake clean" should also clean bundled hiredis
|
26
|
+
Rake::Task[:clean].enhance(['hiredis:clean'])
|
27
|
+
|
28
|
+
# Build from scratch
|
29
|
+
task :rebuild => [:clean, :compile]
|
30
|
+
|
31
|
+
else
|
32
|
+
|
33
|
+
task :rebuild do
|
34
|
+
# no-op
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
23
38
|
|
24
|
-
|
25
|
-
task :build => [:clean, :compile]
|
39
|
+
task :default => [:rebuild, :test]
|
26
40
|
|
27
41
|
desc "Run tests"
|
28
42
|
Rake::TestTask.new(:test) do |t|
|
@@ -4,15 +4,28 @@
|
|
4
4
|
|
5
5
|
typedef struct redisParentContext {
|
6
6
|
redisContext *context;
|
7
|
+
struct timeval *timeout;
|
7
8
|
} redisParentContext;
|
8
9
|
|
9
|
-
static void
|
10
|
+
static void parent_context_try_free_context(redisParentContext *pc) {
|
10
11
|
if (pc->context) {
|
11
12
|
redisFree(pc->context);
|
12
13
|
pc->context = NULL;
|
13
14
|
}
|
14
15
|
}
|
15
16
|
|
17
|
+
static void parent_context_try_free_timeout(redisParentContext *pc) {
|
18
|
+
if (pc->timeout) {
|
19
|
+
free(pc->timeout);
|
20
|
+
pc->timeout = NULL;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
static void parent_context_try_free(redisParentContext *pc) {
|
25
|
+
parent_context_try_free_context(pc);
|
26
|
+
parent_context_try_free_timeout(pc);
|
27
|
+
}
|
28
|
+
|
16
29
|
static void parent_context_mark(redisParentContext *pc) {
|
17
30
|
VALUE root;
|
18
31
|
if (pc->context && pc->context->reader) {
|
@@ -43,8 +56,8 @@ static void parent_context_raise(redisParentContext *pc) {
|
|
43
56
|
rb_sys_fail(0);
|
44
57
|
break;
|
45
58
|
case REDIS_ERR_EOF:
|
46
|
-
/* Raise
|
47
|
-
rb_raise(
|
59
|
+
/* Raise native Ruby EOFError */
|
60
|
+
rb_raise(rb_eEOFError,"%s",errstr);
|
48
61
|
break;
|
49
62
|
default:
|
50
63
|
/* Raise something else */
|
@@ -55,20 +68,56 @@ static void parent_context_raise(redisParentContext *pc) {
|
|
55
68
|
static VALUE connection_parent_context_alloc(VALUE klass) {
|
56
69
|
redisParentContext *pc = malloc(sizeof(*pc));
|
57
70
|
pc->context = NULL;
|
71
|
+
pc->timeout = NULL;
|
58
72
|
return Data_Wrap_Struct(klass, parent_context_mark, parent_context_free, pc);
|
59
73
|
}
|
60
74
|
|
61
|
-
static
|
75
|
+
static int __wait_readable(int fd, struct timeval *timeout, int *isset) {
|
76
|
+
fd_set fds;
|
77
|
+
FD_ZERO(&fds);
|
78
|
+
FD_SET(fd, &fds);
|
79
|
+
|
80
|
+
if (rb_thread_select(fd + 1, &fds, NULL, NULL, timeout) < 0) {
|
81
|
+
return -1;
|
82
|
+
}
|
83
|
+
|
84
|
+
if (FD_ISSET(fd, &fds) && isset) {
|
85
|
+
*isset = 1;
|
86
|
+
}
|
87
|
+
|
88
|
+
return 0;
|
89
|
+
}
|
90
|
+
|
91
|
+
static int __wait_writable(int fd, struct timeval *timeout, int *isset) {
|
92
|
+
fd_set fds;
|
93
|
+
FD_ZERO(&fds);
|
94
|
+
FD_SET(fd, &fds);
|
95
|
+
|
96
|
+
if (rb_thread_select(fd + 1, NULL, &fds, NULL, timeout) < 0) {
|
97
|
+
return -1;
|
98
|
+
}
|
99
|
+
|
100
|
+
if (FD_ISSET(fd, &fds) && isset) {
|
101
|
+
*isset = 1;
|
102
|
+
}
|
103
|
+
|
104
|
+
return 0;
|
105
|
+
}
|
106
|
+
|
107
|
+
static VALUE connection_generic_connect(VALUE self, redisContext *c, VALUE arg_timeout) {
|
62
108
|
redisParentContext *pc;
|
63
|
-
|
64
|
-
|
109
|
+
struct timeval tv;
|
110
|
+
struct timeval *timeout = NULL;
|
65
111
|
|
66
112
|
Data_Get_Struct(self,redisParentContext,pc);
|
67
113
|
|
68
114
|
if (c->err) {
|
115
|
+
char buf[1024];
|
116
|
+
int err;
|
117
|
+
|
69
118
|
/* Copy error and free context */
|
70
119
|
err = c->err;
|
71
|
-
snprintf(
|
120
|
+
snprintf(buf,sizeof(buf),"%s",c->errstr);
|
72
121
|
redisFree(c);
|
73
122
|
|
74
123
|
if (err == REDIS_ERR_IO) {
|
@@ -76,86 +125,107 @@ static VALUE connection_generic_connect(VALUE self, redisContext *c) {
|
|
76
125
|
rb_sys_fail(0);
|
77
126
|
} else {
|
78
127
|
/* Raise something else */
|
79
|
-
rb_raise(rb_eRuntimeError,"%s",
|
128
|
+
rb_raise(rb_eRuntimeError,"%s",buf);
|
80
129
|
}
|
81
130
|
}
|
82
131
|
|
83
|
-
|
132
|
+
/* Default to context-wide timeout setting */
|
133
|
+
if (pc->timeout != NULL) {
|
134
|
+
timeout = pc->timeout;
|
135
|
+
}
|
136
|
+
|
137
|
+
/* Override timeout when timeout argument is available */
|
138
|
+
if (arg_timeout != Qnil) {
|
139
|
+
tv.tv_sec = NUM2INT(arg_timeout) / 1000000;
|
140
|
+
tv.tv_usec = NUM2INT(arg_timeout) % 1000000;
|
141
|
+
timeout = &tv;
|
142
|
+
}
|
143
|
+
|
144
|
+
/* Wait for socket to become writable */
|
145
|
+
int writable = 0;
|
146
|
+
if (__wait_writable(c->fd, timeout, &writable) < 0) {
|
147
|
+
goto sys_fail;
|
148
|
+
}
|
149
|
+
|
150
|
+
if (!writable) {
|
151
|
+
errno = ETIMEDOUT;
|
152
|
+
goto sys_fail;
|
153
|
+
}
|
154
|
+
|
155
|
+
/* Check for socket error */
|
156
|
+
int optval = 0;
|
157
|
+
socklen_t optlen = sizeof(optval);
|
158
|
+
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
|
159
|
+
goto sys_fail;
|
160
|
+
}
|
161
|
+
|
162
|
+
if (optval) {
|
163
|
+
errno = optval;
|
164
|
+
goto sys_fail;
|
165
|
+
}
|
166
|
+
|
167
|
+
parent_context_try_free_context(pc);
|
84
168
|
pc->context = c;
|
169
|
+
pc->context->reader->fn = &redisExtReplyObjectFunctions;
|
85
170
|
return Qnil;
|
86
|
-
}
|
87
171
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
struct timeval timeout = { s, us };
|
92
|
-
return timeout;
|
172
|
+
sys_fail:
|
173
|
+
redisFree(c);
|
174
|
+
rb_sys_fail(0);
|
93
175
|
}
|
94
176
|
|
95
177
|
static VALUE connection_connect(int argc, VALUE *argv, VALUE self) {
|
96
178
|
redisParentContext *pc;
|
97
179
|
redisContext *c;
|
98
|
-
VALUE
|
99
|
-
VALUE
|
100
|
-
VALUE
|
101
|
-
char *host;
|
102
|
-
int port;
|
103
|
-
struct timeval timeout;
|
180
|
+
VALUE arg_host = Qnil;
|
181
|
+
VALUE arg_port = Qnil;
|
182
|
+
VALUE arg_timeout = Qnil;
|
104
183
|
|
105
184
|
if (argc == 2 || argc == 3) {
|
106
|
-
|
107
|
-
|
108
|
-
if (argc == 3)
|
109
|
-
_timeout = &argv[2];
|
110
|
-
} else {
|
111
|
-
rb_raise(rb_eArgError, "invalid number of arguments");
|
112
|
-
return Qnil;
|
113
|
-
}
|
185
|
+
arg_host = argv[0];
|
186
|
+
arg_port = argv[1];
|
114
187
|
|
115
|
-
|
116
|
-
|
188
|
+
if (argc == 3) {
|
189
|
+
arg_timeout = argv[2];
|
117
190
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
191
|
+
/* Sanity check */
|
192
|
+
if (NUM2INT(arg_timeout) <= 0) {
|
193
|
+
rb_raise(rb_eArgError, "timeout should be positive");
|
194
|
+
}
|
195
|
+
}
|
123
196
|
} else {
|
124
|
-
|
197
|
+
rb_raise(rb_eArgError, "invalid number of arguments");
|
125
198
|
}
|
126
199
|
|
127
|
-
|
200
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
201
|
+
c = redisConnectNonBlock(StringValuePtr(arg_host), NUM2INT(arg_port));
|
202
|
+
return connection_generic_connect(self,c,arg_timeout);
|
128
203
|
}
|
129
204
|
|
130
205
|
static VALUE connection_connect_unix(int argc, VALUE *argv, VALUE self) {
|
131
206
|
redisParentContext *pc;
|
132
207
|
redisContext *c;
|
133
|
-
VALUE
|
134
|
-
VALUE
|
135
|
-
char *path;
|
136
|
-
struct timeval timeout;
|
208
|
+
VALUE arg_path = Qnil;
|
209
|
+
VALUE arg_timeout = Qnil;
|
137
210
|
|
138
211
|
if (argc == 1 || argc == 2) {
|
139
|
-
|
140
|
-
if (argc == 2)
|
141
|
-
_timeout = &argv[1];
|
142
|
-
} else {
|
143
|
-
rb_raise(rb_eArgError, "invalid number of arguments");
|
144
|
-
return Qnil;
|
145
|
-
}
|
212
|
+
arg_path = argv[0];
|
146
213
|
|
147
|
-
|
148
|
-
|
214
|
+
if (argc == 2) {
|
215
|
+
arg_timeout = argv[1];
|
149
216
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
217
|
+
/* Sanity check */
|
218
|
+
if (NUM2INT(arg_timeout) <= 0) {
|
219
|
+
rb_raise(rb_eArgError, "timeout should be positive");
|
220
|
+
}
|
221
|
+
}
|
154
222
|
} else {
|
155
|
-
|
223
|
+
rb_raise(rb_eArgError, "invalid number of arguments");
|
156
224
|
}
|
157
225
|
|
158
|
-
|
226
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
227
|
+
c = redisConnectUnixNonBlock(StringValuePtr(arg_path));
|
228
|
+
return connection_generic_connect(self,c,arg_timeout);
|
159
229
|
}
|
160
230
|
|
161
231
|
static VALUE connection_is_connected(VALUE self) {
|
@@ -210,31 +280,113 @@ static VALUE connection_write(VALUE self, VALUE command) {
|
|
210
280
|
return Qnil;
|
211
281
|
}
|
212
282
|
|
283
|
+
static VALUE connection_flush(VALUE self) {
|
284
|
+
redisParentContext *pc;
|
285
|
+
redisContext *c;
|
286
|
+
int wdone = 0;
|
287
|
+
|
288
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
289
|
+
if (!pc->context)
|
290
|
+
rb_raise(rb_eRuntimeError, "not connected");
|
291
|
+
|
292
|
+
c = pc->context;
|
293
|
+
while (!wdone) {
|
294
|
+
errno = 0;
|
295
|
+
|
296
|
+
if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
|
297
|
+
/* Socket error */
|
298
|
+
parent_context_raise(pc);
|
299
|
+
}
|
300
|
+
|
301
|
+
if (errno == EAGAIN) {
|
302
|
+
int writable = 0;
|
303
|
+
|
304
|
+
if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
|
305
|
+
rb_sys_fail(0);
|
306
|
+
}
|
307
|
+
|
308
|
+
if (!writable) {
|
309
|
+
errno = EAGAIN;
|
310
|
+
rb_sys_fail(0);
|
311
|
+
}
|
312
|
+
}
|
313
|
+
}
|
314
|
+
|
315
|
+
return Qnil;
|
316
|
+
}
|
317
|
+
|
213
318
|
static int __get_reply(redisParentContext *pc, VALUE *reply) {
|
214
319
|
redisContext *c = pc->context;
|
215
320
|
int wdone = 0;
|
216
321
|
void *aux = NULL;
|
217
322
|
|
218
323
|
/* Try to read pending replies */
|
219
|
-
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
|
324
|
+
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
|
325
|
+
/* Protocol error */
|
220
326
|
return -1;
|
327
|
+
}
|
221
328
|
|
222
329
|
if (aux == NULL) {
|
223
|
-
|
224
|
-
|
330
|
+
/* Write until the write buffer is drained */
|
331
|
+
while (!wdone) {
|
332
|
+
errno = 0;
|
333
|
+
|
334
|
+
if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
|
335
|
+
/* Socket error */
|
225
336
|
return -1;
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
337
|
+
}
|
338
|
+
|
339
|
+
if (errno == EAGAIN) {
|
340
|
+
int writable = 0;
|
341
|
+
|
342
|
+
if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
|
343
|
+
rb_sys_fail(0);
|
344
|
+
}
|
345
|
+
|
346
|
+
if (!writable) {
|
347
|
+
errno = EAGAIN;
|
348
|
+
rb_sys_fail(0);
|
349
|
+
}
|
350
|
+
}
|
351
|
+
}
|
352
|
+
|
353
|
+
/* Read until there is a full reply */
|
354
|
+
while (aux == NULL) {
|
355
|
+
errno = 0;
|
356
|
+
|
357
|
+
if (redisBufferRead(c) == REDIS_ERR) {
|
358
|
+
/* Socket error */
|
230
359
|
return -1;
|
231
|
-
|
360
|
+
}
|
361
|
+
|
362
|
+
if (errno == EAGAIN) {
|
363
|
+
int readable = 0;
|
364
|
+
|
365
|
+
if (__wait_readable(c->fd, pc->timeout, &readable) < 0) {
|
366
|
+
rb_sys_fail(0);
|
367
|
+
}
|
368
|
+
|
369
|
+
if (!readable) {
|
370
|
+
errno = EAGAIN;
|
371
|
+
rb_sys_fail(0);
|
372
|
+
}
|
373
|
+
|
374
|
+
/* Retry */
|
375
|
+
continue;
|
376
|
+
}
|
377
|
+
|
378
|
+
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
|
379
|
+
/* Protocol error */
|
232
380
|
return -1;
|
233
|
-
|
381
|
+
}
|
382
|
+
}
|
234
383
|
}
|
235
384
|
|
236
385
|
/* Set reply object */
|
237
|
-
if (reply != NULL)
|
386
|
+
if (reply != NULL) {
|
387
|
+
*reply = (VALUE)aux;
|
388
|
+
}
|
389
|
+
|
238
390
|
return 0;
|
239
391
|
}
|
240
392
|
|
@@ -254,23 +406,41 @@ static VALUE connection_read(VALUE self) {
|
|
254
406
|
|
255
407
|
static VALUE connection_set_timeout(VALUE self, VALUE usecs) {
|
256
408
|
redisParentContext *pc;
|
257
|
-
|
258
|
-
int us = NUM2INT(usecs)-(s*1000000);
|
259
|
-
struct timeval timeout = { s, us };
|
409
|
+
struct timeval *ptr;
|
260
410
|
|
261
411
|
Data_Get_Struct(self,redisParentContext,pc);
|
262
|
-
if (!pc->context)
|
263
|
-
rb_raise(rb_eRuntimeError, "not connected");
|
264
412
|
|
265
|
-
if (
|
266
|
-
|
413
|
+
if (NUM2INT(usecs) < 0) {
|
414
|
+
rb_raise(rb_eArgError, "timeout cannot be negative");
|
415
|
+
} else {
|
416
|
+
parent_context_try_free_timeout(pc);
|
417
|
+
|
418
|
+
/* A timeout equal to zero means not to time out. This translates to a
|
419
|
+
* NULL timeout for select(2). Only allocate and populate the timeout
|
420
|
+
* when it is a positive integer. */
|
421
|
+
if (NUM2INT(usecs) > 0) {
|
422
|
+
ptr = malloc(sizeof(*ptr));
|
423
|
+
ptr->tv_sec = NUM2INT(usecs) / 1000000;
|
424
|
+
ptr->tv_usec = NUM2INT(usecs) % 1000000;
|
425
|
+
pc->timeout = ptr;
|
426
|
+
}
|
427
|
+
}
|
267
428
|
|
268
|
-
return
|
429
|
+
return Qnil;
|
269
430
|
}
|
270
431
|
|
432
|
+
static VALUE connection_fileno(VALUE self) {
|
433
|
+
redisParentContext *pc;
|
434
|
+
|
435
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
436
|
+
|
437
|
+
if (!pc->context)
|
438
|
+
rb_raise(rb_eRuntimeError, "not connected");
|
439
|
+
|
440
|
+
return INT2NUM(pc->context->fd);
|
441
|
+
}
|
271
442
|
|
272
443
|
VALUE klass_connection;
|
273
|
-
VALUE error_eof;
|
274
444
|
void InitConnection(VALUE mod) {
|
275
445
|
klass_connection = rb_define_class_under(mod, "Connection", rb_cObject);
|
276
446
|
rb_define_alloc_func(klass_connection, connection_parent_context_alloc);
|
@@ -279,7 +449,8 @@ void InitConnection(VALUE mod) {
|
|
279
449
|
rb_define_method(klass_connection, "connected?", connection_is_connected, 0);
|
280
450
|
rb_define_method(klass_connection, "disconnect", connection_disconnect, 0);
|
281
451
|
rb_define_method(klass_connection, "timeout=", connection_set_timeout, 1);
|
452
|
+
rb_define_method(klass_connection, "fileno", connection_fileno, 0);
|
282
453
|
rb_define_method(klass_connection, "write", connection_write, 1);
|
454
|
+
rb_define_method(klass_connection, "flush", connection_flush, 0);
|
283
455
|
rb_define_method(klass_connection, "read", connection_read, 0);
|
284
|
-
error_eof = rb_define_class_under(klass_connection, "EOFError", rb_eStandardError);
|
285
456
|
}
|
@@ -1,16 +1,29 @@
|
|
1
1
|
require "hiredis/ext/hiredis_ext"
|
2
2
|
require "hiredis/version"
|
3
|
+
require "socket"
|
3
4
|
|
4
5
|
module Hiredis
|
5
6
|
module Ext
|
6
7
|
class Connection
|
7
|
-
|
8
|
+
alias :_disconnect :disconnect
|
9
|
+
|
10
|
+
def disconnect
|
11
|
+
_disconnect
|
12
|
+
ensure
|
13
|
+
@sock = nil
|
14
|
+
end
|
15
|
+
|
8
16
|
alias :_read :read
|
17
|
+
|
9
18
|
def read
|
10
19
|
_read
|
11
|
-
rescue EOFError
|
20
|
+
rescue ::EOFError
|
12
21
|
raise Errno::ECONNRESET
|
13
22
|
end
|
23
|
+
|
24
|
+
def sock
|
25
|
+
@sock ||= Socket.for_fd(fileno)
|
26
|
+
end
|
14
27
|
end
|
15
28
|
end
|
16
29
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "socket"
|
2
|
-
require "timeout"
|
3
2
|
require "hiredis/ruby/reader"
|
4
3
|
require "hiredis/version"
|
5
4
|
|
@@ -7,46 +6,211 @@ module Hiredis
|
|
7
6
|
module Ruby
|
8
7
|
class Connection
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
13
23
|
|
14
|
-
def connected?
|
15
|
-
!! @sock
|
16
24
|
end
|
17
25
|
|
18
|
-
|
19
|
-
|
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
|
20
48
|
|
21
|
-
begin
|
22
49
|
begin
|
23
|
-
Timeout.timeout(
|
24
|
-
|
25
|
-
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
50
|
+
Timeout.timeout(timeout) do
|
51
|
+
sock = UNIXSocket.new(path)
|
26
52
|
end
|
53
|
+
rescue SocketError => se
|
54
|
+
raise se.message
|
27
55
|
rescue Timeout::Error
|
28
56
|
raise Errno::ETIMEDOUT
|
29
57
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
36
160
|
end
|
161
|
+
|
162
|
+
sock
|
163
|
+
rescue
|
164
|
+
sock.close if sock
|
165
|
+
|
166
|
+
# Re-raise
|
167
|
+
raise
|
37
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
|
38
199
|
end
|
39
200
|
|
40
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
|
+
|
41
210
|
@reader = ::Hiredis::Ruby::Reader.new
|
211
|
+
@sock = sock
|
42
212
|
|
43
|
-
|
44
|
-
Timeout.timeout(usecs.to_f / 1_000_000) do
|
45
|
-
@sock = UNIXSocket.new(path)
|
46
|
-
end
|
47
|
-
rescue Timeout::Error
|
48
|
-
raise Errno::ETIMEDOUT
|
49
|
-
end
|
213
|
+
nil
|
50
214
|
end
|
51
215
|
|
52
216
|
def disconnect
|
@@ -57,18 +221,21 @@ module Hiredis
|
|
57
221
|
end
|
58
222
|
|
59
223
|
def timeout=(usecs)
|
60
|
-
raise "
|
224
|
+
raise ArgumentError.new("timeout cannot be negative") if usecs < 0
|
61
225
|
|
62
|
-
|
63
|
-
|
226
|
+
if usecs == 0
|
227
|
+
@timeout = nil
|
228
|
+
else
|
229
|
+
@timeout = usecs / 1_000_000.0
|
230
|
+
end
|
64
231
|
|
65
|
-
|
232
|
+
nil
|
233
|
+
end
|
66
234
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
235
|
+
def fileno
|
236
|
+
raise "not connected" unless connected?
|
237
|
+
|
238
|
+
@sock.fileno
|
72
239
|
end
|
73
240
|
|
74
241
|
COMMAND_DELIMITER = "\r\n".freeze
|
@@ -82,18 +249,36 @@ module Hiredis
|
|
82
249
|
command << arg
|
83
250
|
end
|
84
251
|
|
85
|
-
|
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
|
86
261
|
end
|
87
262
|
|
88
263
|
def read
|
89
264
|
raise "not connected" unless connected?
|
90
265
|
|
91
266
|
while (reply = @reader.gets) == false
|
92
|
-
|
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
|
93
278
|
end
|
94
279
|
|
95
280
|
reply
|
96
|
-
rescue EOFError
|
281
|
+
rescue ::EOFError
|
97
282
|
raise Errno::ECONNRESET
|
98
283
|
end
|
99
284
|
|
data/lib/hiredis/version.rb
CHANGED
metadata
CHANGED
@@ -1,39 +1,35 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: hiredis
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
4
5
|
prerelease:
|
5
|
-
version: 0.3.2
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Pieter Noordhuis
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
dependencies:
|
16
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2011-10-26 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
17
15
|
name: rake-compiler
|
18
|
-
|
19
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &70311128546620 !ruby/object:Gem::Requirement
|
20
17
|
none: false
|
21
|
-
requirements:
|
18
|
+
requirements:
|
22
19
|
- - ~>
|
23
|
-
- !ruby/object:Gem::Version
|
20
|
+
- !ruby/object:Gem::Version
|
24
21
|
version: 0.7.1
|
25
22
|
type: :development
|
26
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70311128546620
|
27
25
|
description: Ruby extension that wraps Hiredis (blocking connection and reply parsing)
|
28
|
-
email:
|
26
|
+
email:
|
29
27
|
- pcnoordhuis@gmail.com
|
30
28
|
executables: []
|
31
|
-
|
32
|
-
extensions:
|
29
|
+
extensions:
|
33
30
|
- ext/hiredis_ext/extconf.rb
|
34
31
|
extra_rdoc_files: []
|
35
|
-
|
36
|
-
files:
|
32
|
+
files:
|
37
33
|
- ext/hiredis_ext/extconf.rb
|
38
34
|
- ext/hiredis_ext/connection.c
|
39
35
|
- ext/hiredis_ext/hiredis_ext.c
|
@@ -63,33 +59,28 @@ files:
|
|
63
59
|
- lib/hiredis.rb
|
64
60
|
- COPYING
|
65
61
|
- Rakefile
|
66
|
-
has_rdoc: true
|
67
62
|
homepage: http://github.com/pietern/hiredis-rb
|
68
63
|
licenses: []
|
69
|
-
|
70
64
|
post_install_message:
|
71
65
|
rdoc_options: []
|
72
|
-
|
73
|
-
require_paths:
|
66
|
+
require_paths:
|
74
67
|
- lib
|
75
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
69
|
none: false
|
77
|
-
requirements:
|
78
|
-
- -
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
version:
|
81
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
75
|
none: false
|
83
|
-
requirements:
|
84
|
-
- -
|
85
|
-
- !ruby/object:Gem::Version
|
86
|
-
version:
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
87
80
|
requirements: []
|
88
|
-
|
89
81
|
rubyforge_project:
|
90
|
-
rubygems_version: 1.
|
82
|
+
rubygems_version: 1.8.10
|
91
83
|
signing_key:
|
92
84
|
specification_version: 3
|
93
85
|
summary: Ruby extension that wraps Hiredis (blocking connection and reply parsing)
|
94
86
|
test_files: []
|
95
|
-
|