methodmissing_hiredis 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
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,45 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rake/testtask"
5
+ require "rake/extensiontask"
6
+
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
14
+
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")
22
+ end
23
+ end
24
+
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
38
+
39
+ task :default => [:rebuild, :test]
40
+
41
+ desc "Run tests"
42
+ Rake::TestTask.new(:test) do |t|
43
+ t.pattern = 'test/**/*_test.rb'
44
+ t.verbose = true
45
+ end
@@ -0,0 +1,472 @@
1
+ #include <sys/socket.h>
2
+ #include <errno.h>
3
+ #include "hiredis_ext.h"
4
+
5
+ typedef struct redisParentContext {
6
+ redisContext *context;
7
+ struct timeval *timeout;
8
+ } redisParentContext;
9
+
10
+ static void parent_context_try_free_context(redisParentContext *pc) {
11
+ if (pc->context) {
12
+ redisFree(pc->context);
13
+ pc->context = NULL;
14
+ }
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
+
29
+ static void parent_context_mark(redisParentContext *pc) {
30
+ VALUE root;
31
+ if (pc->context && pc->context->reader) {
32
+ root = (VALUE)redisReplyReaderGetObject(pc->context->reader);
33
+ if (root != 0 && TYPE(root) == T_ARRAY) {
34
+ rb_gc_mark(root);
35
+ }
36
+ }
37
+ }
38
+
39
+ static void parent_context_free(redisParentContext *pc) {
40
+ parent_context_try_free(pc);
41
+ free(pc);
42
+ }
43
+
44
+ static void parent_context_raise(redisParentContext *pc) {
45
+ int err;
46
+ char errstr[1024];
47
+
48
+ /* Copy error and free context */
49
+ err = pc->context->err;
50
+ snprintf(errstr,sizeof(errstr),"%s",pc->context->errstr);
51
+ parent_context_try_free(pc);
52
+
53
+ switch(err) {
54
+ case REDIS_ERR_IO:
55
+ /* Raise native Ruby I/O error */
56
+ rb_sys_fail(0);
57
+ break;
58
+ case REDIS_ERR_EOF:
59
+ /* Raise native Ruby EOFError */
60
+ rb_raise(rb_eEOFError,"%s",errstr);
61
+ break;
62
+ default:
63
+ /* Raise something else */
64
+ rb_raise(rb_eRuntimeError,"%s",errstr);
65
+ }
66
+ }
67
+
68
+ static VALUE connection_parent_context_alloc(VALUE klass) {
69
+ redisParentContext *pc = malloc(sizeof(*pc));
70
+ pc->context = NULL;
71
+ pc->timeout = NULL;
72
+ return Data_Wrap_Struct(klass, parent_context_mark, parent_context_free, pc);
73
+ }
74
+
75
+ static int __wait_readable(int fd, const struct timeval *timeout, int *isset) {
76
+ struct timeval to;
77
+ struct timeval *toptr = NULL;
78
+ fd_set fds;
79
+ FD_ZERO(&fds);
80
+ FD_SET(fd, &fds);
81
+
82
+ /* rb_thread_select modifies the passed timeval, so we pass a copy */
83
+ if (timeout != NULL) {
84
+ memcpy(&to, timeout, sizeof(to));
85
+ toptr = &to;
86
+ }
87
+
88
+ if (rb_thread_select(fd + 1, &fds, NULL, NULL, toptr) < 0) {
89
+ return -1;
90
+ }
91
+
92
+ if (FD_ISSET(fd, &fds) && isset) {
93
+ *isset = 1;
94
+ }
95
+
96
+ return 0;
97
+ }
98
+
99
+ static int __wait_writable(int fd, const struct timeval *timeout, int *isset) {
100
+ struct timeval to;
101
+ struct timeval *toptr = NULL;
102
+ fd_set fds;
103
+ FD_ZERO(&fds);
104
+ FD_SET(fd, &fds);
105
+
106
+ /* rb_thread_select modifies the passed timeval, so we pass a copy */
107
+ if (timeout != NULL) {
108
+ memcpy(&to, timeout, sizeof(to));
109
+ toptr = &to;
110
+ }
111
+
112
+ if (rb_thread_select(fd + 1, NULL, &fds, NULL, toptr) < 0) {
113
+ return -1;
114
+ }
115
+
116
+ if (FD_ISSET(fd, &fds) && isset) {
117
+ *isset = 1;
118
+ }
119
+
120
+ return 0;
121
+ }
122
+
123
+ static VALUE connection_generic_connect(VALUE self, redisContext *c, VALUE arg_timeout) {
124
+ redisParentContext *pc;
125
+ struct timeval tv;
126
+ struct timeval *timeout = NULL;
127
+
128
+ Data_Get_Struct(self,redisParentContext,pc);
129
+
130
+ if (c->err) {
131
+ char buf[1024];
132
+ int err;
133
+
134
+ /* Copy error and free context */
135
+ err = c->err;
136
+ snprintf(buf,sizeof(buf),"%s",c->errstr);
137
+ redisFree(c);
138
+
139
+ if (err == REDIS_ERR_IO) {
140
+ /* Raise native Ruby I/O error */
141
+ rb_sys_fail(0);
142
+ } else {
143
+ /* Raise something else */
144
+ rb_raise(rb_eRuntimeError,"%s",buf);
145
+ }
146
+ }
147
+
148
+ /* Default to context-wide timeout setting */
149
+ if (pc->timeout != NULL) {
150
+ timeout = pc->timeout;
151
+ }
152
+
153
+ /* Override timeout when timeout argument is available */
154
+ if (arg_timeout != Qnil) {
155
+ tv.tv_sec = NUM2INT(arg_timeout) / 1000000;
156
+ tv.tv_usec = NUM2INT(arg_timeout) % 1000000;
157
+ timeout = &tv;
158
+ }
159
+
160
+ /* Wait for socket to become writable */
161
+ int writable = 0;
162
+ if (__wait_writable(c->fd, timeout, &writable) < 0) {
163
+ goto sys_fail;
164
+ }
165
+
166
+ if (!writable) {
167
+ errno = ETIMEDOUT;
168
+ goto sys_fail;
169
+ }
170
+
171
+ /* Check for socket error */
172
+ int optval = 0;
173
+ socklen_t optlen = sizeof(optval);
174
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
175
+ goto sys_fail;
176
+ }
177
+
178
+ if (optval) {
179
+ errno = optval;
180
+ goto sys_fail;
181
+ }
182
+
183
+ parent_context_try_free_context(pc);
184
+ pc->context = c;
185
+ pc->context->reader->fn = &redisExtReplyObjectFunctions;
186
+ return Qnil;
187
+
188
+ sys_fail:
189
+ redisFree(c);
190
+ rb_sys_fail(0);
191
+ }
192
+
193
+ static VALUE connection_connect(int argc, VALUE *argv, VALUE self) {
194
+ redisParentContext *pc;
195
+ redisContext *c;
196
+ VALUE arg_host = Qnil;
197
+ VALUE arg_port = Qnil;
198
+ VALUE arg_timeout = Qnil;
199
+
200
+ if (argc == 2 || argc == 3) {
201
+ arg_host = argv[0];
202
+ arg_port = argv[1];
203
+
204
+ if (argc == 3) {
205
+ arg_timeout = argv[2];
206
+
207
+ /* Sanity check */
208
+ if (NUM2INT(arg_timeout) <= 0) {
209
+ rb_raise(rb_eArgError, "timeout should be positive");
210
+ }
211
+ }
212
+ } else {
213
+ rb_raise(rb_eArgError, "invalid number of arguments");
214
+ }
215
+
216
+ Data_Get_Struct(self,redisParentContext,pc);
217
+ c = redisConnectNonBlock(StringValuePtr(arg_host), NUM2INT(arg_port));
218
+ return connection_generic_connect(self,c,arg_timeout);
219
+ }
220
+
221
+ static VALUE connection_connect_unix(int argc, VALUE *argv, VALUE self) {
222
+ redisParentContext *pc;
223
+ redisContext *c;
224
+ VALUE arg_path = Qnil;
225
+ VALUE arg_timeout = Qnil;
226
+
227
+ if (argc == 1 || argc == 2) {
228
+ arg_path = argv[0];
229
+
230
+ if (argc == 2) {
231
+ arg_timeout = argv[1];
232
+
233
+ /* Sanity check */
234
+ if (NUM2INT(arg_timeout) <= 0) {
235
+ rb_raise(rb_eArgError, "timeout should be positive");
236
+ }
237
+ }
238
+ } else {
239
+ rb_raise(rb_eArgError, "invalid number of arguments");
240
+ }
241
+
242
+ Data_Get_Struct(self,redisParentContext,pc);
243
+ c = redisConnectUnixNonBlock(StringValuePtr(arg_path));
244
+ return connection_generic_connect(self,c,arg_timeout);
245
+ }
246
+
247
+ static VALUE connection_is_connected(VALUE self) {
248
+ redisParentContext *pc;
249
+ Data_Get_Struct(self,redisParentContext,pc);
250
+ if (pc->context && !pc->context->err)
251
+ return Qtrue;
252
+ else
253
+ return Qfalse;
254
+ }
255
+
256
+ static VALUE connection_disconnect(VALUE self) {
257
+ redisParentContext *pc;
258
+ Data_Get_Struct(self,redisParentContext,pc);
259
+ if (!pc->context)
260
+ rb_raise(rb_eRuntimeError,"%s","not connected");
261
+ parent_context_try_free(pc);
262
+ return Qnil;
263
+ }
264
+
265
+ static VALUE connection_write(VALUE self, VALUE command) {
266
+ redisParentContext *pc;
267
+ int argc;
268
+ VALUE *args;
269
+ char **argv = NULL;
270
+ size_t *alen = NULL;
271
+ int i;
272
+
273
+ /* Commands should be an array of commands, where each command
274
+ * is an array of string arguments. */
275
+ if (TYPE(command) != T_ARRAY)
276
+ rb_raise(rb_eArgError,"%s","not an array");
277
+
278
+ Data_Get_Struct(self,redisParentContext,pc);
279
+ if (!pc->context)
280
+ rb_raise(rb_eRuntimeError,"%s","not connected");
281
+
282
+ argc = (int)RARRAY_LEN(command);
283
+ args = RARRAY_PTR(command);
284
+ argv = malloc(argc*sizeof(char*));
285
+ alen = malloc(argc*sizeof(size_t));
286
+ for (i = 0; i < argc; i++) {
287
+ /* Replace arguments in the arguments array to prevent their string
288
+ * equivalents to be garbage collected before this loop is done. */
289
+ args[i] = rb_obj_as_string(args[i]);
290
+ argv[i] = RSTRING_PTR(args[i]);
291
+ alen[i] = RSTRING_LEN(args[i]);
292
+ }
293
+ redisAppendCommandArgv(pc->context,argc,(const char**)argv,alen);
294
+ free(argv);
295
+ free(alen);
296
+ return Qnil;
297
+ }
298
+
299
+ static VALUE connection_flush(VALUE self) {
300
+ redisParentContext *pc;
301
+ redisContext *c;
302
+ int wdone = 0;
303
+
304
+ Data_Get_Struct(self,redisParentContext,pc);
305
+ if (!pc->context)
306
+ rb_raise(rb_eRuntimeError, "not connected");
307
+
308
+ c = pc->context;
309
+ while (!wdone) {
310
+ errno = 0;
311
+
312
+ if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
313
+ /* Socket error */
314
+ parent_context_raise(pc);
315
+ }
316
+
317
+ if (errno == EAGAIN) {
318
+ int writable = 0;
319
+
320
+ if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
321
+ rb_sys_fail(0);
322
+ }
323
+
324
+ if (!writable) {
325
+ errno = EAGAIN;
326
+ rb_sys_fail(0);
327
+ }
328
+ }
329
+ }
330
+
331
+ return Qnil;
332
+ }
333
+
334
+ static int __get_reply(redisParentContext *pc, VALUE *reply) {
335
+ redisContext *c = pc->context;
336
+ int wdone = 0;
337
+ void *aux = NULL;
338
+
339
+ /* Try to read pending replies */
340
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
341
+ /* Protocol error */
342
+ return -1;
343
+ }
344
+
345
+ if (aux == NULL) {
346
+ /* Write until the write buffer is drained */
347
+ while (!wdone) {
348
+ errno = 0;
349
+
350
+ if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
351
+ /* Socket error */
352
+ return -1;
353
+ }
354
+
355
+ if (errno == EAGAIN) {
356
+ int writable = 0;
357
+
358
+ if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
359
+ rb_sys_fail(0);
360
+ }
361
+
362
+ if (!writable) {
363
+ errno = EAGAIN;
364
+ rb_sys_fail(0);
365
+ }
366
+ }
367
+ }
368
+
369
+ /* Read until there is a full reply */
370
+ while (aux == NULL) {
371
+ errno = 0;
372
+
373
+ if (redisBufferRead(c) == REDIS_ERR) {
374
+ /* Socket error */
375
+ return -1;
376
+ }
377
+
378
+ if (errno == EAGAIN) {
379
+ int readable = 0;
380
+
381
+ if (__wait_readable(c->fd, pc->timeout, &readable) < 0) {
382
+ rb_sys_fail(0);
383
+ }
384
+
385
+ if (!readable) {
386
+ errno = EAGAIN;
387
+ rb_sys_fail(0);
388
+ }
389
+
390
+ /* Retry */
391
+ continue;
392
+ }
393
+
394
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
395
+ /* Protocol error */
396
+ return -1;
397
+ }
398
+ }
399
+ }
400
+
401
+ /* Set reply object */
402
+ if (reply != NULL) {
403
+ *reply = (VALUE)aux;
404
+ }
405
+
406
+ return 0;
407
+ }
408
+
409
+ static VALUE connection_read(VALUE self) {
410
+ redisParentContext *pc;
411
+ VALUE reply;
412
+
413
+ Data_Get_Struct(self,redisParentContext,pc);
414
+ if (!pc->context)
415
+ rb_raise(rb_eRuntimeError, "not connected");
416
+
417
+ if (__get_reply(pc,&reply) == -1)
418
+ parent_context_raise(pc);
419
+
420
+ return reply;
421
+ }
422
+
423
+ static VALUE connection_set_timeout(VALUE self, VALUE usecs) {
424
+ redisParentContext *pc;
425
+ struct timeval *ptr;
426
+
427
+ Data_Get_Struct(self,redisParentContext,pc);
428
+
429
+ if (NUM2INT(usecs) < 0) {
430
+ rb_raise(rb_eArgError, "timeout cannot be negative");
431
+ } else {
432
+ parent_context_try_free_timeout(pc);
433
+
434
+ /* A timeout equal to zero means not to time out. This translates to a
435
+ * NULL timeout for select(2). Only allocate and populate the timeout
436
+ * when it is a positive integer. */
437
+ if (NUM2INT(usecs) > 0) {
438
+ ptr = malloc(sizeof(*ptr));
439
+ ptr->tv_sec = NUM2INT(usecs) / 1000000;
440
+ ptr->tv_usec = NUM2INT(usecs) % 1000000;
441
+ pc->timeout = ptr;
442
+ }
443
+ }
444
+
445
+ return Qnil;
446
+ }
447
+
448
+ static VALUE connection_fileno(VALUE self) {
449
+ redisParentContext *pc;
450
+
451
+ Data_Get_Struct(self,redisParentContext,pc);
452
+
453
+ if (!pc->context)
454
+ rb_raise(rb_eRuntimeError, "not connected");
455
+
456
+ return INT2NUM(pc->context->fd);
457
+ }
458
+
459
+ VALUE klass_connection;
460
+ void InitConnection(VALUE mod) {
461
+ klass_connection = rb_define_class_under(mod, "Connection", rb_cObject);
462
+ rb_define_alloc_func(klass_connection, connection_parent_context_alloc);
463
+ rb_define_method(klass_connection, "connect", connection_connect, -1);
464
+ rb_define_method(klass_connection, "connect_unix", connection_connect_unix, -1);
465
+ rb_define_method(klass_connection, "connected?", connection_is_connected, 0);
466
+ rb_define_method(klass_connection, "disconnect", connection_disconnect, 0);
467
+ rb_define_method(klass_connection, "timeout=", connection_set_timeout, 1);
468
+ rb_define_method(klass_connection, "fileno", connection_fileno, 0);
469
+ rb_define_method(klass_connection, "write", connection_write, 1);
470
+ rb_define_method(klass_connection, "flush", connection_flush, 0);
471
+ rb_define_method(klass_connection, "read", connection_read, 0);
472
+ }