isomorfeus-iodine 0.7.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +32 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1038 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +44 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/config.ru +56 -0
  25. data/examples/echo.ru +59 -0
  26. data/examples/hello.ru +29 -0
  27. data/examples/pubsub_engine.ru +81 -0
  28. data/examples/redis.ru +70 -0
  29. data/examples/shootout.ru +73 -0
  30. data/examples/sub-protocols.ru +90 -0
  31. data/examples/tcp_client.rb +66 -0
  32. data/examples/x-sendfile.ru +14 -0
  33. data/exe/iodine +277 -0
  34. data/ext/iodine/extconf.rb +109 -0
  35. data/ext/iodine/fio.c +11985 -0
  36. data/ext/iodine/fio.h +6373 -0
  37. data/ext/iodine/fio_cli.c +431 -0
  38. data/ext/iodine/fio_cli.h +189 -0
  39. data/ext/iodine/fio_json_parser.h +687 -0
  40. data/ext/iodine/fio_siphash.c +157 -0
  41. data/ext/iodine/fio_siphash.h +37 -0
  42. data/ext/iodine/fio_tls.h +129 -0
  43. data/ext/iodine/fio_tls_missing.c +649 -0
  44. data/ext/iodine/fio_tls_openssl.c +1056 -0
  45. data/ext/iodine/fio_tmpfile.h +50 -0
  46. data/ext/iodine/fiobj.h +44 -0
  47. data/ext/iodine/fiobj4fio.h +21 -0
  48. data/ext/iodine/fiobj_ary.c +333 -0
  49. data/ext/iodine/fiobj_ary.h +139 -0
  50. data/ext/iodine/fiobj_data.c +1185 -0
  51. data/ext/iodine/fiobj_data.h +167 -0
  52. data/ext/iodine/fiobj_hash.c +409 -0
  53. data/ext/iodine/fiobj_hash.h +176 -0
  54. data/ext/iodine/fiobj_json.c +622 -0
  55. data/ext/iodine/fiobj_json.h +68 -0
  56. data/ext/iodine/fiobj_mem.h +71 -0
  57. data/ext/iodine/fiobj_mustache.c +317 -0
  58. data/ext/iodine/fiobj_mustache.h +62 -0
  59. data/ext/iodine/fiobj_numbers.c +344 -0
  60. data/ext/iodine/fiobj_numbers.h +127 -0
  61. data/ext/iodine/fiobj_str.c +433 -0
  62. data/ext/iodine/fiobj_str.h +172 -0
  63. data/ext/iodine/fiobject.c +620 -0
  64. data/ext/iodine/fiobject.h +654 -0
  65. data/ext/iodine/hpack.h +1923 -0
  66. data/ext/iodine/http.c +2754 -0
  67. data/ext/iodine/http.h +1002 -0
  68. data/ext/iodine/http1.c +912 -0
  69. data/ext/iodine/http1.h +29 -0
  70. data/ext/iodine/http1_parser.h +873 -0
  71. data/ext/iodine/http_internal.c +1278 -0
  72. data/ext/iodine/http_internal.h +237 -0
  73. data/ext/iodine/http_mime_parser.h +350 -0
  74. data/ext/iodine/iodine.c +1430 -0
  75. data/ext/iodine/iodine.h +63 -0
  76. data/ext/iodine/iodine_caller.c +218 -0
  77. data/ext/iodine/iodine_caller.h +27 -0
  78. data/ext/iodine/iodine_connection.c +933 -0
  79. data/ext/iodine/iodine_connection.h +55 -0
  80. data/ext/iodine/iodine_defer.c +420 -0
  81. data/ext/iodine/iodine_defer.h +6 -0
  82. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  83. data/ext/iodine/iodine_helpers.c +282 -0
  84. data/ext/iodine/iodine_helpers.h +12 -0
  85. data/ext/iodine/iodine_http.c +1171 -0
  86. data/ext/iodine/iodine_http.h +23 -0
  87. data/ext/iodine/iodine_json.c +302 -0
  88. data/ext/iodine/iodine_json.h +6 -0
  89. data/ext/iodine/iodine_mustache.c +567 -0
  90. data/ext/iodine/iodine_mustache.h +6 -0
  91. data/ext/iodine/iodine_pubsub.c +580 -0
  92. data/ext/iodine/iodine_pubsub.h +26 -0
  93. data/ext/iodine/iodine_rack_io.c +281 -0
  94. data/ext/iodine/iodine_rack_io.h +20 -0
  95. data/ext/iodine/iodine_store.c +142 -0
  96. data/ext/iodine/iodine_store.h +20 -0
  97. data/ext/iodine/iodine_tcp.c +346 -0
  98. data/ext/iodine/iodine_tcp.h +13 -0
  99. data/ext/iodine/iodine_tls.c +261 -0
  100. data/ext/iodine/iodine_tls.h +13 -0
  101. data/ext/iodine/mustache_parser.h +1546 -0
  102. data/ext/iodine/redis_engine.c +957 -0
  103. data/ext/iodine/redis_engine.h +79 -0
  104. data/ext/iodine/resp_parser.h +317 -0
  105. data/ext/iodine/websocket_parser.h +505 -0
  106. data/ext/iodine/websockets.c +735 -0
  107. data/ext/iodine/websockets.h +185 -0
  108. data/isomorfeus-iodine.gemspec +42 -0
  109. data/lib/iodine/connection.rb +61 -0
  110. data/lib/iodine/json.rb +42 -0
  111. data/lib/iodine/mustache.rb +113 -0
  112. data/lib/iodine/pubsub.rb +55 -0
  113. data/lib/iodine/rack_utils.rb +43 -0
  114. data/lib/iodine/tls.rb +16 -0
  115. data/lib/iodine/version.rb +3 -0
  116. data/lib/iodine.rb +274 -0
  117. data/lib/rack/handler/iodine.rb +33 -0
  118. data/logo.png +0 -0
  119. metadata +271 -0
