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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3d912067683beb1533c0e9f3912e72e8572bf372151553f4ad12a9b86db07985
4
+ data.tar.gz: 1715b16a9c305cc246667a88eca2b748b00f0952e9d222d2a2c2768614e5ec03
5
+ SHA512:
6
+ metadata.gz: 94b5014ba1286a8707923bb9a0a839f5d0b6cecf0d420220e4e8a36fae85bf1141034b3c304dc5f6cb97da9d16be1d4bca923f672b8f6184c97cd99762626bbc
7
+ data.tar.gz: 8d711caf0ee44acab3ef4736601586d38784859e33c80f513a2056362d249bbc2db607f05e66f4d68f07220756a9dd4e113a0845650b34cd29bfe7dae5494180
data/COPYING ADDED
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2010-2012, Pieter Noordhuis
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ * Neither the name of Redis nor the names of its contributors may be used to
16
+ endorse or promote products derived from this software without specific prior
17
+ written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rbconfig"
5
+ require "rake/testtask"
6
+ require "rake/extensiontask"
7
+
8
+ if RUBY_PLATFORM =~ /java|mswin|mingw/i
9
+
10
+ task :rebuild do
11
+ # no-op
12
+ end
13
+
14
+ else
15
+
16
+ Rake::ExtensionTask.new('hiredis_ext') do |task|
17
+ # Pass --with-foo-config args to extconf.rb
18
+ task.config_options = ARGV[1..-1] || []
19
+ task.lib_dir = File.join(*['lib', 'hiredis', 'ext'])
20
+ end
21
+
22
+ namespace :hiredis do
23
+ task :clean do
24
+ # Fetch hiredis if not present
25
+ if !File.directory?("vendor/hiredis/.git")
26
+ system("git submodule update --init")
27
+ end
28
+ RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
29
+ make_program = $1 || ENV['make']
30
+ unless make_program then
31
+ make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
32
+ end
33
+ system("cd vendor/hiredis && #{make_program} clean")
34
+ end
35
+ end
36
+
37
+ # "rake clean" should also clean bundled hiredis
38
+ Rake::Task[:clean].enhance(['hiredis:clean'])
39
+
40
+ # Build from scratch
41
+ task :rebuild => [:clean, :compile]
42
+
43
+ end
44
+
45
+
46
+
47
+ task :default => [:rebuild, :test]
48
+
49
+ desc "Run tests"
50
+ Rake::TestTask.new(:test) do |t|
51
+ t.pattern = 'test/**/*_test.rb'
52
+ t.verbose = true
53
+ end
@@ -0,0 +1,611 @@
1
+ #include <sys/socket.h>
2
+ #include <errno.h>
3
+ #include "hiredis_ext.h"
4
+
5
+ #ifdef HAVE_RUBY_FIBER_SCHEDULER_H
6
+ #include "ruby/fiber/scheduler.h"
7
+ #include "ruby/io.h"
8
+
9
+ // TODO: I copied these from Ruby; are they supposed to be exposed as part of the C extension API?
10
+
11
+ #define FMODE_PREP (1<<16)
12
+
13
+ static int
14
+ io_check_tty(rb_io_t *fptr)
15
+ {
16
+ int t = isatty(fptr->fd);
17
+ if (t)
18
+ fptr->mode |= FMODE_TTY|FMODE_DUPLEX;
19
+ return t;
20
+ }
21
+
22
+ static VALUE
23
+ io_alloc(VALUE klass)
24
+ {
25
+ NEWOBJ_OF(io, struct RFile, klass, T_FILE);
26
+
27
+ io->fptr = 0;
28
+
29
+ return (VALUE)io;
30
+ }
31
+
32
+ static VALUE
33
+ prep_io(int fd, int fmode, VALUE klass, const char *path)
34
+ {
35
+ rb_io_t *fp;
36
+ VALUE io = io_alloc(klass);
37
+
38
+ MakeOpenFile(io, fp);
39
+ fp->self = io;
40
+ fp->fd = fd;
41
+ fp->mode = fmode;
42
+ if (!io_check_tty(fp)) {
43
+ #ifdef __CYGWIN__
44
+ fp->mode |= FMODE_BINMODE;
45
+ setmode(fd, O_BINARY);
46
+ #endif
47
+ }
48
+ if (path) fp->pathv = rb_obj_freeze(rb_str_new_cstr(path));
49
+ rb_update_max_fd(fd);
50
+
51
+ return io;
52
+ }
53
+
54
+ static VALUE
55
+ io_from_fd(int fd)
56
+ {
57
+ return prep_io(fd, FMODE_PREP, rb_cIO, NULL);
58
+ }
59
+
60
+ #endif
61
+
62
+ typedef struct redisParentContext {
63
+ redisContext *context;
64
+ struct timeval *timeout;
65
+ } redisParentContext;
66
+
67
+ static void parent_context_try_free_context(redisParentContext *pc) {
68
+ if (pc->context) {
69
+ redisFree(pc->context);
70
+ pc->context = NULL;
71
+ }
72
+ }
73
+
74
+ static void parent_context_try_free_timeout(redisParentContext *pc) {
75
+ if (pc->timeout) {
76
+ free(pc->timeout);
77
+ pc->timeout = NULL;
78
+ }
79
+ }
80
+
81
+ static void parent_context_try_free(redisParentContext *pc) {
82
+ parent_context_try_free_context(pc);
83
+ parent_context_try_free_timeout(pc);
84
+ }
85
+
86
+ static void parent_context_mark(redisParentContext *pc) {
87
+ // volatile until rb_gc_mark
88
+ volatile VALUE root;
89
+ if (pc->context && pc->context->reader) {
90
+ root = (VALUE)redisReaderGetObject(pc->context->reader);
91
+ if (root != 0 && TYPE(root) == T_ARRAY) {
92
+ rb_gc_mark(root);
93
+ }
94
+ }
95
+ }
96
+
97
+ static void parent_context_free(redisParentContext *pc) {
98
+ parent_context_try_free(pc);
99
+ free(pc);
100
+ }
101
+
102
+ static void parent_context_raise(redisParentContext *pc) {
103
+ int err;
104
+ char errstr[1024];
105
+
106
+ /* Copy error and free context */
107
+ err = pc->context->err;
108
+ snprintf(errstr,sizeof(errstr),"%s",pc->context->errstr);
109
+ parent_context_try_free(pc);
110
+
111
+ switch(err) {
112
+ case REDIS_ERR_IO:
113
+ /* Raise native Ruby I/O error */
114
+ rb_sys_fail(0);
115
+ break;
116
+ case REDIS_ERR_EOF:
117
+ /* Raise native Ruby EOFError */
118
+ rb_raise(rb_eEOFError,"%s",errstr);
119
+ break;
120
+ default:
121
+ /* Raise something else */
122
+ rb_raise(rb_eRuntimeError,"%s",errstr);
123
+ }
124
+ }
125
+
126
+ static VALUE connection_parent_context_alloc(VALUE klass) {
127
+ redisParentContext *pc = malloc(sizeof(*pc));
128
+ pc->context = NULL;
129
+ pc->timeout = NULL;
130
+ return Data_Wrap_Struct(klass, parent_context_mark, parent_context_free, pc);
131
+ }
132
+
133
+
134
+ /*
135
+ * The rb_fdset_t API was introduced in Ruby 1.9.1. The rb_fd_thread_select
136
+ * function was introduced in a later version. Therefore, there are one more
137
+ * versions where we cannot simply test HAVE_RB_FD_INIT and be done, we have to
138
+ * explicitly test for HAVE_RB_THREAD_FD_SELECT (also see extconf.rb).
139
+ */
140
+ #ifdef HAVE_RB_THREAD_FD_SELECT
141
+ typedef rb_fdset_t _fdset_t;
142
+ #define _fd_zero(f) rb_fd_zero(f)
143
+ #define _fd_set(n, f) rb_fd_set(n, f)
144
+ #define _fd_clr(n, f) rb_fd_clr(n, f)
145
+ #define _fd_isset(n, f) rb_fd_isset(n, f)
146
+ #define _fd_copy(d, s, n) rb_fd_copy(d, s, n)
147
+ #define _fd_ptr(f) rb_fd_ptr(f)
148
+ #define _fd_init(f) rb_fd_init(f)
149
+ #define _fd_term(f) rb_fd_term(f)
150
+ #define _fd_max(f) rb_fd_max(f)
151
+ #define _thread_fd_select(n, r, w, e, t) rb_thread_fd_select(n, r, w, e, t)
152
+ #else
153
+ typedef fd_set _fdset_t;
154
+ #define _fd_zero(f) FD_ZERO(f)
155
+ #define _fd_set(n, f) FD_SET(n, f)
156
+ #define _fd_clr(n, f) FD_CLR(n, f)
157
+ #define _fd_isset(n, f) FD_ISSET(n, f)
158
+ #define _fd_copy(d, s, n) (*(d) = *(s))
159
+ #define _fd_ptr(f) (f)
160
+ #define _fd_init(f) FD_ZERO(f)
161
+ #define _fd_term(f) (void)(f)
162
+ #define _fd_max(f) FD_SETSIZE
163
+ #define _thread_fd_select(n, r, w, e, t) rb_thread_select(n, r, w, e, t)
164
+ #endif
165
+
166
+ static int __wait_readable(int fd, const struct timeval *timeout, int *isset) {
167
+ #ifdef HAVE_RUBY_FIBER_SCHEDULER_H
168
+ VALUE scheduler = rb_fiber_scheduler_current();
169
+ if (scheduler != Qnil) {
170
+ VALUE result = rb_fiber_scheduler_io_wait(scheduler,
171
+ io_from_fd(fd),
172
+ RB_UINT2NUM(RUBY_IO_READABLE),
173
+ rb_fiber_scheduler_make_timeout((struct timeval *) timeout));
174
+
175
+ if (RTEST(result)) {
176
+ if (isset) {
177
+ *isset = 1;
178
+ }
179
+ return 0;
180
+ } else {
181
+ // timeout
182
+ return -1;
183
+ }
184
+ }
185
+ #endif
186
+
187
+ struct timeval to;
188
+ struct timeval *toptr = NULL;
189
+
190
+ _fdset_t fds;
191
+
192
+ /* Be cautious: a call to rb_fd_init to initialize the rb_fdset_t structure
193
+ * must be paired with a call to rb_fd_term to free it. */
194
+ _fd_init(&fds);
195
+ _fd_set(fd, &fds);
196
+
197
+ /* rb_thread_{fd_,}select modifies the passed timeval, so we pass a copy */
198
+ if (timeout != NULL) {
199
+ memcpy(&to, timeout, sizeof(to));
200
+ toptr = &to;
201
+ }
202
+
203
+ if (_thread_fd_select(fd + 1, &fds, NULL, NULL, toptr) < 0) {
204
+ _fd_term(&fds);
205
+ return -1;
206
+ }
207
+
208
+ if (_fd_isset(fd, &fds) && isset) {
209
+ *isset = 1;
210
+ }
211
+
212
+ _fd_term(&fds);
213
+ return 0;
214
+ }
215
+
216
+ static int __wait_writable(int fd, const struct timeval *timeout, int *isset) {
217
+ #ifdef HAVE_RUBY_FIBER_SCHEDULER_H
218
+ VALUE scheduler = rb_fiber_scheduler_current();
219
+ if (scheduler != Qnil) {
220
+ VALUE result = rb_fiber_scheduler_io_wait(scheduler,
221
+ io_from_fd(fd),
222
+ RB_UINT2NUM(RUBY_IO_WRITABLE),
223
+ rb_fiber_scheduler_make_timeout((struct timeval *) timeout));
224
+ if (RTEST(result)) {
225
+ if (isset) {
226
+ *isset = 1;
227
+ }
228
+ return 0;
229
+ } else {
230
+ // timeout
231
+ return -1;
232
+ }
233
+ }
234
+ #endif
235
+
236
+ struct timeval to;
237
+ struct timeval *toptr = NULL;
238
+
239
+ _fdset_t fds;
240
+
241
+ /* Be cautious: a call to rb_fd_init to initialize the rb_fdset_t structure
242
+ * must be paired with a call to rb_fd_term to free it. */
243
+ _fd_init(&fds);
244
+ _fd_set(fd, &fds);
245
+
246
+ /* rb_thread_{fd_,}select modifies the passed timeval, so we pass a copy */
247
+ if (timeout != NULL) {
248
+ memcpy(&to, timeout, sizeof(to));
249
+ toptr = &to;
250
+ }
251
+
252
+ if (_thread_fd_select(fd + 1, NULL, &fds, NULL, toptr) < 0) {
253
+ _fd_term(&fds);
254
+ return -1;
255
+ }
256
+
257
+ if (_fd_isset(fd, &fds) && isset) {
258
+ *isset = 1;
259
+ }
260
+
261
+ _fd_term(&fds);
262
+ return 0;
263
+ }
264
+
265
+ static VALUE connection_generic_connect(VALUE self, redisContext *c, VALUE arg_timeout) {
266
+ redisParentContext *pc;
267
+ struct timeval tv;
268
+ struct timeval *timeout = NULL;
269
+ int writable = 0;
270
+ int optval = 0;
271
+ socklen_t optlen = sizeof(optval);
272
+
273
+ Data_Get_Struct(self,redisParentContext,pc);
274
+
275
+ if (c->err) {
276
+ char buf[1024];
277
+ int err;
278
+
279
+ /* Copy error and free context */
280
+ err = c->err;
281
+ snprintf(buf,sizeof(buf),"%s",c->errstr);
282
+ redisFree(c);
283
+
284
+ if (err == REDIS_ERR_IO) {
285
+ /* Raise native Ruby I/O error */
286
+ rb_sys_fail(0);
287
+ } else {
288
+ /* Raise something else */
289
+ rb_raise(rb_eRuntimeError,"%s",buf);
290
+ }
291
+ }
292
+
293
+ /* Default to context-wide timeout setting */
294
+ if (pc->timeout != NULL) {
295
+ timeout = pc->timeout;
296
+ }
297
+
298
+ /* Override timeout when timeout argument is available */
299
+ if (arg_timeout != Qnil) {
300
+ tv.tv_sec = NUM2INT(arg_timeout) / 1000000;
301
+ tv.tv_usec = NUM2INT(arg_timeout) % 1000000;
302
+ timeout = &tv;
303
+ }
304
+
305
+ /* Wait for socket to become writable */
306
+ if (__wait_writable(c->fd, timeout, &writable) < 0) {
307
+ goto sys_fail;
308
+ }
309
+
310
+ if (!writable) {
311
+ errno = ETIMEDOUT;
312
+ goto sys_fail;
313
+ }
314
+
315
+ /* Check for socket error */
316
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
317
+ goto sys_fail;
318
+ }
319
+
320
+ if (optval) {
321
+ errno = optval;
322
+ goto sys_fail;
323
+ }
324
+
325
+ parent_context_try_free_context(pc);
326
+ pc->context = c;
327
+ pc->context->reader->fn = &redisExtReplyObjectFunctions;
328
+ return Qnil;
329
+
330
+ sys_fail:
331
+ redisFree(c);
332
+ rb_sys_fail(0);
333
+ }
334
+
335
+ // entrypoint for Driver#connect
336
+ static VALUE connection_connect(int argc, VALUE *argv, VALUE self) {
337
+ redisContext *c;
338
+ VALUE arg_host = Qnil;
339
+ VALUE arg_port = Qnil;
340
+ VALUE arg_timeout = Qnil;
341
+
342
+ if (argc == 2 || argc == 3) {
343
+ arg_host = argv[0];
344
+ arg_port = argv[1];
345
+
346
+ if (argc == 3) {
347
+ arg_timeout = argv[2];
348
+
349
+ /* Sanity check */
350
+ if (NUM2INT(arg_timeout) <= 0) {
351
+ rb_raise(rb_eArgError, "timeout should be positive");
352
+ }
353
+ }
354
+ } else {
355
+ rb_raise(rb_eArgError, "invalid number of arguments");
356
+ }
357
+
358
+ c = redisConnectNonBlock(StringValuePtr(arg_host), NUM2INT(arg_port));
359
+ return connection_generic_connect(self,c,arg_timeout);
360
+ }
361
+
362
+ static VALUE connection_connect_unix(int argc, VALUE *argv, VALUE self) {
363
+ redisContext *c;
364
+ VALUE arg_path = Qnil;
365
+ VALUE arg_timeout = Qnil;
366
+
367
+ if (argc == 1 || argc == 2) {
368
+ arg_path = argv[0];
369
+
370
+ if (argc == 2) {
371
+ arg_timeout = argv[1];
372
+
373
+ /* Sanity check */
374
+ if (NUM2INT(arg_timeout) <= 0) {
375
+ rb_raise(rb_eArgError, "timeout should be positive");
376
+ }
377
+ }
378
+ } else {
379
+ rb_raise(rb_eArgError, "invalid number of arguments");
380
+ }
381
+
382
+ c = redisConnectUnixNonBlock(StringValuePtr(arg_path));
383
+ return connection_generic_connect(self,c,arg_timeout);
384
+ }
385
+
386
+ static VALUE connection_is_connected(VALUE self) {
387
+ redisParentContext *pc;
388
+ Data_Get_Struct(self,redisParentContext,pc);
389
+ if (pc->context && !pc->context->err)
390
+ return Qtrue;
391
+ else
392
+ return Qfalse;
393
+ }
394
+
395
+ static VALUE connection_disconnect(VALUE self) {
396
+ redisParentContext *pc;
397
+ Data_Get_Struct(self,redisParentContext,pc);
398
+ if (!pc->context)
399
+ rb_raise(rb_eRuntimeError,"%s","not connected");
400
+ parent_context_try_free(pc);
401
+ return Qnil;
402
+ }
403
+
404
+ static VALUE connection_write(VALUE self, VALUE command) {
405
+ redisParentContext *pc;
406
+ int argc;
407
+ char **argv = NULL;
408
+ size_t *alen = NULL;
409
+ int i;
410
+
411
+ /* Commands should be an array of commands, where each command
412
+ * is an array of string arguments. */
413
+ if (TYPE(command) != T_ARRAY)
414
+ rb_raise(rb_eArgError,"%s","not an array");
415
+
416
+ Data_Get_Struct(self,redisParentContext,pc);
417
+ if (!pc->context)
418
+ rb_raise(rb_eRuntimeError,"%s","not connected");
419
+
420
+ argc = (int)RARRAY_LEN(command);
421
+ argv = malloc(argc*sizeof(char*));
422
+ alen = malloc(argc*sizeof(size_t));
423
+ for (i = 0; i < argc; i++) {
424
+ /* Replace arguments in the arguments array to prevent their string
425
+ * equivalents to be garbage collected before this loop is done. */
426
+ VALUE entry = rb_obj_as_string(rb_ary_entry(command, i));
427
+ rb_ary_store(command, i, entry);
428
+ argv[i] = RSTRING_PTR(entry);
429
+ alen[i] = RSTRING_LEN(entry);
430
+ }
431
+ redisAppendCommandArgv(pc->context,argc,(const char**)argv,alen);
432
+ free(argv);
433
+ free(alen);
434
+ return Qnil;
435
+ }
436
+
437
+ static VALUE connection_flush(VALUE self) {
438
+ redisParentContext *pc;
439
+ redisContext *c;
440
+ int wdone = 0;
441
+
442
+ Data_Get_Struct(self,redisParentContext,pc);
443
+ if (!pc->context)
444
+ rb_raise(rb_eRuntimeError, "not connected");
445
+
446
+ c = pc->context;
447
+ while (!wdone) {
448
+ errno = 0;
449
+
450
+ if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
451
+ /* Socket error */
452
+ parent_context_raise(pc);
453
+ }
454
+
455
+ if (errno == EAGAIN) {
456
+ int writable = 0;
457
+
458
+ if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
459
+ rb_sys_fail(0);
460
+ }
461
+
462
+ if (!writable) {
463
+ errno = EAGAIN;
464
+ rb_sys_fail(0);
465
+ }
466
+ }
467
+ }
468
+
469
+ return Qnil;
470
+ }
471
+
472
+ static int __get_reply(redisParentContext *pc, VALUE *reply) {
473
+ redisContext *c = pc->context;
474
+ int wdone = 0;
475
+ void *aux = NULL;
476
+
477
+ /* Try to read pending replies */
478
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
479
+ /* Protocol error */
480
+ return -1;
481
+ }
482
+
483
+ if (aux == NULL) {
484
+ /* Write until the write buffer is drained */
485
+ while (!wdone) {
486
+ errno = 0;
487
+
488
+ if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
489
+ /* Socket error */
490
+ return -1;
491
+ }
492
+
493
+ if (errno == EAGAIN) {
494
+ int writable = 0;
495
+
496
+ if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
497
+ rb_sys_fail(0);
498
+ }
499
+
500
+ if (!writable) {
501
+ errno = EAGAIN;
502
+ rb_sys_fail(0);
503
+ }
504
+ }
505
+ }
506
+
507
+ /* Read until there is a full reply */
508
+ while (aux == NULL) {
509
+ errno = 0;
510
+
511
+ if (redisBufferRead(c) == REDIS_ERR) {
512
+ /* Socket error */
513
+ return -1;
514
+ }
515
+
516
+ if (errno == EAGAIN) {
517
+ int readable = 0;
518
+
519
+ if (__wait_readable(c->fd, pc->timeout, &readable) < 0) {
520
+ rb_sys_fail(0);
521
+ }
522
+
523
+ if (!readable) {
524
+ errno = EAGAIN;
525
+ rb_sys_fail(0);
526
+ }
527
+
528
+ /* Retry */
529
+ continue;
530
+ }
531
+
532
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
533
+ /* Protocol error */
534
+ return -1;
535
+ }
536
+ }
537
+ }
538
+
539
+ /* Set reply object */
540
+ if (reply != NULL) {
541
+ *reply = (VALUE)aux;
542
+ }
543
+
544
+ return 0;
545
+ }
546
+
547
+ static VALUE connection_read(VALUE self) {
548
+ redisParentContext *pc;
549
+ volatile VALUE reply;
550
+
551
+ Data_Get_Struct(self,redisParentContext,pc);
552
+ if (!pc->context)
553
+ rb_raise(rb_eRuntimeError, "not connected");
554
+
555
+ if (__get_reply(pc,&reply) == -1)
556
+ parent_context_raise(pc);
557
+
558
+ return reply;
559
+ }
560
+
561
+ static VALUE connection_set_timeout(VALUE self, VALUE usecs) {
562
+ redisParentContext *pc;
563
+ struct timeval *ptr;
564
+
565
+ Data_Get_Struct(self,redisParentContext,pc);
566
+
567
+ if (NUM2INT(usecs) < 0) {
568
+ rb_raise(rb_eArgError, "timeout cannot be negative");
569
+ } else {
570
+ parent_context_try_free_timeout(pc);
571
+
572
+ /* A timeout equal to zero means not to time out. This translates to a
573
+ * NULL timeout for select(2). Only allocate and populate the timeout
574
+ * when it is a positive integer. */
575
+ if (NUM2INT(usecs) > 0) {
576
+ ptr = malloc(sizeof(*ptr));
577
+ ptr->tv_sec = NUM2INT(usecs) / 1000000;
578
+ ptr->tv_usec = NUM2INT(usecs) % 1000000;
579
+ pc->timeout = ptr;
580
+ }
581
+ }
582
+
583
+ return Qnil;
584
+ }
585
+
586
+ static VALUE connection_fileno(VALUE self) {
587
+ redisParentContext *pc;
588
+
589
+ Data_Get_Struct(self,redisParentContext,pc);
590
+
591
+ if (!pc->context)
592
+ rb_raise(rb_eRuntimeError, "not connected");
593
+
594
+ return INT2NUM(pc->context->fd);
595
+ }
596
+
597
+ VALUE klass_connection;
598
+ void InitConnection(VALUE mod) {
599
+ klass_connection = rb_define_class_under(mod, "Connection", rb_cObject);
600
+ rb_global_variable(&klass_connection);
601
+ rb_define_alloc_func(klass_connection, connection_parent_context_alloc);
602
+ rb_define_method(klass_connection, "connect", connection_connect, -1);
603
+ rb_define_method(klass_connection, "connect_unix", connection_connect_unix, -1);
604
+ rb_define_method(klass_connection, "connected?", connection_is_connected, 0);
605
+ rb_define_method(klass_connection, "disconnect", connection_disconnect, 0);
606
+ rb_define_method(klass_connection, "timeout=", connection_set_timeout, 1);
607
+ rb_define_method(klass_connection, "fileno", connection_fileno, 0);
608
+ rb_define_method(klass_connection, "write", connection_write, 1);
609
+ rb_define_method(klass_connection, "flush", connection_flush, 0);
610
+ rb_define_method(klass_connection, "read", connection_read, 0);
611
+ }