hiredis-client 0.4.0

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