redis-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +190 -0
  3. data/CHANGELOG.md +3 -0
  4. data/Gemfile +23 -0
  5. data/Gemfile.lock +67 -0
  6. data/LICENSE.md +21 -0
  7. data/README.md +347 -0
  8. data/Rakefile +86 -0
  9. data/ext/redis_client/hiredis/extconf.rb +54 -0
  10. data/ext/redis_client/hiredis/hiredis_connection.c +696 -0
  11. data/ext/redis_client/hiredis/vendor/.gitignore +9 -0
  12. data/ext/redis_client/hiredis/vendor/.travis.yml +131 -0
  13. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +364 -0
  14. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +165 -0
  15. data/ext/redis_client/hiredis/vendor/COPYING +29 -0
  16. data/ext/redis_client/hiredis/vendor/Makefile +308 -0
  17. data/ext/redis_client/hiredis/vendor/README.md +664 -0
  18. data/ext/redis_client/hiredis/vendor/adapters/ae.h +130 -0
  19. data/ext/redis_client/hiredis/vendor/adapters/glib.h +156 -0
  20. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +84 -0
  21. data/ext/redis_client/hiredis/vendor/adapters/libev.h +179 -0
  22. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +175 -0
  23. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +117 -0
  24. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +115 -0
  25. data/ext/redis_client/hiredis/vendor/adapters/qt.h +135 -0
  26. data/ext/redis_client/hiredis/vendor/alloc.c +86 -0
  27. data/ext/redis_client/hiredis/vendor/alloc.h +91 -0
  28. data/ext/redis_client/hiredis/vendor/appveyor.yml +24 -0
  29. data/ext/redis_client/hiredis/vendor/async.c +887 -0
  30. data/ext/redis_client/hiredis/vendor/async.h +147 -0
  31. data/ext/redis_client/hiredis/vendor/async_private.h +75 -0
  32. data/ext/redis_client/hiredis/vendor/dict.c +352 -0
  33. data/ext/redis_client/hiredis/vendor/dict.h +126 -0
  34. data/ext/redis_client/hiredis/vendor/fmacros.h +12 -0
  35. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +13 -0
  36. data/ext/redis_client/hiredis/vendor/hiredis.c +1174 -0
  37. data/ext/redis_client/hiredis/vendor/hiredis.h +336 -0
  38. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +12 -0
  39. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +13 -0
  40. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +157 -0
  41. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +12 -0
  42. data/ext/redis_client/hiredis/vendor/net.c +612 -0
  43. data/ext/redis_client/hiredis/vendor/net.h +56 -0
  44. data/ext/redis_client/hiredis/vendor/read.c +739 -0
  45. data/ext/redis_client/hiredis/vendor/read.h +129 -0
  46. data/ext/redis_client/hiredis/vendor/sds.c +1289 -0
  47. data/ext/redis_client/hiredis/vendor/sds.h +278 -0
  48. data/ext/redis_client/hiredis/vendor/sdsalloc.h +44 -0
  49. data/ext/redis_client/hiredis/vendor/sockcompat.c +248 -0
  50. data/ext/redis_client/hiredis/vendor/sockcompat.h +92 -0
  51. data/ext/redis_client/hiredis/vendor/ssl.c +544 -0
  52. data/ext/redis_client/hiredis/vendor/test.c +1401 -0
  53. data/ext/redis_client/hiredis/vendor/test.sh +78 -0
  54. data/ext/redis_client/hiredis/vendor/win32.h +56 -0
  55. data/lib/redis-client.rb +3 -0
  56. data/lib/redis_client/buffered_io.rb +149 -0
  57. data/lib/redis_client/config.rb +174 -0
  58. data/lib/redis_client/connection.rb +86 -0
  59. data/lib/redis_client/hiredis_connection.rb +78 -0
  60. data/lib/redis_client/pooled.rb +86 -0
  61. data/lib/redis_client/resp3.rb +225 -0
  62. data/lib/redis_client/sentinel_config.rb +134 -0
  63. data/lib/redis_client/version.rb +5 -0
  64. data/lib/redis_client.rb +438 -0
  65. data/redis-client.gemspec +34 -0
  66. metadata +125 -0
