iodine 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/LIMITS.md +25 -0
  4. data/README.md +39 -80
  5. data/SPEC-Websocket-Draft.md +129 -4
  6. data/bin/echo +2 -2
  7. data/bin/http-hello +1 -0
  8. data/bin/updated api +113 -0
  9. data/bin/ws-echo +0 -1
  10. data/examples/broadcast.ru +56 -0
  11. data/examples/echo.ru +57 -0
  12. data/examples/hello.ru +30 -0
  13. data/examples/redis.ru +69 -0
  14. data/examples/shootout.ru +53 -0
  15. data/exe/iodine +2 -80
  16. data/ext/iodine/defer.c +11 -5
  17. data/ext/iodine/empty.h +26 -0
  18. data/ext/iodine/evio.h +1 -1
  19. data/ext/iodine/facil.c +103 -61
  20. data/ext/iodine/facil.h +20 -12
  21. data/ext/iodine/fio_dict.c +446 -0
  22. data/ext/iodine/fio_dict.h +90 -0
  23. data/ext/iodine/fio_hash_table.h +370 -0
  24. data/ext/iodine/fio_list.h +30 -3
  25. data/ext/iodine/http.c +169 -37
  26. data/ext/iodine/http.h +33 -10
  27. data/ext/iodine/http1.c +78 -42
  28. data/ext/iodine/http_request.c +6 -0
  29. data/ext/iodine/http_request.h +3 -0
  30. data/ext/iodine/http_response.c +43 -11
  31. data/ext/iodine/iodine.c +380 -0
  32. data/ext/iodine/iodine.h +62 -0
  33. data/ext/iodine/iodine_helpers.c +235 -0
  34. data/ext/iodine/iodine_helpers.h +13 -0
  35. data/ext/iodine/iodine_http.c +409 -241
  36. data/ext/iodine/iodine_http.h +7 -14
  37. data/ext/iodine/iodine_protocol.c +626 -0
  38. data/ext/iodine/iodine_protocol.h +13 -0
  39. data/ext/iodine/iodine_pubsub.c +646 -0
  40. data/ext/iodine/iodine_pubsub.h +27 -0
  41. data/ext/iodine/iodine_websockets.c +796 -0
  42. data/ext/iodine/iodine_websockets.h +19 -0
  43. data/ext/iodine/pubsub.c +544 -0
  44. data/ext/iodine/pubsub.h +215 -0
  45. data/ext/iodine/random.c +4 -4
  46. data/ext/iodine/rb-call.c +1 -5
  47. data/ext/iodine/rb-defer.c +3 -20
  48. data/ext/iodine/rb-rack-io.c +22 -22
  49. data/ext/iodine/rb-rack-io.h +3 -4
  50. data/ext/iodine/rb-registry.c +111 -118
  51. data/ext/iodine/redis_connection.c +277 -0
  52. data/ext/iodine/redis_connection.h +77 -0
  53. data/ext/iodine/redis_engine.c +398 -0
  54. data/ext/iodine/redis_engine.h +68 -0
  55. data/ext/iodine/resp.c +842 -0
  56. data/ext/iodine/resp.h +253 -0
  57. data/ext/iodine/sock.c +26 -12
  58. data/ext/iodine/sock.h +14 -3
  59. data/ext/iodine/spnlock.inc +19 -2
  60. data/ext/iodine/websockets.c +299 -11
  61. data/ext/iodine/websockets.h +159 -6
  62. data/lib/iodine.rb +104 -1
  63. data/lib/iodine/cli.rb +106 -0
  64. data/lib/iodine/monkeypatch.rb +40 -0
  65. data/lib/iodine/pubsub.rb +70 -0
  66. data/lib/iodine/version.rb +1 -1
  67. data/lib/iodine/websocket.rb +12 -0
  68. data/lib/rack/handler/iodine.rb +33 -7
  69. metadata +35 -7
  70. data/ext/iodine/iodine_core.c +0 -760
  71. data/ext/iodine/iodine_core.h +0 -79
  72. data/ext/iodine/iodine_websocket.c +0 -551
  73. data/ext/iodine/iodine_websocket.h +0 -22
  74. data/lib/iodine/http.rb +0 -4