@@ -0,0 +1,580 @@
1
+ #include "iodine_pubsub.h"
2
+ #include "iodine_fiobj2rb.h"
3
+
4
+ #include "redis_engine.h"
5
+
6
+ /*
7
+ NOTE:
8
+
9
+ This file defines Pub/Sub management and settings, not Pub/Sub usage.
10
+
11
+ This file doen't include the `Iodine.subscribe`, `Iodine.unsubscribe` and
12
+ `Iodine.publish` methods.
13
+
14
+ These methods are all defined in the Connection module (iodine_connection.h).
15
+ */
16
+
17
+ /* *****************************************************************************
18
+ static consts
19
+ ***************************************************************************** */
20
+
21
+ static ID subscribe_id;
22
+ static ID unsubscribe_id;
23
+ static ID publish_id;
24
+ static ID default_id;
25
+ static ID redis_id;
26
+ static ID call_id;
27
+
28
+ /**
29
+ The {Iodine::PubSub::Engine} class is the parent for all engines to inherit
30
+ from.
31
+
32
+ Engines should inherit this class and override the `subscribe`, `unsubscribe`
33
+ and `publish` callbacks (which shall be called by {Iodine}).
34
+
35
+ After creation, Engines should attach themselves to Iodine using
36
+ {Iodine::PubSub.attach} or their callbacks will never get called.
37
+
38
+ Engines can also set themselves to be the default engine using
39
+ {Iodine::PubSub.default=}.
40
+ */
41
+ static VALUE EngineClass;
42
+
43
+ /* *****************************************************************************
44
+ Ruby <=> C Callbacks
45
+ ***************************************************************************** */
46
+
47
+ typedef struct {
48
+ iodine_pubsub_s *eng;
49
+ fio_str_info_s ch;
50
+ fio_str_info_s msg;
51
+ fio_match_fn pattern;
52
+ } iodine_pubsub_task_s;
53
+
54
+ #define iodine_engine(eng) ((iodine_pubsub_s *)(eng))
55
+
56
+ /* calls an engine's `subscribe` callback within the GVL */
57
+ static void *iodine_pubsub_GIL_subscribe(void *tsk_) {
58
+ iodine_pubsub_task_s *task = tsk_;
59
+ VALUE args[2];
60
+ args[0] = rb_str_new(task->ch.data, task->ch.len);
61
+ args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis
62
+ IodineCaller.call2(task->eng->handler, subscribe_id, 2, args);
63
+ return NULL;
64
+ }
65
+
66
+ /** Must subscribe channel. Failures are ignored. */
67
+ static void iodine_pubsub_on_subscribe(const fio_pubsub_engine_s *eng,
68
+ fio_str_info_s channel,
69
+ fio_match_fn match) {
70
+ if (iodine_engine(eng)->handler == Qnil) {
71
+ return;
72
+ }
73
+ iodine_pubsub_task_s task = {
74
+ .eng = iodine_engine(eng), .ch = channel, .pattern = match};
75
+ IodineCaller.enterGVL(iodine_pubsub_GIL_subscribe, &task);
76
+ }
77
+
78
+ /* calls an engine's `unsubscribe` callback within the GVL */
79
+ static void *iodine_pubsub_GIL_unsubscribe(void *tsk_) {
80
+ iodine_pubsub_task_s *task = tsk_;
81
+ VALUE args[2];
82
+ args[0] = rb_str_new(task->ch.data, task->ch.len);
83
+ args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis
84
+ IodineCaller.call2(task->eng->handler, unsubscribe_id, 2, args);
85
+ return NULL;
86
+ }
87
+
88
+ /** Must unsubscribe channel. Failures are ignored. */
89
+ static void iodine_pubsub_on_unsubscribe(const fio_pubsub_engine_s *eng,
90
+ fio_str_info_s channel,
91
+ fio_match_fn match) {
92
+ if (iodine_engine(eng)->handler == Qnil) {
93
+ return;
94
+ }
95
+ iodine_pubsub_task_s task = {
96
+ .eng = iodine_engine(eng), .ch = channel, .pattern = match};
97
+ IodineCaller.enterGVL(iodine_pubsub_GIL_unsubscribe, &task);
98
+ }
99
+
100
+ /* calls an engine's `unsubscribe` callback within the GVL */
101
+ static void *iodine_pubsub_GIL_publish(void *tsk_) {
102
+ iodine_pubsub_task_s *task = tsk_;
103
+ VALUE args[2];
104
+ args[0] = rb_str_new(task->ch.data, task->ch.len);
105
+ args[1] = rb_str_new(task->msg.data, task->msg.len);
106
+ IodineCaller.call2(task->eng->handler, publish_id, 2, args);
107
+ return NULL;
108
+ }
109
+
110
+ /** Should return 0 on success and -1 on failure. */
111
+ static void iodine_pubsub_on_publish(const fio_pubsub_engine_s *eng,
112
+ fio_str_info_s channel, fio_str_info_s msg,
113
+ uint8_t is_json) {
114
+ if (iodine_engine(eng)->handler == Qnil) {
115
+ return;
116
+ }
117
+ iodine_pubsub_task_s task = {
118
+ .eng = iodine_engine(eng), .ch = channel, .msg = msg};
119
+ IodineCaller.enterGVL(iodine_pubsub_GIL_publish, &task);
120
+ (void)is_json;
121
+ }
122
+
123
+ /* *****************************************************************************
124
+ Ruby methods
125
+ ***************************************************************************** */
126
+
127
+ /**
128
+ OVERRIDE this callback - it will be called by {Iodine} whenever the process
129
+ CLUSTER (not just this process) subscribes to a stream / channel.
130
+ */
131
+ static VALUE iodine_pubsub_subscribe(VALUE self, VALUE to, VALUE match) {
132
+ return Qnil;
133
+ (void)self;
134
+ (void)to;
135
+ (void)match;
136
+ }
137
+
138
+ /**
139
+ OVERRIDE this callback - it will be called by {Iodine} whenever the whole
140
+ process CLUSTER (not just this process) unsubscribes from a stream / channel.
141
+ */
142
+ static VALUE iodine_pubsub_unsubscribe(VALUE self, VALUE to, VALUE match) {
143
+ return Qnil;
144
+ (void)self;
145
+ (void)to;
146
+ (void)match;
147
+ }
148
+
149
+ /**
150
+ OVERRIDE this callback - it will be called by {Iodine} whenever the
151
+ {Iodine.publish} (or {Iodine::Connection#publish}) is called for this engine.
152
+
153
+ If this {Engine} is set as the default {Engine}, then any call to
154
+ {Iodine.publish} (or {Iodine::Connection#publish} will invoke this callback
155
+ (unless another {Engine} was specified).
156
+
157
+ NOTE: this callback is called per process event (not per cluster event) and the
158
+ {Engine} is responsible for message propagation.
159
+ */
160
+ static VALUE iodine_pubsub_publish(VALUE self, VALUE to, VALUE message) {
161
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
162
+ if (!e || e->engine == &e->do_not_touch) {
163
+ /* this is a Ruby engine, nothing to do. */
164
+ return Qnil;
165
+ }
166
+ fio_publish(.engine = e->engine, .channel = IODINE_RSTRINFO(to),
167
+ .message = IODINE_RSTRINFO(message));
168
+ return self;
169
+ }
170
+
171
+ /* *****************************************************************************
172
+ Ruby <=> C Data Type
173
+ ***************************************************************************** */
174
+
175
+ /* a callback for the GC (marking active objects) */
176
+ static void iodine_pubsub_data_mark(void *c_) {
177
+ iodine_pubsub_s *c = c_;
178
+ if (c->handler != Qnil) {
179
+ rb_gc_mark(c->handler);
180
+ }
181
+ }
182
+ /* a callback for the GC (marking active objects) */
183
+ static void iodine_pubsub_data_free(void *c_) {
184
+ FIO_LOG_DEBUG("iodine destroying engine");
185
+ iodine_pubsub_s *data = c_;
186
+ fio_pubsub_detach(data->engine);
187
+ IodineStore.remove(data->handler); /* redundant except during exit */
188
+ if (data->dealloc) {
189
+ data->dealloc(data->engine);
190
+ }
191
+ free(data);
192
+ }
193
+
194
+ static size_t iodine_pubsub_data_size(const void *c_) {
195
+ return sizeof(iodine_pubsub_s);
196
+ (void)c_;
197
+ }
198
+
199
+ const rb_data_type_t iodine_pubsub_data_type = {
200
+ .wrap_struct_name = "IodinePubSubData",
201
+ .function =
202
+ {
203
+ .dmark = iodine_pubsub_data_mark,
204
+ .dfree = iodine_pubsub_data_free,
205
+ .dsize = iodine_pubsub_data_size,
206
+ },
207
+ .data = NULL,
208
+ // .flags = RUBY_TYPED_FREE_IMMEDIATELY,
209
+ };
210
+
211
+ /* Iodine::PubSub::Engine.allocate */
212
+ static VALUE iodine_pubsub_data_alloc_c(VALUE self) {
213
+ iodine_pubsub_s *c = malloc(sizeof(*c));
214
+ *c = (iodine_pubsub_s){
215
+ .do_not_touch =
216
+ {
217
+ .subscribe = iodine_pubsub_on_subscribe,
218
+ .unsubscribe = iodine_pubsub_on_unsubscribe,
219
+ .publish = iodine_pubsub_on_publish,
220
+ },
221
+ .handler = Qnil,
222
+ .engine = &c->do_not_touch,
223
+ };
224
+ return TypedData_Wrap_Struct(self, &iodine_pubsub_data_type, c);
225
+ }
226
+
227
+ /* *****************************************************************************
228
+ C engines
229
+ ***************************************************************************** */
230
+
231
+ static VALUE iodine_pubsub_make_C_engine(const fio_pubsub_engine_s *e) {
232
+ VALUE engine = IodineCaller.call(EngineClass, rb_intern2("new", 3));
233
+ if (engine == Qnil) {
234
+ return Qnil;
235
+ }
236
+ iodine_pubsub_CData(engine)->engine = (fio_pubsub_engine_s *)e;
237
+ return engine;
238
+ }
239
+
240
+ /* *****************************************************************************
241
+ PubSub module methods
242
+ ***************************************************************************** */
243
+
244
+ /** Sets the default {Iodine::PubSub::Engine} for pub/sub methods. */
245
+ static VALUE iodine_pubsub_default_set(VALUE self, VALUE engine) {
246
+ if (engine == Qnil) {
247
+ engine = rb_const_get(self, rb_intern2("CLUSTER", 7));
248
+ }
249
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
250
+ if (!e) {
251
+ rb_raise(rb_eTypeError, "not a valid engine");
252
+ return Qnil;
253
+ }
254
+ if (e->handler == Qnil) {
255
+ e->handler = engine;
256
+ }
257
+ FIO_PUBSUB_DEFAULT = e->engine;
258
+ rb_ivar_set(self, rb_intern2("default_engine", 14), engine);
259
+ return engine;
260
+ }
261
+
262
+ /** Returns the default {Iodine::PubSub::Engine} for pub/sub methods. */
263
+ static VALUE iodine_pubsub_default_get(VALUE self) {
264
+ VALUE def = rb_ivar_get(self, rb_intern2("default_engine", 14));
265
+ if (def == Qnil) {
266
+ def = rb_const_get(self, rb_intern2("CLUSTER", 7));
267
+ iodine_pubsub_default_set(self, def);
268
+ }
269
+ return def;
270
+ }
271
+
272
+ /**
273
+ * Attaches an {Iodine::PubSub::Engine} to the pub/sub system (more than a
274
+ * single engine can be attached at the same time).
275
+ *
276
+ * After an engine was attached, it's callbacks will be called
277
+ * ({Iodine::PubSub::Engine#subscribe} and {Iodine::PubSub::Engine#unsubscribe})
278
+ * in response to Pub/Sub events.
279
+ */
280
+ static VALUE iodine_pubsub_attach(VALUE self, VALUE engine) {
281
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
282
+ if (!e) {
283
+ rb_raise(rb_eTypeError, "not a valid engine");
284
+ return Qnil;
285
+ }
286
+ if (e->handler == Qnil) {
287
+ e->handler = engine;
288
+ }
289
+ IodineStore.add(engine);
290
+ fio_pubsub_attach(e->engine);
291
+ return engine;
292
+ (void)self;
293
+ }
294
+
295
+ /**
296
+ * Removes an {Iodine::PubSub::Engine} from the pub/sub system.
297
+ *
298
+ * After an {Iodine::PubSub::Engine} was detached, Iodine will no longer call
299
+ * the {Iodine::PubSub::Engine}'s callbacks ({Iodine::PubSub::Engine#subscribe}
300
+ * and {Iodine::PubSub::Engine#unsubscribe})
301
+ */
302
+ static VALUE iodine_pubsub_detach(VALUE self, VALUE engine) {
303
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
304
+ if (!e) {
305
+ rb_raise(rb_eTypeError, "not a valid engine");
306
+ return Qnil;
307
+ }
308
+ if (e->handler == Qnil) {
309
+ e->handler = engine;
310
+ }
311
+ IodineStore.remove(engine);
312
+ fio_pubsub_detach(e->engine);
313
+ return engine;
314
+ (void)self;
315
+ }
316
+
317
+ /**
318
+ * Forces {Iodine} to call the {Iodine::PubSub::Engine#subscribe} callback for
319
+ * all existing subscriptions (i.e., when reconnecting to a Pub/Sub backend such
320
+ * as Redis).
321
+ */
322
+ static VALUE iodine_pubsub_reset(VALUE self, VALUE engine) {
323
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
324
+ if (!e) {
325
+ rb_raise(rb_eTypeError, "not a valid engine");
326
+ return Qnil;
327
+ }
328
+ if (e->handler == Qnil) {
329
+ e->handler = engine;
330
+ }
331
+ fio_pubsub_reattach(e->engine);
332
+ return engine;
333
+ (void)self;
334
+ }
335
+
336
+ /* *****************************************************************************
337
+ Redis Engine
338
+ ***************************************************************************** */
339
+
340
+ /**
341
+ Initializes a new {Iodine::PubSub::Redis} engine.
342
+
343
+ Iodine::PubSub::Redis.new(url, opt = {})
344
+
345
+ use:
346
+
347
+ REDIS_URL = "redis://localhost:6379/"
348
+ Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
349
+
350
+ To use Redis authentication, add the password to the URL. i.e.:
351
+
352
+ REDIS_URL = "redis://redis:password@localhost:6379/"
353
+ Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
354
+
355
+ The options hash accepts:
356
+
357
+ :ping:: the PING interval up to 255 seconds. Default: 0 (~5 minutes).
358
+ */
359
+ static VALUE iodine_pubsub_redis_new(int argc, VALUE *argv, VALUE self) {
360
+ if (!argc) {
361
+ rb_raise(rb_eArgError, "Iodine::PubSub::Redis.new(address, opt={}) "
362
+ "requires at least 1 argument.");
363
+ }
364
+ VALUE url = argv[0];
365
+ Check_Type(url, T_STRING);
366
+ if (RSTRING_LEN(url) > 4096) {
367
+ rb_raise(rb_eArgError, "Redis URL too long.");
368
+ }
369
+ uint8_t ping = 0;
370
+
371
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
372
+ if (!e) {
373
+ rb_raise(rb_eTypeError, "not a valid engine");
374
+ return Qnil;
375
+ }
376
+
377
+ /* extract options */
378
+ if (argc == 2) {
379
+ Check_Type(argv[1], T_HASH);
380
+ VALUE tmp = rb_hash_aref(argv[1], rb_id2sym(rb_intern2("ping", 4)));
381
+ if (tmp != Qnil) {
382
+ Check_Type(tmp, T_FIXNUM);
383
+ if (NUM2SIZET(tmp) > 255) {
384
+ rb_raise(rb_eArgError,
385
+ ":ping must be a non-negative integer under 255 seconds.");
386
+ }
387
+ ping = (uint8_t)NUM2SIZET(tmp);
388
+ }
389
+ }
390
+
391
+ /* parse URL assume redis://redis:password@localhost:6379 */
392
+ fio_url_s info = fio_url_parse(RSTRING_PTR(url), RSTRING_LEN(url));
393
+
394
+ FIO_LOG_INFO("Initializing Redis engine for address: %.*s",
395
+ (int)RSTRING_LEN(url), RSTRING_PTR(url));
396
+ /* create engine */
397
+ e->engine = redis_engine_create(.address = info.host, .port = info.port,
398
+ .auth = info.password, .ping_interval = ping);
399
+ if (!e->engine) {
400
+ e->engine = &e->do_not_touch;
401
+ } else {
402
+ e->dealloc = redis_engine_destroy;
403
+ }
404
+
405
+ if (e->engine == &e->do_not_touch) {
406
+ rb_raise(rb_eArgError,
407
+ "Error initializing the Redis engine - malformed URL?");
408
+ }
409
+ return self;
410
+ (void)self;
411
+ (void)argc;
412
+ (void)argv;
413
+ }
414
+
415
+ struct redis_callback_data {
416
+ FIOBJ response;
417
+ VALUE block;
418
+ };
419
+
420
+ /** A callback for Redis commands. */
421
+ static void *iodine_pubsub_redis_callback_in_gil(void *data_) {
422
+ struct redis_callback_data *d = data_;
423
+ VALUE rb = Qnil;
424
+ if (!FIOBJ_IS_NULL(d->response)) {
425
+ rb = fiobj2rb_deep(d->response, 0);
426
+ }
427
+ IodineCaller.call2(d->block, call_id, 1, &rb);
428
+ IodineStore.remove(rb);
429
+ return NULL;
430
+ }
431
+
432
+ /** A callback for Redis commands. */
433
+ static void iodine_pubsub_redis_callback(fio_pubsub_engine_s *e, FIOBJ response,
434
+ void *udata) {
435
+ struct redis_callback_data d = {.response = response, .block = (VALUE)udata};
436
+ if (d.block == Qnil) {
437
+ return;
438
+ }
439
+ IodineCaller.enterGVL(iodine_pubsub_redis_callback_in_gil, &d);
440
+ IodineStore.remove(d.block);
441
+ (void)e;
442
+ }
443
+
444
+ // clang-format off
445
+ /**
446
+ Sends a Redis command. Accepts an optional block that will recieve the response.
447
+
448
+ i.e.:
449
+
450
+ REDIS_URL = "redis://redis:password@localhost:6379/"
451
+ redis = Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
452
+ Iodine::PubSub.default = redis
453
+ redis.cmd("KEYS", "*") {|result| p result
454
+ }
455
+
456
+
457
+ */
458
+ static VALUE iodine_pubsub_redis_cmd(int argc, VALUE *argv, VALUE self) {
459
+ // clang-format on
460
+ if (argc <= 0) {
461
+ rb_raise(rb_eArgError, "Iodine::PubSub::Redis#cmd(command, ...) is missing "
462
+ "the required command argument.");
463
+ }
464
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
465
+ if (!e || !e->engine || e->engine == &e->do_not_touch) {
466
+ rb_raise(rb_eTypeError,
467
+ "Iodine::PubSub::Redis internal error - obsolete object?");
468
+ }
469
+ VALUE block = Qnil;
470
+ if (rb_block_given_p()) {
471
+ block = IodineStore.add(rb_block_proc());
472
+ }
473
+ FIOBJ data = fiobj_ary_new2((size_t)argc);
474
+ for (int i = 0; i < argc; ++i) {
475
+ switch (TYPE(argv[i])) {
476
+ case T_SYMBOL:
477
+ argv[i] = rb_sym2str(argv[i]);
478
+ /* overflow */
479
+ case T_STRING:
480
+ fiobj_ary_push(data,
481
+ fiobj_str_new(RSTRING_PTR(argv[i]), RSTRING_LEN(argv[i])));
482
+ break;
483
+ case T_FIXNUM:
484
+ fiobj_ary_push(data, fiobj_num_new(NUM2SSIZET(argv[i])));
485
+ break;
486
+ case T_FLOAT:
487
+ fiobj_ary_push(data, fiobj_float_new(rb_float_value(argv[i])));
488
+ break;
489
+ case T_NIL:
490
+ fiobj_ary_push(data, fiobj_null());
491
+ break;
492
+ case T_TRUE:
493
+ fiobj_ary_push(data, fiobj_true());
494
+ break;
495
+ case T_FALSE:
496
+ fiobj_ary_push(data, fiobj_false());
497
+ break;
498
+ default:
499
+ goto wrong_type;
500
+ }
501
+ }
502
+ if (redis_engine_send(e->engine, data, iodine_pubsub_redis_callback,
503
+ (void *)block)) {
504
+ iodine_pubsub_redis_callback(e->engine, fiobj_null(), (void *)block);
505
+ }
506
+ fiobj_free(data);
507
+ return self;
508
+
509
+ wrong_type:
510
+ fiobj_free(data);
511
+ rb_raise(rb_eArgError,
512
+ "only String, Number (with limits), Symbol, true, false and nil "
513
+ "arguments can be used.");
514
+ }
515
+
516
+ /* *****************************************************************************
517
+ Module initialization
518
+ ***************************************************************************** */
519
+
520
+ /** Initializes the Connection Ruby class. */
521
+ void iodine_pubsub_init(void) {
522
+ subscribe_id = rb_intern2("subscribe", 9);
523
+ unsubscribe_id = rb_intern2("unsubscribe", 11);
524
+ publish_id = rb_intern2("publish", 7);
525
+ default_id = rb_intern2("default_engine", 14);
526
+ redis_id = rb_intern2("redis", 5);
527
+ call_id = rb_intern2("call", 4);
528
+
529
+ /* Define the PubSub module and it's methods */
530
+
531
+ VALUE PubSubModule = rb_define_module_under(IodineModule, "PubSub");
532
+ rb_define_module_function(PubSubModule, "default=", iodine_pubsub_default_set,
533
+ 1);
534
+ rb_define_module_function(PubSubModule, "default", iodine_pubsub_default_get,
535
+ 0);
536
+ rb_define_module_function(PubSubModule, "attach", iodine_pubsub_attach, 1);
537
+ rb_define_module_function(PubSubModule, "detach", iodine_pubsub_detach, 1);
538
+ rb_define_module_function(PubSubModule, "reset", iodine_pubsub_reset, 1);
539
+
540
+ /* Define the Engine class and it's methods */
541
+
542
+ /**
543
+ The {Iodine::PubSub::Engine} class is the parent for all engines to inherit
544
+ from.
545
+
546
+ Engines should inherit this class and override the `subscribe`, `unsubscribe`
547
+ and `publish` callbacks (which shall be called by {Iodine}).
548
+
549
+ After creation, Engines should attach themselves to Iodine using
550
+ {Iodine::PubSub.attach} or their callbacks will never get called.
551
+
552
+ Engines can also set themselves to be the default engine using
553
+ {Iodine::PubSub.default=}.
554
+ */
555
+ EngineClass = rb_define_class_under(PubSubModule, "Engine", rb_cObject);
556
+ rb_define_alloc_func(EngineClass, iodine_pubsub_data_alloc_c);
557
+ rb_define_method(EngineClass, "subscribe", iodine_pubsub_subscribe, 2);
558
+ rb_define_method(EngineClass, "unsubscribe", iodine_pubsub_unsubscribe, 2);
559
+ rb_define_method(EngineClass, "publish", iodine_pubsub_publish, 2);
560
+
561
+ /* Define the CLUSTER and PROCESS engines */
562
+
563
+ /* CLUSTER publishes data to all the subscribers in the process cluster. */
564
+ rb_define_const(PubSubModule, "CLUSTER",
565
+ iodine_pubsub_make_C_engine(FIO_PUBSUB_CLUSTER));
566
+ /* PROCESS publishes data to all the subscribers in a single process. */
567
+ rb_define_const(PubSubModule, "PROCESS",
568
+ iodine_pubsub_make_C_engine(FIO_PUBSUB_PROCESS));
569
+ /* SIBLINGS publishes data to all the subscribers in the *other* processes
570
+ * process. */
571
+ rb_define_const(PubSubModule, "SIBLINGS",
572
+ iodine_pubsub_make_C_engine(FIO_PUBSUB_SIBLINGS));
573
+ /* PUBLISH2ROOT publishes data only to the root / master process. */
574
+ rb_define_const(PubSubModule, "PUBLISH2ROOT",
575
+ iodine_pubsub_make_C_engine(FIO_PUBSUB_ROOT));
576
+
577
+ VALUE RedisClass = rb_define_class_under(PubSubModule, "Redis", EngineClass);
578
+ rb_define_method(RedisClass, "initialize", iodine_pubsub_redis_new, -1);
579
+ rb_define_method(RedisClass, "cmd", iodine_pubsub_redis_cmd, -1);
580
+ }
@@ -0,0 +1,26 @@
1
+ #ifndef H_IODINE_PUBSUB_H
2
+ #define H_IODINE_PUBSUB_H
3
+
4
+ #include "iodine.h"
5
+
6
+ #include "fio.h"
7
+
8
+ /** Initializes the PubSub::Engine Ruby class. */
9
+ void iodine_pubsub_init(void);
10
+
11
+ extern const rb_data_type_t iodine_pubsub_data_type;
12
+
13
+ typedef struct {
14
+ fio_pubsub_engine_s do_not_touch;
15
+ VALUE handler;
16
+ fio_pubsub_engine_s *engine;
17
+ void (*dealloc)(fio_pubsub_engine_s *engine);
18
+ } iodine_pubsub_s;
19
+
20
+ static inline iodine_pubsub_s *iodine_pubsub_CData(VALUE obj) {
21
+ iodine_pubsub_s *c = NULL;
22
+ TypedData_Get_Struct(obj, iodine_pubsub_s, &iodine_pubsub_data_type, c);
23
+ return c;
24
+ }
25
+
26
+ #endif