@@ -0,0 +1,696 @@
1
+ // Extensive parts of this code is taken from `hiredis-rb`, so we're keeping the
2
+ // initial copyright:
3
+ //
4
+ // Copyright (c) 2010-2012, Pieter Noordhuis
5
+ //
6
+ // All rights reserved.
7
+ //
8
+ // Redistribution and use in source and binary forms, with or without
9
+ // modification, are permitted provided that the following conditions are met:
10
+ //
11
+ // * Redistributions of source code must retain the above copyright notice, this
12
+ // list of conditions and the following disclaimer.
13
+ //
14
+ // * Redistributions in binary form must reproduce the above copyright notice,
15
+ // this list of conditions and the following disclaimer in the documentation
16
+ // and/or other materials provided with the distribution.
17
+ //
18
+ // * Neither the name of Redis nor the names of its contributors may be used to
19
+ // endorse or promote products derived from this software without specific prior
20
+ // written permission.
21
+ //
22
+ // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23
+ // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24
+ // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+ // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26
+ // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27
+ // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28
+ // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29
+ // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
+ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31
+ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
33
+ #include "ruby.h"
34
+ #include "ruby/encoding.h"
35
+ #include <errno.h>
36
+ #include <sys/socket.h>
37
+ #include <stdbool.h>
38
+ #include "hiredis.h"
39
+ #include "hiredis_ssl.h"
40
+
41
+ static VALUE rb_cSet, rb_eRedisClientCommandError, rb_eRedisClientConnectionError;
42
+ static VALUE rb_eRedisClientConnectTimeoutError, rb_eRedisClientReadTimeoutError, rb_eRedisClientWriteTimeoutError;
43
+ static ID id_parse, id_add, id_new;
44
+
45
+ typedef struct {
46
+ redisSSLContext *context;
47
+ } hiredis_ssl_context_t;
48
+
49
+ #define ENSURE_CONNECTED(connection) if (!connection->context) rb_raise(rb_eRuntimeError, "[BUG] not connected");
50
+
51
+ #define SSL_CONTEXT(from, name) \
52
+ hiredis_ssl_context_t *name = NULL; \
53
+ TypedData_Get_Struct(from, hiredis_ssl_context_t, &hiredis_ssl_context_data_type, name); \
54
+ if(name == NULL) { \
55
+ rb_raise(rb_eArgError, "NULL found for " # name " when shouldn't be."); \
56
+ }
57
+
58
+ void hiredis_ssl_context_mark(void *ptr) { }
59
+
60
+ void hiredis_ssl_context_free(void *ptr) {
61
+ hiredis_ssl_context_t *ssl_context = (hiredis_ssl_context_t *)ptr;
62
+ if (ssl_context->context) {
63
+ redisFreeSSLContext(ssl_context->context);
64
+ ssl_context = NULL;
65
+ }
66
+ }
67
+
68
+ static size_t hiredis_ssl_context_memsize(const void *ptr) {
69
+ size_t size = sizeof(hiredis_ssl_context_t);
70
+ // Note: I couldn't find a way to measure the SSLContext size.
71
+ return size;
72
+ }
73
+
74
+ static const rb_data_type_t hiredis_ssl_context_data_type = {
75
+ .wrap_struct_name = "redis-client:hiredis_ssl_context",
76
+ .function = {
77
+ .dmark = hiredis_ssl_context_mark,
78
+ .dfree = hiredis_ssl_context_free,
79
+ .dsize = hiredis_ssl_context_memsize,
80
+ #ifdef HAS_GC_COMPACT
81
+ .dcompact = NULL
82
+ #endif
83
+ },
84
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
85
+ };
86
+
87
+ static VALUE hiredis_ssl_context_alloc(VALUE klass) {
88
+ hiredis_ssl_context_t *ssl_context;
89
+ return TypedData_Make_Struct(klass, hiredis_ssl_context_t, &hiredis_ssl_context_data_type, ssl_context);
90
+ }
91
+
92
+ static VALUE hiredis_ssl_context_init(VALUE self, VALUE ca_file, VALUE ca_path, VALUE cert, VALUE key, VALUE hostname) {
93
+ redisSSLContextError ssl_error = 0;
94
+ SSL_CONTEXT(self, ssl_context);
95
+
96
+ ssl_context->context = redisCreateSSLContext(
97
+ RTEST(ca_file) ? StringValueCStr(ca_file) : NULL,
98
+ RTEST(ca_path) ? StringValueCStr(ca_path) : NULL,
99
+ RTEST(cert) ? StringValueCStr(cert) : NULL,
100
+ RTEST(key) ? StringValueCStr(key) : NULL,
101
+ RTEST(hostname) ? StringValueCStr(hostname) : NULL,
102
+ &ssl_error
103
+ );
104
+
105
+ if (ssl_error) {
106
+ return rb_str_new_cstr(redisSSLContextGetError(ssl_error));
107
+ }
108
+
109
+ if (!ssl_context->context) {
110
+ return rb_str_new_cstr("Unknown error while creating SSLContext");
111
+ }
112
+
113
+ return Qnil;
114
+ }
115
+
116
+ static void *reply_append(const redisReadTask *task, VALUE value) {
117
+ if (task && task->parent) {
118
+ VALUE parent = (VALUE)task->parent->obj;
119
+
120
+ switch (task->parent->type) {
121
+ case REDIS_REPLY_ARRAY:
122
+ case REDIS_REPLY_PUSH:
123
+ rb_ary_store(parent, task->idx, value);
124
+ break;
125
+ case REDIS_REPLY_MAP:
126
+ if (task->idx % 2) {
127
+ VALUE key = (VALUE)task->parent->privdata;
128
+ task->parent->privdata = NULL;
129
+ rb_hash_aset(parent, key, value);
130
+ } else {
131
+ task->parent->privdata = (void*)value;
132
+ }
133
+ break;
134
+ case REDIS_REPLY_SET:
135
+ rb_funcall(parent, id_add, 1, value);
136
+ break;
137
+ default:
138
+ rb_bug("[hiredis] Unexpected task parent type %d", task->parent->type);
139
+ break;
140
+ }
141
+ }
142
+ return (void*)value;
143
+ }
144
+
145
+ static void *reply_create_string(const redisReadTask *task, char *cstr, size_t len) {
146
+ VALUE string = rb_external_str_new(cstr, len);
147
+ if (rb_enc_str_coderange(string) == ENC_CODERANGE_BROKEN) {
148
+ rb_enc_associate(string, rb_ascii8bit_encoding());
149
+ }
150
+
151
+ if (task->type == REDIS_REPLY_ERROR) {
152
+ string = rb_funcall(rb_eRedisClientCommandError, id_parse, 1, string);
153
+ }
154
+
155
+ return reply_append(task, string);
156
+ }
157
+ static void *reply_create_array(const redisReadTask *task, size_t elements) {
158
+ VALUE value = Qnil;
159
+ switch (task->type) {
160
+ case REDIS_REPLY_PUSH:
161
+ case REDIS_REPLY_ARRAY:
162
+ value = rb_ary_new_capa(elements);
163
+ break;
164
+ case REDIS_REPLY_MAP:
165
+ value = rb_hash_new();
166
+ break;
167
+ case REDIS_REPLY_SET:
168
+ value = rb_funcallv(rb_cSet, id_new, 0, NULL);
169
+ break;
170
+ default:
171
+ rb_bug("[hiredis] Unexpected create array type %d", task->parent->type);
172
+ break;
173
+ }
174
+
175
+ return reply_append(task, value);
176
+ }
177
+ static void *reply_create_integer(const redisReadTask *task, long long value) {
178
+ return reply_append(task, LL2NUM(value));
179
+ }
180
+ static void *reply_create_double(const redisReadTask *task, double value, char *str, size_t len) {
181
+ return reply_append(task, DBL2NUM(value));
182
+ }
183
+ static void *reply_create_nil(const redisReadTask *task) {
184
+ return reply_append(task, Qnil);
185
+ }
186
+ static void *reply_create_bool(const redisReadTask *task, int bval) {
187
+ reply_append(task, bval ? Qtrue : Qfalse);
188
+ // Qfalse == NULL, so we can't return Qfalse as it would be interpreted as out of memory error.
189
+ // So we return Qnil instead.
190
+ return (void*)(bval ? Qtrue : Qnil);
191
+ }
192
+
193
+ static void reply_free(void *ptr) {
194
+ // we let GC handle it.
195
+ }
196
+
197
+ /* Default set of functions to build the reply. Keep in mind that such a
198
+ * function returning NULL is interpreted as OOM. */
199
+ static redisReplyObjectFunctions reply_functions = {
200
+ reply_create_string,
201
+ reply_create_array,
202
+ reply_create_integer,
203
+ reply_create_double,
204
+ reply_create_nil,
205
+ reply_create_bool,
206
+ reply_free,
207
+ };
208
+
209
+ #define CONNECTION(from, name) \
210
+ hiredis_connection_t *name = NULL; \
211
+ TypedData_Get_Struct(from, hiredis_connection_t, &hiredis_connection_data_type, name); \
212
+ if(name == NULL) { \
213
+ rb_raise(rb_eArgError, "NULL found for " # name " when shouldn't be."); \
214
+ }
215
+
216
+
217
+ typedef struct {
218
+ redisContext *context;
219
+ struct timeval connect_timeout;
220
+ struct timeval read_timeout;
221
+ struct timeval write_timeout;
222
+ } hiredis_connection_t;
223
+
224
+ void hiredis_connection_mark_task(redisReadTask *task) {
225
+ while (task) {
226
+ if (task->obj) { rb_gc_mark((VALUE)task->obj); }
227
+ if (task->privdata) { rb_gc_mark((VALUE)task->privdata); }
228
+ task = task->parent;
229
+ }
230
+ }
231
+
232
+ void hiredis_connection_mark(void *ptr) {
233
+ hiredis_connection_t *connection = ptr;
234
+ if (connection->context) {
235
+ redisReader *reader = connection->context->reader;
236
+ for (int index = 0; index < reader->tasks; index++) {
237
+ hiredis_connection_mark_task(reader->task[index]);
238
+ }
239
+ }
240
+ }
241
+ void hiredis_connection_free(void *ptr) {
242
+ hiredis_connection_t *connection = ptr;
243
+ if (connection) {
244
+ if (connection->context) {
245
+ redisFree(connection->context);
246
+ }
247
+ xfree(connection);
248
+ }
249
+ }
250
+
251
+ static size_t hiredis_connection_task_memsize(redisReadTask *task) {
252
+ size_t size = 0;
253
+ while (task) {
254
+ size += sizeof(redisReadTask);
255
+ task = task->parent;
256
+ }
257
+ return size;
258
+ }
259
+
260
+ static size_t hiredis_connection_memsize(const void *ptr) {
261
+ hiredis_connection_t *connection = (hiredis_connection_t *)ptr;
262
+
263
+ size_t size = sizeof(hiredis_connection_t);
264
+
265
+ if (connection->context) {
266
+ size += sizeof(redisContext);
267
+ if (connection->context->reader) {
268
+ redisReader *reader = connection->context->reader;
269
+ size += sizeof(redisReader);
270
+ size += reader->maxbuf;
271
+
272
+ for (int index = 0; index < reader->tasks; index++) {
273
+ size += hiredis_connection_task_memsize(reader->task[index]);
274
+ }
275
+ }
276
+ }
277
+
278
+ return size;
279
+ }
280
+
281
+ static const rb_data_type_t hiredis_connection_data_type = {
282
+ .wrap_struct_name = "redis-client:hiredis_connection",
283
+ .function = {
284
+ .dmark = hiredis_connection_mark,
285
+ .dfree = hiredis_connection_free,
286
+ .dsize = hiredis_connection_memsize,
287
+ #ifdef HAS_GC_COMPACT
288
+ .dcompact = NULL
289
+ #endif
290
+ },
291
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
292
+ };
293
+
294
+ static VALUE hiredis_alloc(VALUE klass) {
295
+ hiredis_connection_t *connection;
296
+ return TypedData_Make_Struct(klass, hiredis_connection_t, &hiredis_connection_data_type, connection);
297
+ }
298
+
299
+ static inline void redis_raise_error_and_disconnect(redisContext *context, VALUE timeout_error) {
300
+ if (!context) return;
301
+
302
+ int err = context->err;
303
+ char errstr[128];
304
+ MEMCPY(context->errstr, errstr, char, 128);
305
+ redisFree(context);
306
+
307
+ // OpenSSL bug: The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected EOF from the peer.
308
+ if (errno == EAGAIN || (err == REDIS_ERR_IO && errno == 0)) {
309
+ errno = 0;
310
+ rb_raise(timeout_error, "Resource temporarily unavailable");
311
+ }
312
+
313
+ switch(err) {
314
+ case REDIS_ERR_IO:
315
+ rb_sys_fail(0);
316
+ break;
317
+ default:
318
+ /* Raise something else */
319
+ rb_raise(rb_eRedisClientConnectionError, "%s", errstr);
320
+ }
321
+ }
322
+
323
+ static inline void hiredis_raise_error_and_disconnect(hiredis_connection_t *connection, VALUE timeout_error) {
324
+ redisContext *context = connection->context;
325
+ if (!context) return;
326
+ connection->context = NULL;
327
+ redis_raise_error_and_disconnect(context, timeout_error);
328
+ }
329
+
330
+ static VALUE hiredis_set_connect_timeout(VALUE self, VALUE timeout_us) {
331
+ CONNECTION(self, connection);
332
+ connection->connect_timeout.tv_sec = NUM2INT(timeout_us) / 1000000;
333
+ connection->connect_timeout.tv_usec = NUM2INT(timeout_us) % 1000000;
334
+ return timeout_us;
335
+ }
336
+
337
+ static VALUE hiredis_set_read_timeout(VALUE self, VALUE timeout_us) {
338
+ CONNECTION(self, connection);
339
+ connection->read_timeout.tv_sec = NUM2INT(timeout_us) / 1000000;
340
+ connection->read_timeout.tv_usec = NUM2INT(timeout_us) % 1000000;
341
+ return timeout_us;
342
+ }
343
+
344
+ static VALUE hiredis_set_write_timeout(VALUE self, VALUE timeout_us) {
345
+ CONNECTION(self, connection);
346
+ connection->write_timeout.tv_sec = NUM2INT(timeout_us) / 1000000;
347
+ connection->write_timeout.tv_usec = NUM2INT(timeout_us) % 1000000;
348
+ return timeout_us;
349
+ }
350
+
351
+ static int hiredis_wait_readable(int fd, const struct timeval *timeout, int *isset) {
352
+ struct timeval to;
353
+ struct timeval *toptr = NULL;
354
+
355
+ rb_fdset_t fds;
356
+
357
+ /* Be cautious: a call to rb_fd_init to initialize the rb_fdset_t structure
358
+ * must be paired with a call to rb_fd_term to free it. */
359
+ rb_fd_init(&fds);
360
+ rb_fd_set(fd, &fds);
361
+
362
+ /* rb_thread_{fd_,}select modifies the passed timeval, so we pass a copy */
363
+ if (timeout != NULL && (timeout->tv_sec || timeout->tv_usec)) {
364
+ memcpy(&to, timeout, sizeof(to));
365
+ toptr = &to;
366
+ }
367
+
368
+ if (rb_thread_fd_select(fd + 1, &fds, NULL, NULL, toptr) < 0) {
369
+ rb_fd_term(&fds);
370
+ return -1;
371
+ }
372
+
373
+ if (rb_fd_isset(fd, &fds) && isset) {
374
+ *isset = 1;
375
+ }
376
+
377
+ rb_fd_term(&fds);
378
+ return 0;
379
+ }
380
+
381
+ static int hiredis_wait_writable(int fd, const struct timeval *timeout, int *isset) {
382
+ struct timeval to;
383
+ struct timeval *toptr = NULL;
384
+
385
+ /* Be cautious: a call to rb_fd_init to initialize the rb_fdset_t structure
386
+ * must be paired with a call to rb_fd_term to free it. */
387
+ rb_fdset_t fds;
388
+ rb_fd_init(&fds);
389
+ rb_fd_set(fd, &fds);
390
+
391
+ /* rb_thread_{fd_,}select modifies the passed timeval, so we pass a copy */
392
+ if (timeout != NULL && (timeout->tv_sec || timeout->tv_usec)) {
393
+ memcpy(&to, timeout, sizeof(to));
394
+ toptr = &to;
395
+ }
396
+
397
+ if (rb_thread_fd_select(fd + 1, NULL, &fds, NULL, toptr) < 0) {
398
+ rb_fd_term(&fds);
399
+ return -1;
400
+ }
401
+
402
+ if (rb_fd_isset(fd, &fds) && isset) {
403
+ *isset = 1;
404
+ }
405
+
406
+ rb_fd_term(&fds);
407
+ return 0;
408
+ }
409
+
410
+ static VALUE hiredis_connect_finish(hiredis_connection_t *connection, redisContext *context) {
411
+ if (context->err) {
412
+ redis_raise_error_and_disconnect(context, rb_eRedisClientConnectTimeoutError);
413
+ }
414
+
415
+ int writable = 0;
416
+ int optval = 0;
417
+ socklen_t optlen = sizeof(optval);
418
+
419
+ /* Wait for socket to become writable */
420
+ if (hiredis_wait_writable(context->fd, &connection->connect_timeout, &writable) < 0) {
421
+ redis_raise_error_and_disconnect(context, rb_eRedisClientConnectTimeoutError);
422
+ }
423
+
424
+ if (!writable) {
425
+ errno = ETIMEDOUT;
426
+ redis_raise_error_and_disconnect(context, rb_eRedisClientConnectTimeoutError);
427
+ }
428
+
429
+ /* Check for socket error */
430
+ if (getsockopt(context->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
431
+ redis_raise_error_and_disconnect(context, rb_eRedisClientConnectTimeoutError);
432
+ }
433
+
434
+ if (optval) {
435
+ errno = optval;
436
+ redis_raise_error_and_disconnect(context, rb_eRedisClientConnectTimeoutError);
437
+ }
438
+
439
+ context->reader->fn = &reply_functions;
440
+ redisSetPushCallback(context, NULL);
441
+ connection->context = context;
442
+ return Qtrue;
443
+ }
444
+
445
+ static VALUE hiredis_connect_tcp(VALUE self, VALUE host, VALUE port) {
446
+ CONNECTION(self, connection);
447
+ if (connection->context) {
448
+ redisFree(connection->context);
449
+ connection->context = NULL;
450
+ }
451
+ return hiredis_connect_finish(connection, redisConnectNonBlock(StringValuePtr(host), NUM2INT(port)));
452
+ }
453
+
454
+ static VALUE hiredis_connect_unix(VALUE self, VALUE path) {
455
+ CONNECTION(self, connection);
456
+ if (connection->context) {
457
+ redisFree(connection->context);
458
+ connection->context = NULL;
459
+ }
460
+ return hiredis_connect_finish(connection, redisConnectUnixNonBlock(StringValuePtr(path)));
461
+ }
462
+
463
+ static VALUE hiredis_init_ssl(VALUE self, VALUE ssl_param) {
464
+ CONNECTION(self, connection);
465
+ SSL_CONTEXT(ssl_param, ssl_context)
466
+
467
+ if (redisInitiateSSLWithContext(connection->context, ssl_context->context) != REDIS_OK) {
468
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
469
+ }
470
+
471
+ redisSSL *redis_ssl = redisGetSSLSocket(connection->context);
472
+
473
+ if (redis_ssl->wantRead) {
474
+ int readable = 0;
475
+ if (hiredis_wait_readable(connection->context->fd, &connection->connect_timeout, &readable) < 0) {
476
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
477
+ }
478
+ if (!readable) {
479
+ errno = EAGAIN;
480
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
481
+ }
482
+
483
+ if (redisInitiateSSLContinue(connection->context) != REDIS_OK) {
484
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientConnectTimeoutError);
485
+ };
486
+ }
487
+
488
+ return Qtrue;
489
+ }
490
+
491
+ static VALUE hiredis_connected_p(VALUE self) {
492
+ CONNECTION(self, connection);
493
+
494
+ return connection->context ? Qtrue : Qfalse;
495
+ }
496
+
497
+ static VALUE hiredis_write(VALUE self, VALUE command) {
498
+ Check_Type(command, T_ARRAY);
499
+
500
+ CONNECTION(self, connection);
501
+ ENSURE_CONNECTED(connection);
502
+
503
+ int size = (int)RARRAY_LEN(command);
504
+ VALUE _argv_handle;
505
+ char **argv = RB_ALLOCV_N(char *, _argv_handle, size);
506
+
507
+ VALUE _argv_len_handle;
508
+ size_t *argv_len = RB_ALLOCV_N(size_t, _argv_len_handle, size);
509
+
510
+ for (int index = 0; index < size; index++) {
511
+ VALUE arg = rb_ary_entry(command, index);
512
+ Check_Type(arg, T_STRING);
513
+ argv[index] = RSTRING_PTR(arg);
514
+ argv_len[index] = RSTRING_LEN(arg);
515
+ }
516
+
517
+ redisAppendCommandArgv(connection->context, size, (const char **)argv, argv_len);
518
+ return Qnil;
519
+ }
520
+
521
+ static VALUE hiredis_flush(VALUE self) {
522
+ CONNECTION(self, connection);
523
+ ENSURE_CONNECTED(connection);
524
+
525
+ int wdone = 0;
526
+ while (!wdone) {
527
+ errno = 0;
528
+ if (redisBufferWrite(connection->context, &wdone) == REDIS_ERR) {
529
+ if (errno == EAGAIN) {
530
+ int writable = 0;
531
+
532
+ if (hiredis_wait_writable(connection->context->fd, &connection->write_timeout, &writable) < 0) {
533
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientWriteTimeoutError);
534
+ }
535
+
536
+ if (!writable) {
537
+ errno = EAGAIN;
538
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientWriteTimeoutError);
539
+ }
540
+ } else {
541
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientWriteTimeoutError);
542
+ }
543
+ }
544
+ }
545
+
546
+ return Qtrue;
547
+ }
548
+
549
+ static int hiredis_read_internal(hiredis_connection_t *connection, VALUE *reply) {
550
+ void *redis_reply = NULL;
551
+ int wdone = 0;
552
+
553
+ /* Try to read pending replies */
554
+ if (redisGetReplyFromReader(connection->context, &redis_reply) == REDIS_ERR) {
555
+ /* Protocol error */
556
+ return -1;
557
+ }
558
+
559
+ if (redis_reply == NULL) {
560
+ /* Write until the write buffer is drained */
561
+ while (!wdone) {
562
+ errno = 0;
563
+
564
+ if (redisBufferWrite(connection->context, &wdone) == REDIS_ERR) {
565
+ /* Socket error */
566
+ return -1;
567
+ }
568
+
569
+ if (errno == EAGAIN) {
570
+ int writable = 0;
571
+
572
+ if (hiredis_wait_writable(connection->context->fd, &connection->write_timeout, &writable) < 0) {
573
+ return -2;
574
+ }
575
+
576
+ if (!writable) {
577
+ errno = EAGAIN;
578
+ return -2;
579
+ }
580
+ }
581
+ }
582
+
583
+ /* Read until there is a full reply */
584
+ while (redis_reply == NULL) {
585
+ errno = 0;
586
+
587
+ if (redisBufferRead(connection->context) == REDIS_ERR) {
588
+ /* Socket error */
589
+ return -1;
590
+ }
591
+
592
+ if (errno == EAGAIN) {
593
+ int readable = 0;
594
+
595
+ if (hiredis_wait_readable(connection->context->fd, &connection->read_timeout, &readable) < 0) {
596
+ return -2;
597
+ }
598
+
599
+ if (!readable) {
600
+ errno = EAGAIN;
601
+ return -2;
602
+ }
603
+
604
+ /* Retry */
605
+ continue;
606
+ }
607
+
608
+ if (redisGetReplyFromReader(connection->context, &redis_reply) == REDIS_ERR) {
609
+ /* Protocol error */
610
+ return -1;
611
+ }
612
+ }
613
+ }
614
+
615
+ /* Set reply object */
616
+ if (reply != NULL) {
617
+ *reply = (VALUE)redis_reply;
618
+ }
619
+
620
+ return 0;
621
+ }
622
+
623
+ static VALUE hiredis_read(VALUE self) {
624
+ CONNECTION(self, connection);
625
+ ENSURE_CONNECTED(connection);
626
+
627
+ VALUE reply = Qnil;
628
+ if (hiredis_read_internal(connection, &reply)) {
629
+ hiredis_raise_error_and_disconnect(connection, rb_eRedisClientReadTimeoutError);
630
+ }
631
+ return reply;
632
+ }
633
+
634
+ static VALUE hiredis_close(VALUE self) {
635
+ CONNECTION(self, connection);
636
+ if (connection->context) {
637
+ redisFree(connection->context);
638
+ connection->context = NULL;
639
+ }
640
+ return Qnil;
641
+ }
642
+
643
+ void Init_hiredis_connection(void) {
644
+ #ifdef RUBY_ASSERT
645
+ // Qfalse == NULL, so we can't return Qfalse in `reply_create_bool()`
646
+ RUBY_ASSERT((void *)Qfalse == NULL);
647
+ RUBY_ASSERT((void *)Qnil != NULL);
648
+ #endif
649
+
650
+ redisInitOpenSSL();
651
+
652
+ id_parse = rb_intern("parse");
653
+ id_add = rb_intern("add");
654
+ id_new = rb_intern("new");
655
+
656
+ rb_cSet = rb_const_get(rb_cObject, rb_intern("Set"));
657
+ rb_global_variable(&rb_cSet);
658
+
659
+ VALUE rb_cRedisClient = rb_const_get(rb_cObject, rb_intern("RedisClient"));
660
+
661
+ rb_eRedisClientCommandError = rb_const_get(rb_cRedisClient, rb_intern("CommandError"));
662
+ rb_global_variable(&rb_eRedisClientCommandError);
663
+
664
+ rb_eRedisClientConnectionError = rb_const_get(rb_cRedisClient, rb_intern("ConnectionError"));
665
+ rb_global_variable(&rb_eRedisClientConnectionError);
666
+
667
+ rb_eRedisClientConnectTimeoutError = rb_const_get(rb_cRedisClient, rb_intern("ConnectTimeoutError"));
668
+ rb_global_variable(&rb_eRedisClientConnectTimeoutError);
669
+
670
+ rb_eRedisClientReadTimeoutError = rb_const_get(rb_cRedisClient, rb_intern("ReadTimeoutError"));
671
+ rb_global_variable(&rb_eRedisClientReadTimeoutError);
672
+
673
+ rb_eRedisClientWriteTimeoutError = rb_const_get(rb_cRedisClient, rb_intern("WriteTimeoutError"));
674
+ rb_global_variable(&rb_eRedisClientWriteTimeoutError);
675
+
676
+ VALUE rb_cHiredisConnection = rb_define_class_under(rb_cRedisClient, "HiredisConnection", rb_cObject);
677
+ rb_define_alloc_func(rb_cHiredisConnection, hiredis_alloc);
678
+
679
+ rb_define_private_method(rb_cHiredisConnection, "connect_timeout_us=", hiredis_set_connect_timeout, 1);
680
+ rb_define_private_method(rb_cHiredisConnection, "read_timeout_us=", hiredis_set_read_timeout, 1);
681
+ rb_define_private_method(rb_cHiredisConnection, "write_timeout_us=", hiredis_set_write_timeout, 1);
682
+
683
+ rb_define_private_method(rb_cHiredisConnection, "connect_tcp", hiredis_connect_tcp, 2);
684
+ rb_define_private_method(rb_cHiredisConnection, "connect_unix", hiredis_connect_unix, 1);
685
+ rb_define_private_method(rb_cHiredisConnection, "init_ssl", hiredis_init_ssl, 1);
686
+ rb_define_method(rb_cHiredisConnection, "connected?", hiredis_connected_p, 0);
687
+
688
+ rb_define_private_method(rb_cHiredisConnection, "_write", hiredis_write, 1);
689
+ rb_define_private_method(rb_cHiredisConnection, "_read", hiredis_read, 0);
690
+ rb_define_private_method(rb_cHiredisConnection, "flush", hiredis_flush, 0);
691
+ rb_define_method(rb_cHiredisConnection, "close", hiredis_close, 0);
692
+
693
+ VALUE rb_cHiredisSSLContext = rb_define_class_under(rb_cHiredisConnection, "SSLContext", rb_cObject);
694
+ rb_define_alloc_func(rb_cHiredisSSLContext, hiredis_ssl_context_alloc);
695
+ rb_define_private_method(rb_cHiredisSSLContext, "init", hiredis_ssl_context_init, 5);
696
+ }
@@ -0,0 +1,9 @@
1
+ /hiredis-test
2
+ /examples/hiredis-example*
3
+ /*.o
4
+ /*.so
5
+ /*.dylib
6
+ /*.a
7
+ /*.pc
8
+ *.dSYM
9
+ tags