@@ -0,0 +1,13 @@
1
+ #ifndef H_IODINE_PROTOCOL_H
2
+ #define H_IODINE_PROTOCOL_H
3
+ /*
4
+ Copyright: Boaz segev, 2016-2017
5
+ License: MIT
6
+
7
+ Feel free to copy, use and enjoy according to the license provided.
8
+ */
9
+ #include "iodine.h"
10
+
11
+ void Iodine_init_protocol(void);
12
+
13
+ #endif
@@ -0,0 +1,646 @@
1
+ /*
2
+ Copyright: Boaz segev, 2016-2017
3
+ License: MIT
4
+
5
+ Feel free to copy, use and enjoy according to the license provided.
6
+ */
7
+ #include "iodine_pubsub.h"
8
+ #include "rb-call.h"
9
+
10
+ #include "pubsub.h"
11
+ #include "redis_engine.h"
12
+
13
+ VALUE IodineEngine;
14
+
15
+ static VALUE IodinePubSub;
16
+ static ID engine_varid;
17
+ static ID engine_subid;
18
+ static ID engine_pubid;
19
+ static ID engine_unsubid;
20
+ static ID default_pubsubid;
21
+
22
+ static VALUE channel_var_id;
23
+ static VALUE pattern_var_id;
24
+ static VALUE message_var_id;
25
+
26
+ /* *****************************************************************************
27
+ Mock Functions
28
+ ***************************************************************************** */
29
+
30
+ /**
31
+ Override this method to handle (un)subscription requests.
32
+
33
+ This function will be called by Iodine during pub/sub (un)subscription. Don't
34
+ call this function from your own code / application.
35
+
36
+ The function should return `true` on success and `nil` or `false` on failure.
37
+ */
38
+ static VALUE engine_sub_placeholder(VALUE self, VALUE channel,
39
+ VALUE use_pattern) {
40
+ return Qnil;
41
+ (void)self;
42
+ (void)channel;
43
+ (void)use_pattern;
44
+ }
45
+
46
+ /**
47
+ Override this method to handle message publishing to the underlying engine (i.e.
48
+ from Ruby to Redis or from Ruby to MongoDB).
49
+
50
+ This function will be called by Iodine during pub/sub publication. Don't
51
+ call this function from your own code / application.
52
+
53
+ The function should return `true` on success and `nil` or `false` on failure.
54
+ */
55
+ static VALUE engine_pub_placeholder(VALUE self, VALUE channel, VALUE msg,
56
+ VALUE use_pattern) {
57
+ return Qnil;
58
+ (void)self;
59
+ (void)msg;
60
+ (void)channel;
61
+ (void)use_pattern;
62
+ }
63
+
64
+ /* *****************************************************************************
65
+ Ruby API
66
+ ***************************************************************************** */
67
+
68
+ /** @!visibility public
69
+ Called by the engine to distribute a `message` to a `channel`. Supports
70
+ `pattern` channel matching as well.
71
+
72
+ i.e.
73
+
74
+ # Regular message distribution
75
+ self.distribute "My Channel", "Hello!"
76
+ # Pattern message distribution
77
+ self.distribute "My Ch*", "Hello!", true
78
+
79
+ Returns `self`, always.
80
+
81
+ This is the ONLY method inherited from {Iodine::PubSub::Engine} that
82
+ should be called from within your code (by the engine itself).
83
+
84
+ **Notice:**
85
+
86
+ Message distribution requires both the {Iodine::PubSub::Engine} instance and the
87
+ channel to be the same.
88
+
89
+ If a client subscribed to "channel 1" on engine A, they will NOT receive
90
+ messages from "channel 1" on engine B.
91
+ */
92
+ static VALUE engine_distribute(int argc, VALUE *argv, VALUE self) {
93
+ if (argc < 2 || argc > 3)
94
+ rb_raise(rb_eArgError,
95
+ "wrong number of arguments (given %d, expected 2..3).", argc);
96
+ VALUE channel = argv[0];
97
+ VALUE msg = argv[1];
98
+ VALUE pattern = argc >= 3 ? argv[2] : Qnil;
99
+ Check_Type(channel, T_STRING);
100
+ Check_Type(msg, T_STRING);
101
+
102
+ iodine_engine_s *engine;
103
+ Data_Get_Struct(self, iodine_engine_s, engine);
104
+
105
+ pubsub_engine_distribute(.engine = engine->p,
106
+ .channel.name = RSTRING_PTR(channel),
107
+ .channel.len = RSTRING_LEN(channel),
108
+ .msg.data = RSTRING_PTR(msg),
109
+ .msg.len = RSTRING_LEN(msg),
110
+ .use_pattern =
111
+ (pattern != Qnil && pattern != Qfalse));
112
+ return self;
113
+ }
114
+
115
+ pubsub_engine_s *iodine_engine_ruby2facil(VALUE ruby_engine) {
116
+ if (ruby_engine == Qnil || ruby_engine == Qfalse)
117
+ return NULL;
118
+ iodine_engine_s *engine;
119
+ Data_Get_Struct(ruby_engine, iodine_engine_s, engine);
120
+ if (engine)
121
+ return engine->p;
122
+ return NULL;
123
+ }
124
+
125
+ /* *****************************************************************************
126
+ C => Ruby Bridge
127
+ ***************************************************************************** */
128
+
129
+ struct engine_gvl_args_s {
130
+ const pubsub_engine_s *eng;
131
+ const char *ch;
132
+ size_t ch_len;
133
+ const char *msg;
134
+ size_t msg_len;
135
+ uint8_t use_pattern;
136
+ };
137
+
138
+ static void *engine_subscribe_inGVL(void *a_) {
139
+ struct engine_gvl_args_s *args = a_;
140
+ VALUE data[2];
141
+ data[0] = rb_str_new(args->ch, args->ch_len);
142
+ data[1] = args->use_pattern ? Qtrue : Qnil;
143
+ VALUE eng = ((iodine_engine_s *)args->eng)->handler;
144
+ eng = RubyCaller.call2(eng, engine_subid, 2, data);
145
+ return ((eng == Qfalse || eng == Qnil) ? (void *)-1 : (void *)0);
146
+ }
147
+
148
+ /* Should return 0 on success and -1 on failure. */
149
+ static int engine_subscribe(const pubsub_engine_s *eng, const char *ch,
150
+ size_t ch_len, uint8_t use_pattern) {
151
+ struct engine_gvl_args_s args = {
152
+ .eng = eng, .ch = ch, .ch_len = ch_len, .use_pattern = use_pattern,
153
+ };
154
+ return RubyCaller.call_c(engine_subscribe_inGVL, &args) ? 0 : -1;
155
+ }
156
+
157
+ static void *engine_unsubscribe_inGVL(void *a_) {
158
+ struct engine_gvl_args_s *args = a_;
159
+ VALUE data[2];
160
+ data[0] = rb_str_new(args->ch, args->ch_len);
161
+ data[1] = args->use_pattern ? Qtrue : Qnil;
162
+ VALUE eng = ((iodine_engine_s *)args->eng)->handler;
163
+ RubyCaller.call2(eng, engine_unsubid, 2, data);
164
+ return NULL;
165
+ }
166
+
167
+ /* Return value is ignored - nothing should be returned. */
168
+ static void engine_unsubscribe(const pubsub_engine_s *eng, const char *ch,
169
+ size_t ch_len, uint8_t use_pattern) {
170
+ struct engine_gvl_args_s args = {
171
+ .eng = eng, .ch = ch, .ch_len = ch_len, .use_pattern = use_pattern,
172
+ };
173
+ RubyCaller.call_c(engine_unsubscribe_inGVL, &args);
174
+ }
175
+
176
+ static void *engine_publish_inGVL(void *a_) {
177
+ struct engine_gvl_args_s *args = a_;
178
+ VALUE data[3];
179
+ data[0] = rb_str_new(args->ch, args->ch_len);
180
+ data[1] = rb_str_new(args->msg, args->msg_len);
181
+ data[2] = args->use_pattern ? Qtrue : Qnil;
182
+ VALUE eng = ((iodine_engine_s *)args->eng)->handler;
183
+ eng = RubyCaller.call2(eng, engine_pubid, 3, data);
184
+ return ((eng == Qfalse || eng == Qnil) ? (void *)-1 : 0);
185
+ }
186
+
187
+ /* Should return 0 on success and -1 on failure. */
188
+ static int engine_publish(const pubsub_engine_s *eng, const char *ch,
189
+ size_t ch_len, const char *msg, size_t msg_len,
190
+ uint8_t use_pattern) {
191
+ struct engine_gvl_args_s args = {
192
+ .eng = eng,
193
+ .ch = ch,
194
+ .ch_len = ch_len,
195
+ .msg = msg,
196
+ .msg_len = msg_len,
197
+ .use_pattern = use_pattern,
198
+ };
199
+ return RubyCaller.call_c(engine_publish_inGVL, &args) ? 0 : -1;
200
+ }
201
+
202
+ /* *****************************************************************************
203
+ C <=> Ruby Data allocation
204
+ ***************************************************************************** */
205
+
206
+ /* a callback for the GC (marking active objects) */
207
+ static void engine_mark(void *eng_) {
208
+ iodine_engine_s *eng = eng_;
209
+ rb_gc_mark(eng->handler);
210
+ }
211
+ /* a callback for the GC (marking active objects) */
212
+ static void engine_free(void *eng_) {
213
+ iodine_engine_s *eng = eng_;
214
+ if (eng->dealloc)
215
+ eng->dealloc(eng->p);
216
+ free(eng);
217
+ }
218
+
219
+ /* GMP::Integer.allocate */
220
+ static VALUE engine_alloc_c(VALUE self) {
221
+ iodine_engine_s *eng = malloc(sizeof(*eng));
222
+ if (TYPE(self) == T_CLASS)
223
+ *eng = (iodine_engine_s){
224
+ .handler = self,
225
+ .engine =
226
+ {
227
+ .subscribe = engine_subscribe,
228
+ .unsubscribe = engine_unsubscribe,
229
+ .publish = engine_publish,
230
+ },
231
+ .p = &eng->engine,
232
+ };
233
+
234
+ return Data_Wrap_Struct(self, engine_mark, engine_free, eng);
235
+ }
236
+
237
+ static VALUE engine_initialize(VALUE self) {
238
+ iodine_engine_s *engine;
239
+ Data_Get_Struct(self, iodine_engine_s, engine);
240
+ engine->handler = self;
241
+ return self;
242
+ }
243
+
244
+ /* *****************************************************************************
245
+ Redis
246
+ ***************************************************************************** */
247
+
248
+ struct redis_callback_data {
249
+ resp_object_s *msg;
250
+ VALUE block;
251
+ };
252
+
253
+ /*
254
+ populate
255
+ */
256
+ int populate_redis_callback_reply(resp_parser_pt p, resp_object_s *o,
257
+ void *rep) {
258
+ switch (o->type) {
259
+ case RESP_ARRAY:
260
+ case RESP_PUBSUB:
261
+ break;
262
+ case RESP_NULL:
263
+ rb_ary_push((VALUE)rep, Qnil);
264
+ break;
265
+ case RESP_NUMBER:
266
+ rb_ary_push((VALUE)rep, LONG2FIX(resp_obj2num(o)->number));
267
+ break;
268
+ case RESP_ERR:
269
+ case RESP_STRING:
270
+ rb_ary_push((VALUE)rep, rb_str_new((char *)resp_obj2str(o)->string,
271
+ resp_obj2str(o)->len));
272
+ break;
273
+ case RESP_OK:
274
+ rb_ary_push((VALUE)rep, rb_str_new("OK", 2));
275
+ break;
276
+ }
277
+ return 0;
278
+ (void)p;
279
+ }
280
+ /*
281
+ Perform a Redis message callback in the GVL
282
+ */
283
+ static void *perform_redis_callback_inGVL(void *data) {
284
+ struct redis_callback_data *a = data;
285
+ VALUE reply = rb_ary_new();
286
+ resp_obj_each(NULL, a->msg, populate_redis_callback_reply, (void *)reply);
287
+ rb_funcallv(a->block, iodine_call_proc_id, 1, &reply);
288
+ Registry.remove(a->block);
289
+ return NULL;
290
+ }
291
+
292
+ /*
293
+ Redis message callback
294
+ */
295
+ static void redis_callback(pubsub_engine_s *e, resp_object_s *msg,
296
+ void *block) {
297
+ struct redis_callback_data d = {
298
+ .msg = msg, .block = (VALUE)block,
299
+ };
300
+ RubyCaller.call_c(perform_redis_callback_inGVL, &d);
301
+ (void)e;
302
+ }
303
+
304
+ /**
305
+ Sends commands / messages to the underlying Redis Pub connection.
306
+
307
+ The method accepts an optional callback block. i.e.:
308
+
309
+ redis.send("Echo", "Hello World!") do |reply|
310
+ p reply # => ["Hello World!"]
311
+ end
312
+
313
+ This connection is only for publishing and database commands. The Sub commands,
314
+ such as SUBSCRIBE and PSUBSCRIBE, will break the engine.
315
+ */
316
+ static VALUE redis_send(int argc, VALUE *argv, VALUE self) {
317
+ if (argc < 1)
318
+ rb_raise(rb_eArgError,
319
+ "wrong number of arguments (given %d, expected at least 1).",
320
+ argc);
321
+ resp_object_s *cmd = NULL;
322
+ Check_Type(argv[0], T_STRING);
323
+
324
+ iodine_engine_s *e;
325
+ Data_Get_Struct(self, iodine_engine_s, e);
326
+ cmd = resp_arr2obj(argc, NULL);
327
+ for (int i = 0; i < argc; i++) {
328
+ switch (TYPE(argv[i])) {
329
+ case T_SYMBOL:
330
+ argv[i] = rb_sym2str(argv[i]);
331
+ /* Fallthrough */
332
+ case T_STRING:
333
+ resp_obj2arr(cmd)->array[i] =
334
+ resp_str2obj(RSTRING_PTR(argv[i]), RSTRING_LEN(argv[i]));
335
+ break;
336
+ case T_FIXNUM:
337
+ resp_obj2arr(cmd)->array[i] = resp_num2obj(FIX2LONG(argv[i]));
338
+ break;
339
+ default:
340
+ goto error;
341
+ break;
342
+ }
343
+ }
344
+
345
+ if (rb_block_given_p()) {
346
+ VALUE block = rb_block_proc();
347
+ Registry.add(block);
348
+ redis_engine_send(e->p, cmd, redis_callback, (void *)block);
349
+ return block;
350
+ } else {
351
+ redis_engine_send(e->p, cmd, NULL, NULL);
352
+ }
353
+ return Qtrue;
354
+ error:
355
+ if (cmd)
356
+ resp_free_object(cmd);
357
+ rb_raise(rb_eArgError, "Arguments can only include Strings, Symbols and "
358
+ "Integers - no arrays or hashes or other objects can "
359
+ "be sent.");
360
+ return self;
361
+ }
362
+
363
+ /**
364
+ Initializes a new RedisEngine for Pub/Sub.
365
+
366
+ use:
367
+
368
+ RedisEngine.new(address, port = 6379, ping_interval = 0)
369
+
370
+ Accepts:
371
+
372
+ address:: the Redis server's address. Required.
373
+ port:: the Redis Server port. Default: 6379
374
+ ping:: the PING interval. Default: 0 (~5 minutes).
375
+ auth:: authentication password. Default: none.
376
+ */
377
+ static VALUE redis_engine_initialize(int argc, VALUE *argv, VALUE self) {
378
+ if (argc < 1 || argc > 4)
379
+ rb_raise(rb_eArgError,
380
+ "wrong number of arguments (given %d, expected 1..4).", argc);
381
+ VALUE address = argv[0];
382
+ VALUE port = argc >= 2 ? argv[1] : Qnil;
383
+ VALUE ping = argc >= 3 ? argv[2] : Qnil;
384
+ VALUE auth = argc >= 4 ? argv[3] : Qnil;
385
+ Check_Type(address, T_STRING);
386
+ if (port != Qnil) {
387
+ if (TYPE(port) == T_FIXNUM)
388
+ port = rb_fix2str(port, 10);
389
+ Check_Type(port, T_STRING);
390
+ }
391
+ if (ping != Qnil)
392
+ Check_Type(ping, T_FIXNUM);
393
+ if (auth != Qnil) {
394
+ Check_Type(auth, T_STRING);
395
+ }
396
+ size_t iping = FIX2LONG(ping);
397
+ if (iping > 255)
398
+ rb_raise(rb_eRangeError, "ping_interval too big (0..255)");
399
+
400
+ iodine_engine_s *engine;
401
+ Data_Get_Struct(self, iodine_engine_s, engine);
402
+ engine->handler = self;
403
+ engine->p =
404
+ redis_engine_create(.address = StringValueCStr(address),
405
+ .port =
406
+ (port == Qnil ? "6379" : StringValueCStr(port)),
407
+ .ping_interval = iping,
408
+ .auth = (auth == Qnil ? NULL : StringValueCStr(auth)),
409
+ .auth_len = (auth == Qnil ? 0 : RSTRING_LEN(auth)));
410
+ if (!engine->p)
411
+ rb_raise(rb_eRuntimeError, "unknown error, can't initialize RedisEngine.");
412
+ engine->dealloc = redis_engine_destroy;
413
+ return self;
414
+ }
415
+
416
+ /* *****************************************************************************
417
+ PubSub settings
418
+ ***************************************************************************** */
419
+
420
+ /**
421
+ Sets the default Pub/Sub engine to be used.
422
+
423
+ See {Iodine::PubSub} and {Iodine::PubSub::Engine} for more details.
424
+ */
425
+ static VALUE ips_set_default(VALUE self, VALUE en) {
426
+ iodine_engine_s *e;
427
+ Data_Get_Struct(en, iodine_engine_s, e);
428
+ if (!e)
429
+ rb_raise(rb_eArgError, "deafult engine must be an Iodine::PubSub::Engine.");
430
+ if (!e->p)
431
+ rb_raise(rb_eArgError, "This Iodine::PubSub::Engine is broken.");
432
+ rb_ivar_set(self, default_pubsubid, en);
433
+ PUBSUB_DEFAULT_ENGINE = e->p;
434
+ return en;
435
+ }
436
+
437
+ /**
438
+ Returns the default Pub/Sub engine (if any).
439
+
440
+ See {Iodine::PubSub} and {Iodine::PubSub::Engine} for more details.
441
+ */
442
+ static VALUE ips_get_default(VALUE self) {
443
+ return rb_ivar_get(self, default_pubsubid);
444
+ }
445
+
446
+ /* *****************************************************************************
447
+ Pub/Sub API
448
+ ***************************************************************************** */
449
+
450
+ static void iodine_on_unsubscribe(void *u1, void *u2) {
451
+ if (u1 && (VALUE)u1 != Qnil && u1 != (VALUE)Qfalse)
452
+ Registry.remove((VALUE)u1);
453
+ (void)u2;
454
+ }
455
+
456
+ static void *on_pubsub_notificationinGVL(pubsub_message_s *n) {
457
+ VALUE rbn[2];
458
+ rbn[0] = rb_str_new(n->channel.name, n->channel.len);
459
+ Registry.add(rbn[0]);
460
+ rbn[1] = rb_str_new(n->msg.data, n->msg.len);
461
+ Registry.add(rbn[1]);
462
+ RubyCaller.call2((VALUE)n->udata1, iodine_call_proc_id, 2, rbn);
463
+ Registry.remove(rbn[0]);
464
+ Registry.remove(rbn[1]);
465
+ return NULL;
466
+ }
467
+
468
+ static void on_pubsub_notificationin(pubsub_message_s *n) {
469
+ RubyCaller.call_c((void *(*)(void *))on_pubsub_notificationinGVL, n);
470
+ }
471
+
472
+ /**
473
+ Subscribes the process to a channel belonging to a specific pub/sub service
474
+ (using an {Iodine::PubSub::Engine} to connect Iodine to the service).
475
+
476
+ The function accepts a single argument (a Hash) and a required block.
477
+
478
+ Accepts a single Hash argument with the following possible options:
479
+
480
+ :engine :: If provided, the engine to use for pub/sub. Otherwise the default
481
+ engine is used.
482
+
483
+ :channel :: Required (unless :pattern). The channel to subscribe to.
484
+
485
+ :pattern :: An alternative to the required :channel, subscribes to a pattern.
486
+
487
+ */
488
+ static VALUE iodine_subscribe(VALUE self, VALUE args) {
489
+ Check_Type(args, T_HASH);
490
+ rb_need_block();
491
+
492
+ uint8_t use_pattern = 0;
493
+
494
+ VALUE rb_ch = rb_hash_aref(args, channel_var_id);
495
+ if (rb_ch == Qnil || rb_ch == Qfalse) {
496
+ use_pattern = 1;
497
+ rb_ch = rb_hash_aref(args, pattern_var_id);
498
+ if (rb_ch == Qnil || rb_ch == Qfalse)
499
+ rb_raise(rb_eArgError, "a channel is required for pub/sub methods.");
500
+ }
501
+ if (TYPE(rb_ch) == T_SYMBOL)
502
+ rb_ch = rb_sym2str(rb_ch);
503
+ Check_Type(rb_ch, T_STRING);
504
+
505
+ VALUE block = rb_block_proc();
506
+
507
+ pubsub_engine_s *engine =
508
+ iodine_engine_ruby2facil(rb_hash_aref(args, engine_varid));
509
+
510
+ uintptr_t subid = (uintptr_t)
511
+ pubsub_subscribe(.channel.name = RSTRING_PTR(rb_ch),
512
+ .channel.len = RSTRING_LEN(rb_ch), .engine = engine,
513
+ .use_pattern = use_pattern,
514
+ .on_message = (block ? on_pubsub_notificationin : NULL),
515
+ .on_unsubscribe = (block ? iodine_on_unsubscribe : NULL),
516
+ .udata1 = (void *)block);
517
+ if (!subid)
518
+ return Qnil;
519
+ return ULL2NUM(subid);
520
+ (void)self;
521
+ }
522
+
523
+ /**
524
+ Cancels the subscription matching `sub_id`.
525
+ */
526
+ static VALUE iodine_unsubscribe(VALUE self, VALUE sub_id) {
527
+ if (sub_id == Qnil || sub_id == Qfalse)
528
+ return Qnil;
529
+ Check_Type(sub_id, T_FIXNUM);
530
+ pubsub_unsubscribe((pubsub_sub_pt)NUM2LONG(sub_id));
531
+ return Qnil;
532
+ (void)self;
533
+ }
534
+
535
+ /**
536
+ Publishes a message to a channel.
537
+
538
+ Accepts a single Hash argument with the following possible options:
539
+
540
+ :engine :: If provided, the engine to use for pub/sub. Otherwise the default
541
+ engine is used.
542
+
543
+ :channel :: Required (unless :pattern). The channel to publish to.
544
+
545
+ :pattern :: An alternative to the required :channel, publishes to a pattern.
546
+ This is NOT supported by Redis and it's limited to the local process cluster.
547
+
548
+ :message :: REQUIRED. The message to be published.
549
+ :
550
+ */
551
+ static VALUE iodine_publish(VALUE self, VALUE args) {
552
+ Check_Type(args, T_HASH);
553
+ uint8_t use_pattern = 0;
554
+
555
+ VALUE rb_ch = rb_hash_aref(args, channel_var_id);
556
+ if (rb_ch == Qnil || rb_ch == Qfalse) {
557
+ use_pattern = 1;
558
+ rb_ch = rb_hash_aref(args, pattern_var_id);
559
+ if (rb_ch == Qnil || rb_ch == Qfalse)
560
+ rb_raise(rb_eArgError, "channel is required for pub/sub methods.");
561
+ }
562
+ if (TYPE(rb_ch) == T_SYMBOL)
563
+ rb_ch = rb_sym2str(rb_ch);
564
+ Check_Type(rb_ch, T_STRING);
565
+
566
+ VALUE rb_msg = rb_hash_aref(args, message_var_id);
567
+ if (rb_msg == Qnil || rb_msg == Qfalse) {
568
+ rb_raise(rb_eArgError, "message is required for the :publish method.");
569
+ }
570
+ Check_Type(rb_msg, T_STRING);
571
+
572
+ pubsub_engine_s *engine =
573
+ iodine_engine_ruby2facil(rb_hash_aref(args, engine_varid));
574
+
575
+ intptr_t ret =
576
+ pubsub_publish(.engine = engine, .channel.name = (RSTRING_PTR(rb_ch)),
577
+ .channel.len = (RSTRING_LEN(rb_ch)),
578
+ .msg.data = (RSTRING_PTR(rb_msg)),
579
+ .msg.len = (RSTRING_LEN(rb_msg)),
580
+ .use_pattern = use_pattern);
581
+ if (!ret)
582
+ return Qfalse;
583
+ return Qtrue;
584
+ (void)self;
585
+ }
586
+
587
+ /* *****************************************************************************
588
+ Initialization
589
+ ***************************************************************************** */
590
+ void Iodine_init_pubsub(void) {
591
+ engine_varid = rb_intern("engine");
592
+ engine_subid = rb_intern("subscribe");
593
+ engine_unsubid = rb_intern("unsubscribe");
594
+ engine_pubid = rb_intern("publish");
595
+ default_pubsubid = rb_intern("default_pubsub");
596
+ channel_var_id = ID2SYM(rb_intern("channel"));
597
+ pattern_var_id = ID2SYM(rb_intern("pattern"));
598
+ message_var_id = ID2SYM(rb_intern("message"));
599
+
600
+ IodinePubSub = rb_define_module_under(Iodine, "PubSub");
601
+ IodineEngine = rb_define_class_under(IodinePubSub, "Engine", rb_cObject);
602
+
603
+ rb_define_alloc_func(IodineEngine, engine_alloc_c);
604
+ rb_define_method(IodineEngine, "initialize", engine_initialize, 0);
605
+
606
+ rb_define_method(IodineEngine, "distribute", engine_distribute, -1);
607
+ rb_define_method(IodineEngine, "subscribe", engine_sub_placeholder, 2);
608
+ rb_define_method(IodineEngine, "unsubscribe", engine_sub_placeholder, 2);
609
+ rb_define_method(IodineEngine, "publish", engine_pub_placeholder, 3);
610
+
611
+ rb_define_module_function(Iodine, "default_pubsub=", ips_set_default, 1);
612
+ rb_define_module_function(Iodine, "default_pubsub", ips_get_default, 0);
613
+
614
+ rb_define_module_function(Iodine, "subscribe", iodine_subscribe, 1);
615
+ rb_define_module_function(Iodine, "unsubscribe", iodine_unsubscribe, 1);
616
+ rb_define_module_function(Iodine, "publish", iodine_publish, 1);
617
+
618
+ /* *************************
619
+ Initialize C pubsub engines
620
+ ************************** */
621
+ VALUE engine_in_c;
622
+ iodine_engine_s *engine;
623
+
624
+ engine_in_c = rb_funcallv(IodineEngine, iodine_new_func_id, 0, NULL);
625
+ Data_Get_Struct(engine_in_c, iodine_engine_s, engine);
626
+ engine->p = (pubsub_engine_s *)PUBSUB_CLUSTER_ENGINE;
627
+
628
+ /** This is the (currently) default pub/sub engine. It will distribute
629
+ * messages to all subscribers in the process cluster. */
630
+ rb_define_const(IodinePubSub, "CLUSTER", engine_in_c);
631
+ // rb_const_set(IodineEngine, rb_intern("CLUSTER"), e);
632
+
633
+ engine_in_c = rb_funcallv(IodineEngine, iodine_new_func_id, 0, NULL);
634
+ Data_Get_Struct(engine_in_c, iodine_engine_s, engine);
635
+ engine->p = (pubsub_engine_s *)PUBSUB_PROCESS_ENGINE;
636
+
637
+ /** This is a single process pub/sub engine. It will distribute messages to
638
+ * all subscribers sharing the same process. */
639
+ rb_define_const(IodinePubSub, "SINGLE_PROCESS", engine_in_c);
640
+ // rb_const_set(IodineEngine, rb_intern("SINGLE_PROCESS"), e);
641
+
642
+ engine_in_c =
643
+ rb_define_class_under(IodinePubSub, "RedisEngine", IodineEngine);
644
+ rb_define_method(engine_in_c, "initialize", redis_engine_initialize, -1);
645
+ rb_define_method(engine_in_c, "send", redis_send, -1);
646
+ }