iodine 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +63 -100
  4. data/bin/raw-rbhttp +12 -7
  5. data/examples/config.ru +8 -7
  6. data/examples/echo.ru +8 -7
  7. data/examples/info.md +41 -35
  8. data/examples/pubsub_engine.ru +12 -12
  9. data/examples/redis.ru +10 -12
  10. data/examples/shootout.ru +19 -42
  11. data/exe/iodine +116 -1
  12. data/ext/iodine/defer.c +1 -1
  13. data/ext/iodine/facil.c +12 -8
  14. data/ext/iodine/facil.h +2 -2
  15. data/ext/iodine/iodine.c +177 -343
  16. data/ext/iodine/iodine.h +18 -72
  17. data/ext/iodine/iodine_caller.c +132 -0
  18. data/ext/iodine/iodine_caller.h +21 -0
  19. data/ext/iodine/iodine_connection.c +841 -0
  20. data/ext/iodine/iodine_connection.h +55 -0
  21. data/ext/iodine/iodine_defer.c +391 -0
  22. data/ext/iodine/iodine_defer.h +7 -0
  23. data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
  24. data/ext/iodine/iodine_helpers.c +51 -5
  25. data/ext/iodine/iodine_helpers.h +2 -3
  26. data/ext/iodine/iodine_http.c +284 -141
  27. data/ext/iodine/iodine_http.h +2 -2
  28. data/ext/iodine/iodine_json.c +13 -13
  29. data/ext/iodine/iodine_json.h +1 -1
  30. data/ext/iodine/iodine_pubsub.c +573 -823
  31. data/ext/iodine/iodine_pubsub.h +15 -27
  32. data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
  33. data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
  34. data/ext/iodine/iodine_store.c +136 -0
  35. data/ext/iodine/iodine_store.h +20 -0
  36. data/ext/iodine/iodine_tcp.c +385 -0
  37. data/ext/iodine/iodine_tcp.h +9 -0
  38. data/lib/iodine.rb +73 -171
  39. data/lib/iodine/connection.rb +34 -0
  40. data/lib/iodine/pubsub.rb +5 -18
  41. data/lib/iodine/rack_utils.rb +43 -0
  42. data/lib/iodine/version.rb +1 -1
  43. data/lib/rack/handler/iodine.rb +1 -182
  44. metadata +17 -18
  45. data/ext/iodine/iodine_protocol.c +0 -689
  46. data/ext/iodine/iodine_protocol.h +0 -13
  47. data/ext/iodine/iodine_websockets.c +0 -550
  48. data/ext/iodine/iodine_websockets.h +0 -17
  49. data/ext/iodine/rb-call.c +0 -156
  50. data/ext/iodine/rb-call.h +0 -70
  51. data/ext/iodine/rb-defer.c +0 -124
  52. data/ext/iodine/rb-registry.c +0 -150
  53. data/ext/iodine/rb-registry.h +0 -34
  54. data/lib/iodine/cli.rb +0 -89
  55. data/lib/iodine/monkeypatch.rb +0 -46
  56. data/lib/iodine/protocol.rb +0 -42
  57. data/lib/iodine/websocket.rb +0 -16
@@ -1,7 +1,7 @@
1
1
  #ifndef H_IODINE_HTTP_H
2
2
  #define H_IODINE_HTTP_H
3
3
  /*
4
- Copyright: Boaz segev, 2016-2017
4
+ Copyright: Boaz segev, 2016-2018
5
5
  License: MIT
6
6
 
7
7
  Feel free to copy, use and enjoy according to the license provided.
@@ -13,6 +13,6 @@ extern VALUE IODINE_R_HIJACK;
13
13
  extern VALUE IODINE_R_HIJACK_IO;
14
14
  extern VALUE IODINE_R_HIJACK_CB;
15
15
 
16
- void Iodine_init_http(void);
16
+ void iodine_init_http(void);
17
17
 
18
18
  #endif
@@ -4,8 +4,8 @@
4
4
  #include "fio_json_parser.h"
5
5
  #include "fio_mem.h"
6
6
  #include "fiobj.h"
7
- #include "rb-fiobj2rb.h"
8
- #include "rb-registry.h"
7
+ #include "iodine_fiobj2rb.h"
8
+ #include "iodine_store.h"
9
9
 
10
10
  static VALUE max_nesting;
11
11
  static VALUE allow_nan;
@@ -37,20 +37,20 @@ static inline void iodine_json_add2parser(iodine_json_parser_s *p, VALUE o) {
37
37
  if (p->is_hash) {
38
38
  if (p->key) {
39
39
  rb_hash_aset(p->top, p->key, o);
40
- Registry.remove(p->key);
40
+ IodineStore.remove(p->key);
41
41
  p->key = (VALUE)0;
42
42
  } else {
43
43
  // if (p->symbolize) {
44
44
  // o = rb_to_symbol(o);
45
45
  // }
46
46
  p->key = o;
47
- Registry.add(o);
47
+ IodineStore.add(o);
48
48
  }
49
49
  } else {
50
50
  rb_ary_push(p->top, o);
51
51
  }
52
52
  } else {
53
- Registry.add(o);
53
+ IodineStore.add(o);
54
54
  p->top = o;
55
55
  }
56
56
  }
@@ -115,7 +115,7 @@ static void fio_json_on_end_object(json_parser_s *p) {
115
115
  if (pr->key) {
116
116
  fprintf(stderr, "WARNING: (JSON parsing) malformed JSON, "
117
117
  "ignoring dangling Hash key.\n");
118
- Registry.remove(pr->key);
118
+ IodineStore.remove(pr->key);
119
119
  pr->key = (VALUE)0;
120
120
  }
121
121
  pr->top = (VALUE)fio_ary_pop(&pr->stack);
@@ -147,8 +147,8 @@ static void fio_json_on_error(json_parser_s *p) {
147
147
  #if DEBUG
148
148
  fprintf(stderr, "ERROR: JSON on error called.\n");
149
149
  #endif
150
- Registry.remove((VALUE)fio_ary_index(&pr->stack, 0));
151
- Registry.remove(pr->key);
150
+ IodineStore.remove((VALUE)fio_ary_index(&pr->stack, 0));
151
+ IodineStore.remove(pr->key);
152
152
  fio_ary_free(&pr->stack);
153
153
  *pr = (iodine_json_parser_s){.top = 0};
154
154
  }
@@ -162,17 +162,17 @@ static inline VALUE iodine_json_convert(VALUE str, fiobj2rb_settings_s s) {
162
162
  iodine_json_parser_s p = {.top = 0, .symbolize = s.str2sym};
163
163
  size_t consumed = fio_json_parse(&p.p, RSTRING_PTR(str), RSTRING_LEN(str));
164
164
  if (!consumed || p.p.depth) {
165
- Registry.remove((VALUE)fio_ary_index(&p.stack, 0));
165
+ IodineStore.remove((VALUE)fio_ary_index(&p.stack, 0));
166
166
  p.top = FIOBJ_INVALID;
167
167
  }
168
168
  fio_ary_free(&p.stack);
169
169
  if (p.key) {
170
- Registry.remove((VALUE)p.key);
170
+ IodineStore.remove((VALUE)p.key);
171
171
  }
172
172
  if (!p.top) {
173
173
  rb_raise(rb_eEncodingError, "Malformed JSON format.");
174
174
  }
175
- Registry.remove(p.top);
175
+ IodineStore.remove(p.top);
176
176
  return p.top;
177
177
  }
178
178
 
@@ -253,7 +253,7 @@ static VALUE iodine_json_parse_bang(int argc, VALUE *argv, VALUE self) {
253
253
  (void)self;
254
254
  }
255
255
 
256
- void Iodine_init_json(void) {
256
+ void iodine_init_json(void) {
257
257
  /**
258
258
  Iodine::JSON offers a fast(er) JSON parser that is also lenient and supports
259
259
  some JSON extensions such as Hex number recognition and comments.
@@ -292,7 +292,7 @@ void Iodine_init_json(void) {
292
292
 
293
293
 
294
294
  */
295
- VALUE tmp = rb_define_module_under(Iodine, "JSON");
295
+ VALUE tmp = rb_define_module_under(IodineModule, "JSON");
296
296
  max_nesting = ID2SYM(rb_intern("max_nesting"));
297
297
  allow_nan = ID2SYM(rb_intern("allow_nan"));
298
298
  symbolize_names = ID2SYM(rb_intern("symbolize_names"));
@@ -1,6 +1,6 @@
1
1
  #ifndef H_IODINE_JSON_H
2
2
  #define H_IODINE_JSON_H
3
3
 
4
- void Iodine_init_json(void);
4
+ void iodine_init_json(void);
5
5
 
6
6
  #endif
@@ -1,946 +1,696 @@
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
1
  #include "iodine_pubsub.h"
8
- #include "rb-call.h"
9
-
2
+ #include "iodine_fiobj2rb.h"
10
3
  #include "pubsub.h"
11
- #include "rb-fiobj2rb.h"
12
4
  #include "redis_engine.h"
13
- #include "websockets.h"
14
-
15
- VALUE IodineEngine;
16
- ID iodine_engine_pubid;
17
-
18
- static VALUE IodinePubSub;
19
- static VALUE IodinePubSubSubscription;
20
- static ID engine_varid;
21
- static ID engine_subid;
22
- static ID engine_unsubid;
23
- static ID default_pubsubid;
24
-
25
- static ID to_str_shadow_id;
26
-
27
- static VALUE as_sym_id;
28
- static VALUE binary_sym_id;
29
- static VALUE handler_sym_id;
30
- static VALUE match_sym_id;
31
- static VALUE message_sym_id;
32
- static VALUE redis_sym_id;
33
- static VALUE text_sym_id;
34
- static VALUE to_sym_id;
35
- static VALUE channel_sym_id;
36
5
 
37
- /* *****************************************************************************
38
- Mock Functions
39
- ***************************************************************************** */
6
+ /*
7
+ NOTE:
40
8
 
41
- /**
42
- Override this method to handle (un)subscription requests.
9
+ This file defines Pub/Sub management and settings, not Pub/Sub usage.
43
10
 
44
- This function will be called by Iodine during pub/sub (un)subscription. Don't
45
- call this function from your own code / application.
11
+ This file doen't include the `Iodine.subscribe`, `Iodine.unsubscribe` and
12
+ `Iodine.publish` methods.
46
13
 
47
- The function should return `true` on success and `nil` or `false` on failure.
14
+ These methods are all defined in the Connection module (iodine_connection.h).
48
15
  */
49
- static VALUE engine_sub_placeholder(VALUE self, VALUE channel,
50
- VALUE use_pattern) {
51
- return Qnil;
52
- (void)self;
53
- (void)channel;
54
- (void)use_pattern;
55
- }
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;
56
27
 
57
28
  /**
58
- Override this method to handle message publishing to the underlying engine (i.e.
59
- from Ruby to Redis or from Ruby to MongoDB).
29
+ The {Iodine::PubSub::Engine} class is the parent for all engines to inherit
30
+ from.
60
31
 
61
- This function will be called by Iodine during pub/sub publication. Don't
62
- call this function from your own code / application.
32
+ Engines should inherit this class and override the `subscribe`, `unsubscribe`
33
+ and `publish` callbacks (which shall be called by {Iodine}).
63
34
 
64
- The function should return `true` on success and `nil` or `false` on failure.
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=}.
65
40
  */
66
- static VALUE engine_pub_placeholder(VALUE self, VALUE channel, VALUE msg) {
67
- { /* test for built-in C engines */
68
- iodine_engine_s *engine;
69
- Data_Get_Struct(self, iodine_engine_s, engine);
70
- if (engine->p != &engine->engine) {
71
- FIOBJ ch = fiobj_str_new(RSTRING_PTR(channel), RSTRING_LEN(channel));
72
- FIOBJ m = fiobj_str_new(RSTRING_PTR(msg), RSTRING_LEN(msg));
73
- pubsub_publish(.engine = engine->p, .channel = ch, .message = m);
74
- fiobj_free(ch);
75
- fiobj_free(msg);
76
- return Qtrue;
77
- }
78
- }
79
- return Qnil;
80
- (void)self;
81
- (void)msg;
82
- (void)channel;
83
- }
41
+ static VALUE EngineClass;
84
42
 
85
43
  /* *****************************************************************************
86
- Engine registration and resetting
44
+ Ruby <=> C Callbacks
87
45
  ***************************************************************************** */
88
46
 
89
- /**
90
- This method adds the engine to the pub/sub system, allowing it to recieve system
91
- wide notifications.
92
- */
93
- static VALUE iodine_engine_register2(VALUE self, VALUE engine) {
94
- iodine_engine_s *e;
95
- Registry.add(engine);
96
- Data_Get_Struct(engine, iodine_engine_s, e);
97
- if (e->p) {
98
- e->handler = engine;
99
- pubsub_engine_register(e->p);
100
- return Qtrue;
101
- }
102
- return Qfalse;
103
- (void)self;
104
- (void)engine;
47
+ typedef struct {
48
+ iodine_pubsub_s *eng;
49
+ FIOBJ ch;
50
+ FIOBJ msg;
51
+ uint8_t 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
+ fio_cstr_s tmp = fiobj_obj2cstr(task->ch);
61
+ args[0] = rb_str_new(tmp.data, tmp.len);
62
+ args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis
63
+ IodineCaller.call2(task->eng->handler, subscribe_id, 2, args);
64
+ return NULL;
105
65
  }
106
66
 
107
- /**
108
- This method adds the engine to the pub/sub system, allowing it to recieve system
109
- wide notifications.
110
- */
111
- static VALUE iodine_engine_register(VALUE self) {
112
- return iodine_engine_register2(self, self);
67
+ /** Must subscribe channel. Failures are ignored. */
68
+ static void iodine_pubsub_on_subscribe(const pubsub_engine_s *eng,
69
+ FIOBJ channel, uint8_t use_pattern) {
70
+ if (iodine_engine(eng)->handler == Qnil) {
71
+ return;
72
+ }
73
+ iodine_pubsub_task_s task = {
74
+ .eng = iodine_engine(eng), .ch = channel, .pattern = use_pattern};
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
+ fio_cstr_s tmp = fiobj_obj2cstr(task->ch);
83
+ args[0] = rb_str_new(tmp.data, tmp.len);
84
+ args[1] = task->pattern ? Qtrue : Qnil; // TODO: Qtrue should be :redis
85
+ IodineCaller.call2(task->eng->handler, unsubscribe_id, 2, args);
86
+ return NULL;
113
87
  }
114
88
 
115
- /**
116
- This method removes the engine from the pub/sub system.
117
- */
118
- static VALUE iodine_engine_deregister2(VALUE self, VALUE engine) {
119
- iodine_engine_s *e;
120
- Data_Get_Struct(engine, iodine_engine_s, e);
121
- if (e->p) {
122
- pubsub_engine_deregister(e->p);
123
- Registry.remove(engine);
124
- return Qtrue;
125
- }
126
- Registry.remove(engine);
127
- return Qfalse;
128
- (void)self;
129
- (void)engine;
89
+ /** Must unsubscribe channel. Failures are ignored. */
90
+ static void iodine_pubsub_on_unsubscribe(const pubsub_engine_s *eng,
91
+ FIOBJ channel, uint8_t use_pattern) {
92
+ if (iodine_engine(eng)->handler == Qnil) {
93
+ return;
94
+ }
95
+ iodine_pubsub_task_s task = {
96
+ .eng = iodine_engine(eng), .ch = channel, .pattern = use_pattern};
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
+ fio_cstr_s tmp = fiobj_obj2cstr(task->ch);
105
+ args[0] = rb_str_new(tmp.data, tmp.len);
106
+ tmp = fiobj_obj2cstr(task->msg);
107
+ args[1] = rb_str_new(tmp.data, tmp.len);
108
+ IodineCaller.call2(task->eng->handler, publish_id, 2, args);
109
+ return NULL;
130
110
  }
131
111
 
132
- /**
133
- This method removes the engine from the pub/sub system.
134
- */
135
- static VALUE iodine_engine_deregister(VALUE self) {
136
- return iodine_engine_deregister2(self, self);
112
+ /** Should return 0 on success and -1 on failure. */
113
+ static int iodine_pubsub_on_publish(const pubsub_engine_s *eng, FIOBJ channel,
114
+ FIOBJ msg) {
115
+ if (iodine_engine(eng)->handler == Qnil) {
116
+ return -1;
117
+ }
118
+ iodine_pubsub_task_s task = {
119
+ .eng = iodine_engine(eng), .ch = channel, .msg = msg};
120
+ IodineCaller.enterGVL(iodine_pubsub_GIL_publish, &task);
121
+ return 0;
137
122
  }
123
+ /**
124
+ * facil.io will call this callback whenever starting, or restarting, the
125
+ * reactor.
126
+ *
127
+ * but iodine engines should probably use the `before_fork` and `after_fork`
128
+ * hooks.
129
+ */
130
+ static void iodine_pubsub_on_startup(const pubsub_engine_s *eng) { (void)eng; }
131
+
132
+ /* *****************************************************************************
133
+ Ruby methods
134
+ ***************************************************************************** */
138
135
 
139
136
  /**
140
- This method resets the engine, (re)sending all the current subscription data as
141
- if the {register} method was just called.
137
+ OVERRIDE this callback - it will be called by {Iodine} whenever the process
138
+ CLUSTER (not just this process) subscribes to a stream / channel.
142
139
  */
143
- static VALUE iodine_engine_reset2(VALUE self, VALUE engine) {
144
- iodine_engine_s *e;
145
- Data_Get_Struct(engine, iodine_engine_s, e);
146
- if (e->p) {
147
- e->handler = engine;
148
- pubsub_engine_resubscribe(e->p);
149
- return Qtrue;
140
+ static VALUE iodine_pubsub_subscribe(VALUE self, VALUE to, VALUE match) {
141
+ return Qnil;
142
+ #if 0
143
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
144
+ if (e->engine == &e->do_not_touch) {
145
+ /* this is a Ruby engine, nothing to do. */
146
+ return Qnil;
150
147
  }
151
- return Qfalse;
148
+ FIOBJ ch = fiobj_str_new(RSTRING_PTR(to), RSTRING_LEN(to));
149
+ e->engine->subscribe(e->engine, ch, SYM2ID(match) == redis_id);
150
+ fiobj_free(ch);
151
+ return to;
152
+ #endif
152
153
  (void)self;
153
- (void)engine;
154
+ (void)to;
155
+ (void)match;
154
156
  }
155
157
 
156
158
  /**
157
- This method resets the engine, (re)sending all the current subscription data as
158
- if the {register} method was just called.
159
+ OVERRIDE this callback - it will be called by {Iodine} whenever the whole
160
+ process CLUSTER (not just this process) unsubscribes from a stream / channel.
159
161
  */
160
- static VALUE iodine_engine_reset(VALUE self) {
161
- return iodine_engine_reset2(self, self);
162
- }
163
-
164
- /* *****************************************************************************
165
- Ruby Subscription Object
166
- ***************************************************************************** */
167
- typedef struct {
168
- uintptr_t subscription;
169
- intptr_t uuid;
170
- void *owner;
171
- iodine_pubsub_type_e type;
172
- } iodine_subscription_s;
173
-
174
- static inline iodine_subscription_s subscription_data(VALUE self) {
175
- iodine_subscription_s data = {.uuid = iodine_get_fd(self)};
176
- if (data.uuid && !sock_isvalid(data.uuid)) {
177
- iodine_set_fd(self, -1);
178
- data.uuid = -1;
179
- return data;
180
- }
181
-
182
- data.subscription =
183
- ((uintptr_t)NUM2LL(rb_ivar_get(self, iodine_timeout_var_id)));
184
- data.owner = iodine_get_cdata(self);
185
- if (!data.owner) {
186
- data.type = IODINE_PUBSUB_GLOBAL;
187
- } else if ((uintptr_t)data.owner & 1) {
188
- data.owner = (void *)((uintptr_t)data.owner & (~(uintptr_t)1));
189
- data.type = IODINE_PUBSUB_SSE;
190
- } else {
191
- data.type = IODINE_PUBSUB_WEBSOCKET;
162
+ static VALUE iodine_pubsub_unsubscribe(VALUE self, VALUE to, VALUE match) {
163
+ return Qnil;
164
+ #if 0
165
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
166
+ if (e->engine == &e->do_not_touch) {
167
+ /* this is a Ruby engine, nothing to do. */
168
+ return Qnil;
192
169
  }
193
- return data;
170
+ FIOBJ ch = fiobj_str_new(RSTRING_PTR(to), RSTRING_LEN(to));
171
+ e->engine->unsubscribe(e->engine, ch, SYM2ID(match) == redis_id);
172
+ fiobj_free(ch);
173
+ return to;
174
+ #endif
175
+ (void)self;
176
+ (void)to;
177
+ (void)match;
194
178
  }
195
179
 
196
- static inline VALUE subscription_initialize(uintptr_t sub, intptr_t uuid,
197
- void *owner,
198
- iodine_pubsub_type_e type,
199
- VALUE channel) {
200
- VALUE self = RubyCaller.call(IodinePubSubSubscription, iodine_new_func_id);
201
- if (type == IODINE_PUBSUB_SSE)
202
- owner = (void *)((uintptr_t)owner | (uintptr_t)1);
203
- iodine_set_cdata(self, owner);
204
- iodine_set_fd(self, uuid);
205
- rb_ivar_set(self, to_str_shadow_id, channel);
206
- rb_ivar_set(self, iodine_timeout_var_id, ULL2NUM(sub));
207
- return self;
208
- }
180
+ /**
181
+ OVERRIDE this callback - it will be called by {Iodine} whenever the
182
+ {Iodine.publish} (or {Iodine::Connection#publish}) is called for this engine.
209
183
 
210
- // static void set_subscription(VALUE self, pubsub_sub_pt sub) {
211
- // iodine_set_cdata(self, sub);
212
- // }
184
+ If this {Engine} is set as the default {Engine}, then any call to
185
+ {Iodine.publish} (or {Iodine::Connection#publish} will invoke this callback
186
+ (unless another {Engine} was specified).
213
187
 
214
- /** Closes (cancels) a subscription. */
215
- static VALUE close_subscription(VALUE self) {
216
- iodine_subscription_s data = subscription_data(self);
217
- if (!data.subscription)
188
+ NOTE: this callback is called per process event (not per cluster event) and the
189
+ {Engine} is responsible for message propagation.
190
+ */
191
+ static VALUE iodine_pubsub_publish(VALUE self, VALUE to, VALUE message) {
192
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
193
+ if (e->engine == &e->do_not_touch) {
194
+ /* this is a Ruby engine, nothing to do. */
218
195
  return Qnil;
219
- switch (data.type) {
220
- case IODINE_PUBSUB_GLOBAL:
221
- pubsub_unsubscribe((pubsub_sub_pt)data.subscription);
222
- break;
223
- case IODINE_PUBSUB_WEBSOCKET:
224
- websocket_unsubscribe(data.owner, data.subscription);
225
- break;
226
- case IODINE_PUBSUB_SSE:
227
- http_sse_unsubscribe(data.owner, data.subscription);
228
- break;
229
- }
230
- rb_ivar_set(self, iodine_timeout_var_id, ULL2NUM(0));
231
- return Qnil;
232
- }
233
-
234
- /** Test if the subscription's target is equal to String. */
235
- static VALUE subscription_eq_s(VALUE self, VALUE str) {
236
- return rb_str_equal(rb_attr_get(self, to_str_shadow_id), str);
237
- }
238
-
239
- /** Returns the target stream / channel / pattern as a String object. */
240
- static VALUE subscription_to_s(VALUE self) {
241
- return rb_attr_get(self, to_str_shadow_id);
242
- }
243
-
244
- /* *****************************************************************************
245
- Ruby API
246
- ***************************************************************************** */
247
-
248
- pubsub_engine_s *iodine_engine_ruby2facil(VALUE ruby_engine) {
249
- if (ruby_engine == Qnil || ruby_engine == Qfalse)
250
- return NULL;
251
- iodine_engine_s *engine;
252
- Data_Get_Struct(ruby_engine, iodine_engine_s, engine);
253
- if (engine)
254
- return engine->p;
255
- return NULL;
196
+ }
197
+ FIOBJ ch, msg;
198
+ ch = fiobj_str_new(RSTRING_PTR(to), RSTRING_LEN(to));
199
+ msg = fiobj_str_new(RSTRING_PTR(message), RSTRING_LEN(message));
200
+ e->engine->publish(e->engine, ch, msg);
201
+ fiobj_free(ch);
202
+ fiobj_free(msg);
203
+ return self;
204
+ (void)self;
205
+ (void)to;
206
+ (void)message;
256
207
  }
257
208
 
258
209
  /* *****************************************************************************
259
- C => Ruby Bridge
210
+ Ruby <=> C Data Type
260
211
  ***************************************************************************** */
261
212
 
262
- struct engine_gvl_args_s {
263
- const pubsub_engine_s *eng;
264
- FIOBJ ch;
265
- FIOBJ msg;
266
- uint8_t use_pattern;
267
- };
268
-
269
- static void *engine_subscribe_inGVL(void *a_) {
270
- struct engine_gvl_args_s *args = a_;
271
- VALUE eng = ((iodine_engine_s *)args->eng)->handler;
272
- if (!eng || eng == Qnil || eng == Qfalse)
273
- return NULL;
274
- VALUE data[2];
275
- fio_cstr_s tmp = fiobj_obj2cstr(args->ch);
276
- data[1] = args->use_pattern ? Qtrue : Qnil;
277
- data[0] = rb_str_new(tmp.data, tmp.len);
278
- eng = RubyCaller.call2(eng, engine_subid, 2, data);
279
- return NULL;
280
- }
281
-
282
- /* Should return 0 on success and -1 on failure. */
283
- static void engine_subscribe(const pubsub_engine_s *eng, FIOBJ ch,
284
- uint8_t use_pattern) {
285
- struct engine_gvl_args_s args = {
286
- .eng = eng, .ch = ch, .use_pattern = use_pattern,
287
- };
288
- RubyCaller.call_c(engine_subscribe_inGVL, &args);
213
+ /* a callback for the GC (marking active objects) */
214
+ static void iodine_pubsub_data_mark(void *c_) {
215
+ iodine_pubsub_s *c = c_;
216
+ if (c->handler != Qnil) {
217
+ rb_gc_mark(c->handler);
218
+ }
289
219
  }
290
-
291
- static void *engine_unsubscribe_inGVL(void *a_) {
292
- struct engine_gvl_args_s *args = a_;
293
- VALUE eng = ((iodine_engine_s *)args->eng)->handler;
294
- if (!eng || eng == Qnil || eng == Qfalse)
295
- return NULL;
296
- VALUE data[2];
297
- fio_cstr_s tmp = fiobj_obj2cstr(args->ch);
298
- data[1] = args->use_pattern ? Qtrue : Qnil;
299
- data[0] = rb_str_new(tmp.data, tmp.len);
300
- RubyCaller.call2(eng, engine_unsubid, 2, data);
301
- return NULL;
220
+ /* a callback for the GC (marking active objects) */
221
+ static void iodine_pubsub_data_free(void *c_) {
222
+ iodine_pubsub_s *data = c_;
223
+ if (data->dealloc) {
224
+ data->dealloc(data->engine);
225
+ }
226
+ free(data);
302
227
  }
303
228
 
304
- /* Return value is ignored - nothing should be returned. */
305
- static void engine_unsubscribe(const pubsub_engine_s *eng, FIOBJ ch,
306
- uint8_t use_pattern) {
307
- struct engine_gvl_args_s args = {
308
- .eng = eng, .ch = ch, .use_pattern = use_pattern,
309
- };
310
- RubyCaller.call_c(engine_unsubscribe_inGVL, &args);
229
+ static size_t iodine_pubsub_data_size(const void *c_) {
230
+ return sizeof(iodine_pubsub_s);
231
+ (void)c_;
311
232
  }
312
233
 
313
- static void *engine_publish_inGVL(void *a_) {
314
- struct engine_gvl_args_s *args = a_;
315
- VALUE eng = ((iodine_engine_s *)args->eng)->handler;
316
- if (!eng || eng == Qnil || eng == Qfalse)
317
- return NULL;
318
- VALUE data[2];
319
- fio_cstr_s tmp = fiobj_obj2cstr(args->ch);
320
- data[0] = rb_str_new(tmp.data, tmp.len);
321
- Registry.add(data[0]);
322
- tmp = fiobj_obj2cstr(args->msg);
323
- data[1] = rb_str_new(tmp.data, tmp.len);
324
- Registry.add(data[1]);
325
- eng = RubyCaller.call2(eng, iodine_engine_pubid, 2, data);
326
- Registry.remove(data[0]);
327
- Registry.remove(data[1]);
328
- return ((eng == Qfalse || eng == Qnil) ? (void *)-1 : 0);
329
- }
234
+ const rb_data_type_t iodine_pubsub_data_type = {
235
+ .wrap_struct_name = "IodinePubSubData",
236
+ .function =
237
+ {
238
+ .dmark = iodine_pubsub_data_mark,
239
+ .dfree = iodine_pubsub_data_free,
240
+ .dsize = iodine_pubsub_data_size,
241
+ },
242
+ .data = NULL,
243
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
244
+ };
330
245
 
331
- /* Should return 0 on success and -1 on failure. */
332
- static int engine_publish(const pubsub_engine_s *eng, FIOBJ ch, FIOBJ msg) {
333
- struct engine_gvl_args_s args = {
334
- .eng = eng, .ch = ch, .msg = msg,
246
+ /* Iodine::PubSub::Engine.allocate */
247
+ static VALUE iodine_pubsub_data_alloc_c(VALUE self) {
248
+ iodine_pubsub_s *c = malloc(sizeof(*c));
249
+ *c = (iodine_pubsub_s){
250
+ .do_not_touch =
251
+ {
252
+ .subscribe = iodine_pubsub_on_subscribe,
253
+ .unsubscribe = iodine_pubsub_on_unsubscribe,
254
+ .publish = iodine_pubsub_on_publish,
255
+ .on_startup = iodine_pubsub_on_startup,
256
+ },
257
+ .handler = Qnil,
258
+ .engine = &c->do_not_touch,
335
259
  };
336
- return RubyCaller.call_c(engine_publish_inGVL, &args) ? 0 : -1;
260
+ return TypedData_Wrap_Struct(self, &iodine_pubsub_data_type, c);
337
261
  }
338
262
 
339
263
  /* *****************************************************************************
340
- C <=> Ruby Data allocation
264
+ C engines
341
265
  ***************************************************************************** */
342
266
 
343
- /* a callback for the GC (marking active objects) */
344
- static void engine_mark(void *eng_) {
345
- iodine_engine_s *eng = eng_;
346
- rb_gc_mark(eng->handler);
347
- }
348
- /* a callback for the GC (marking active objects) */
349
- static void engine_free(void *eng_) {
350
- iodine_engine_s *eng = eng_;
351
- if (eng->dealloc)
352
- eng->dealloc(eng->p);
353
- free(eng);
354
- }
355
-
356
- /* Iodine::PubSub::Engine.allocate */
357
- static VALUE engine_alloc_c(VALUE self) {
358
- iodine_engine_s *eng = malloc(sizeof(*eng));
359
- if (TYPE(self) == T_CLASS)
360
- *eng = (iodine_engine_s){
361
- .handler = (VALUE)0,
362
- .engine =
363
- {
364
- .subscribe = engine_subscribe,
365
- .unsubscribe = engine_unsubscribe,
366
- .publish = engine_publish,
367
- },
368
- .p = &eng->engine,
369
- };
370
-
371
- return Data_Wrap_Struct(self, engine_mark, engine_free, eng);
372
- }
373
-
374
- static VALUE engine_initialize(VALUE self) {
375
- iodine_engine_s *engine;
376
- Data_Get_Struct(self, iodine_engine_s, engine);
377
- if (TYPE(self) == T_CLASS) {
378
- fprintf(stderr, "This sucks...\n");
267
+ static VALUE iodine_pubsub_make_C_engine(const pubsub_engine_s *e) {
268
+ VALUE engine = IodineCaller.call(EngineClass, rb_intern2("new", 3));
269
+ if (engine == Qnil) {
270
+ return Qnil;
379
271
  }
380
- engine->handler = self;
381
- return self;
272
+ iodine_pubsub_CData(engine)->engine = (pubsub_engine_s *)e;
273
+ return engine;
382
274
  }
383
275
 
384
276
  /* *****************************************************************************
385
- Redis
277
+ PubSub module methods
386
278
  ***************************************************************************** */
387
279
 
388
- struct redis_callback_data {
389
- FIOBJ msg;
390
- VALUE block;
391
- };
392
-
393
- /*
394
- Perform a Redis message callback in the GVL
395
- */
396
- static void *perform_redis_callback_inGVL(void *data) {
397
- struct redis_callback_data *a = data;
398
- VALUE reply = fiobj2rb_deep(a->msg, 1);
399
- Registry.add(reply);
400
- rb_funcallv(a->block, iodine_call_proc_id, 1, &reply);
401
- Registry.remove(a->block);
402
- Registry.remove(reply);
403
- return NULL;
404
- }
405
-
406
- /*
407
- Redis message callback
408
- */
409
- static void redis_callback(pubsub_engine_s *e, FIOBJ reply, void *block) {
410
- struct redis_callback_data d = {
411
- .msg = reply, .block = (VALUE)block,
412
- };
413
- RubyCaller.call_c(perform_redis_callback_inGVL, &d);
414
- (void)e;
415
- }
416
-
417
- /**
418
- Sends commands / messages to the underlying Redis Pub connection.
419
-
420
- The method accepts an optional callback block. i.e.:
421
-
422
- redis.send("Echo", "Hello World!") do |reply|
423
- p reply # => ["Hello World!"]
424
- end
425
-
426
- This connection is only for publishing and database commands. The Sub commands,
427
- such as SUBSCRIBE and PSUBSCRIBE, will break the engine.
428
- */
429
- static VALUE redis_send(int argc, VALUE *argv, VALUE self) {
430
- if (argc < 1)
431
- rb_raise(rb_eArgError,
432
- "wrong number of arguments (given %d, expected at least 1).",
433
- argc);
434
- Check_Type(argv[0], T_STRING);
435
- FIOBJ data = FIOBJ_INVALID;
436
- FIOBJ cmd = FIOBJ_INVALID;
437
- if (argc > 1) {
438
- for (int i = 0; i < argc; ++i) {
439
- if (TYPE(argv[i]) == T_SYMBOL)
440
- argv[i] = rb_sym2str(argv[i]);
441
- if (TYPE(argv[i]) != T_FIXNUM)
442
- Check_Type(argv[i], T_STRING);
443
- }
444
- data = fiobj_ary_new();
445
- for (int i = 0; i < argc; ++i) {
446
- if (TYPE(argv[i]) == T_FIXNUM)
447
- fiobj_ary_push(data, fiobj_num_new(FIX2LONG(argv[i])));
448
- else
449
- fiobj_ary_push(
450
- data, fiobj_str_new(RSTRING_PTR(argv[i]), RSTRING_LEN(argv[i])));
451
- }
280
+ /** Sets the default {Iodine::PubSub::Engine} for pub/sub methods. */
281
+ static VALUE iodine_pubsub_default_set(VALUE self, VALUE engine) {
282
+ if (engine == Qnil) {
283
+ engine = rb_const_get(self, rb_intern2("CLUSTER", 7));
452
284
  }
453
- cmd = fiobj_str_new(RSTRING_PTR(argv[0]), RSTRING_LEN(argv[0]));
454
- iodine_engine_s *e;
455
- Data_Get_Struct(self, iodine_engine_s, e);
456
-
457
- if (rb_block_given_p()) {
458
- VALUE block = rb_block_proc();
459
- Registry.add(block);
460
- redis_engine_send(e->p, cmd, data, redis_callback, (void *)block);
461
- return block;
462
- } else {
463
- redis_engine_send(e->p, cmd, data, NULL, NULL);
285
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
286
+ if (!e) {
287
+ rb_raise(rb_eTypeError, "not a valid engine");
288
+ return Qnil;
464
289
  }
465
- fiobj_free(cmd);
466
- fiobj_free(data);
467
- return Qtrue;
290
+ if (e->handler == Qnil) {
291
+ e->handler = engine;
292
+ }
293
+ PUBSUB_DEFAULT_ENGINE = e->engine;
294
+ rb_ivar_set(self, rb_intern2("default_engine", 14), engine);
295
+ return engine;
468
296
  }
469
297
 
470
- /**
471
- Initializes a new RedisEngine for Pub/Sub.
472
-
473
- use:
474
-
475
- RedisEngine.new(address, port = 6379, ping_interval = 0)
476
-
477
- Accepts:
478
-
479
- address:: the Redis server's address. Required.
480
- port:: the Redis Server port. Default: 6379
481
- ping:: the PING interval up to 255 seconds. Default: 0 (~5 minutes).
482
- auth:: authentication password. Default: none.
483
- */
484
- static VALUE redis_engine_initialize(int argc, VALUE *argv, VALUE self) {
485
- if (argc < 1 || argc > 4)
486
- rb_raise(rb_eArgError,
487
- "wrong number of arguments (given %d, expected 1..4).", argc);
488
- VALUE address = argv[0];
489
- VALUE port = argc >= 2 ? argv[1] : Qnil;
490
- VALUE ping = argc >= 3 ? argv[2] : Qnil;
491
- VALUE auth = argc >= 4 ? argv[3] : Qnil;
492
- Check_Type(address, T_STRING);
493
- if (port != Qnil) {
494
- if (TYPE(port) == T_FIXNUM)
495
- port = rb_fix2str(port, 10);
496
- Check_Type(port, T_STRING);
497
- }
498
- if (ping != Qnil)
499
- Check_Type(ping, T_FIXNUM);
500
- if (auth != Qnil) {
501
- Check_Type(auth, T_STRING);
502
- }
503
- size_t iping = FIX2LONG(ping);
504
- if (iping > 255)
505
- rb_raise(rb_eRangeError, "ping_interval too big (0..255)");
506
-
507
- iodine_engine_s *engine;
508
- Data_Get_Struct(self, iodine_engine_s, engine);
509
- engine->handler = self;
510
- engine->p =
511
- redis_engine_create(.address = StringValueCStr(address),
512
- .port =
513
- (port == Qnil ? "6379" : StringValueCStr(port)),
514
- .ping_interval = iping,
515
- .auth = (auth == Qnil ? NULL : StringValueCStr(auth)),
516
- .auth_len = (auth == Qnil ? 0 : RSTRING_LEN(auth)));
517
- engine->dealloc = redis_engine_destroy;
518
- if (!engine->p)
519
- rb_raise(rb_eRuntimeError, "unknown error, can't initialize RedisEngine.");
520
- return self;
298
+ /** Returns the default {Iodine::PubSub::Engine} for pub/sub methods. */
299
+ static VALUE iodine_pubsub_default_get(VALUE self) {
300
+ VALUE def = rb_ivar_get(self, rb_intern2("default_engine", 14));
301
+ if (def == Qnil) {
302
+ def = rb_const_get(self, rb_intern2("CLUSTER", 7));
303
+ iodine_pubsub_default_set(self, def);
304
+ }
305
+ return def;
521
306
  }
522
307
 
523
- /* *****************************************************************************
524
- PubSub settings
525
- ***************************************************************************** */
526
-
527
308
  /**
528
- Sets the default Pub/Sub engine to be used.
529
-
530
- See {Iodine::PubSub} and {Iodine::PubSub::Engine} for more details.
531
- */
532
- static VALUE ips_set_default(VALUE self, VALUE en) {
533
- iodine_engine_s *e;
534
- Data_Get_Struct(en, iodine_engine_s, e);
535
- if (!e)
536
- rb_raise(rb_eArgError, "deafult engine must be an Iodine::PubSub::Engine.");
537
- if (!e->p)
538
- rb_raise(rb_eArgError, "This Iodine::PubSub::Engine is broken.");
539
- rb_ivar_set(Iodine, default_pubsubid, en);
540
- PUBSUB_DEFAULT_ENGINE = e->p;
541
- return en;
309
+ * Attaches an {Iodine::PubSub::Engine} to the pub/sub system (more than a
310
+ * single engine can be attached at the same time).
311
+ *
312
+ * After an engine was attached, it's callbacks will be called
313
+ * ({Iodine::PubSub::Engine#subscribe} and {Iodine::PubSub::Engine#unsubscribe})
314
+ * in response to Pub/Sub events.
315
+ */
316
+ static VALUE iodine_pubsub_attach(VALUE self, VALUE engine) {
317
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
318
+ if (!e) {
319
+ rb_raise(rb_eTypeError, "not a valid engine");
320
+ return Qnil;
321
+ }
322
+ if (e->handler == Qnil) {
323
+ e->handler = engine;
324
+ }
325
+ IodineStore.add(engine);
326
+ pubsub_engine_register(e->engine);
327
+ return engine;
542
328
  (void)self;
543
329
  }
544
330
 
545
- /** Deprecated. Use {Iodine::PubSub.default_engine=}. */
546
- static VALUE ips_set_default_dep(VALUE self, VALUE en) {
547
- fprintf(stderr, "WARNING: Iodine.default_pubsub is deprecated. Use "
548
- "Iodine::PubSub.default_engine.\n");
549
- return ips_set_default(self, en);
550
- }
551
-
552
331
  /**
553
- Returns the default Pub/Sub engine (if any).
554
-
555
- See {Iodine::PubSub} and {Iodine::PubSub::Engine} for more details.
556
- */
557
- static VALUE ips_get_default(VALUE self) {
558
- return rb_ivar_get(Iodine, default_pubsubid);
332
+ * Removes an {Iodine::PubSub::Engine} from the pub/sub system.
333
+ *
334
+ * After an {Iodine::PubSub::Engine} was detached, Iodine will no longer call
335
+ * the {Iodine::PubSub::Engine}'s callbacks ({Iodine::PubSub::Engine#subscribe}
336
+ * and {Iodine::PubSub::Engine#unsubscribe})
337
+ */
338
+ static VALUE iodine_pubsub_dettach(VALUE self, VALUE engine) {
339
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
340
+ if (!e) {
341
+ rb_raise(rb_eTypeError, "not a valid engine");
342
+ return Qnil;
343
+ }
344
+ if (e->handler == Qnil) {
345
+ e->handler = engine;
346
+ }
347
+ IodineStore.remove(engine);
348
+ pubsub_engine_deregister(e->engine);
349
+ return engine;
559
350
  (void)self;
560
351
  }
561
352
 
562
353
  /**
563
- Deprecated. Use {Iodine::PubSub.default_engine}.
564
- */
565
- static VALUE ips_get_default_dep(VALUE self) {
566
- fprintf(stderr, "WARNING: Iodine.default_pubsub is deprecated. Use "
567
- "Iodine::PubSub.default_engine.\n");
568
- return ips_get_default(self);
354
+ * Forces {Iodine} to call the {Iodine::PubSub::Engine#subscribe} callback for
355
+ * all existing subscriptions (i.e., when reconnecting to a Pub/Sub backend such
356
+ * as Redis).
357
+ */
358
+ static VALUE iodine_pubsub_reset(VALUE self, VALUE engine) {
359
+ iodine_pubsub_s *e = iodine_pubsub_CData(engine);
360
+ if (!e) {
361
+ rb_raise(rb_eTypeError, "not a valid engine");
362
+ return Qnil;
363
+ }
364
+ if (e->handler == Qnil) {
365
+ e->handler = engine;
366
+ }
367
+ pubsub_engine_resubscribe(e->engine);
368
+ return engine;
369
+ (void)self;
569
370
  }
570
371
 
571
372
  /* *****************************************************************************
572
- Pub/Sub API
373
+ Redis Engine
573
374
  ***************************************************************************** */
574
375
 
575
- static void iodine_on_unsubscribe(void *u1, void *u2) {
576
- if (u1 && (VALUE)u1 != Qnil && u1 != (VALUE)Qfalse)
577
- Registry.remove((VALUE)u1);
578
- (void)u2;
579
- }
376
+ /**
377
+ Initializes a new {Iodine::PubSub::Redis} engine.
580
378
 
581
- static void *on_pubsub_notificationinGVL(pubsub_message_s *n) {
582
- VALUE rbn[2];
583
- fio_cstr_s tmp = fiobj_obj2cstr(n->channel);
584
- rbn[0] = rb_str_new(tmp.data, tmp.len);
585
- Registry.add(rbn[0]);
586
- tmp = fiobj_obj2cstr(n->message);
587
- rbn[1] = rb_str_new(tmp.data, tmp.len);
588
- Registry.add(rbn[1]);
589
- RubyCaller.call2((VALUE)n->udata1, iodine_call_proc_id, 2, rbn);
590
- Registry.remove(rbn[0]);
591
- Registry.remove(rbn[1]);
592
- return NULL;
593
- }
379
+ Iodine::PubSub::Redis.new(url, opt = {})
594
380
 
595
- static void on_pubsub_notificationin(pubsub_message_s *n) {
596
- RubyCaller.call_c((void *(*)(void *))on_pubsub_notificationinGVL, n);
597
- }
381
+ use:
598
382
 
599
- static void iodine_on_unsubscribe_ws(void *u) {
600
- if (u && (VALUE)u != Qnil && u != (VALUE)Qfalse)
601
- Registry.remove((VALUE)u);
602
- }
383
+ REDIS_URL = "redis://localhost:6379/"
384
+ Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
603
385
 
604
- static void *
605
- on_pubsub_notificationinGVL_ws(websocket_pubsub_notification_s *n) {
606
- VALUE rbn[2];
607
- fio_cstr_s tmp = fiobj_obj2cstr(n->channel);
608
- rbn[0] = rb_str_new(tmp.data, tmp.len);
609
- Registry.add(rbn[0]);
610
- tmp = fiobj_obj2cstr(n->message);
611
- rbn[1] = rb_str_new(tmp.data, tmp.len);
612
- Registry.add(rbn[1]);
613
- RubyCaller.call2((VALUE)n->udata, iodine_call_proc_id, 2, rbn);
614
- Registry.remove(rbn[0]);
615
- Registry.remove(rbn[1]);
616
- return NULL;
617
- }
386
+ To use Redis authentication, add the password to the URL. i.e.:
618
387
 
619
- static void on_pubsub_notificationin_ws(websocket_pubsub_notification_s n) {
620
- RubyCaller.call_c((void *(*)(void *))on_pubsub_notificationinGVL_ws, &n);
621
- }
388
+ REDIS_URL = "redis://redis:password@localhost:6379/"
389
+ Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
622
390
 
623
- static void on_pubsub_notificationin_sse(http_sse_s *sse, FIOBJ channel,
624
- FIOBJ message, void *udata) {
625
- websocket_pubsub_notification_s n = {
626
- .channel = channel, .message = message, .udata = udata};
627
- RubyCaller.call_c((void *(*)(void *))on_pubsub_notificationinGVL, &n);
628
- (void)sse;
629
- }
391
+ The options hash accepts:
630
392
 
631
- /** Subscribes to a Pub/Sub channel - internal implementation */
632
- VALUE iodine_subscribe(int argc, VALUE *argv, void *owner,
633
- iodine_pubsub_type_e type) {
634
-
635
- VALUE rb_ch = Qnil;
636
- VALUE rb_opt = 0;
637
- VALUE block = 0;
638
- uint8_t use_pattern = 0, force_text = 1, force_binary = 0;
639
- intptr_t uuid = 0;
640
-
641
- switch (argc) {
642
- case 2:
643
- rb_ch = argv[0];
644
- rb_opt = argv[1];
645
- break;
646
- case 1:
647
- /* single argument must be a Hash / channel name */
648
- if (TYPE(argv[0]) == T_HASH) {
649
- rb_opt = argv[0];
650
- rb_ch = rb_hash_aref(argv[0], to_sym_id);
651
- if (rb_ch == Qnil || rb_ch == Qfalse) {
652
- /* temporary backport support */
653
- rb_ch = rb_hash_aref(argv[0], channel_sym_id);
654
- if (rb_ch) {
655
- fprintf(stderr,
656
- "WARNING: use of :channel in subscribe is deprecated.\n");
657
- }
658
- }
659
- } else {
660
- rb_ch = argv[0];
661
- }
662
- break;
663
- default:
664
- rb_raise(rb_eArgError, "method accepts 1 or 2 arguments.");
393
+ :ping:: the PING interval up to 255 seconds. Default: 0 (~5 minutes).
394
+ */
395
+ static VALUE iodine_pubsub_redis_new(int argc, VALUE *argv, VALUE self) {
396
+ if (!argc) {
397
+ rb_raise(rb_eArgError, "Iodine::PubSub::Redis.new(address, opt={}) "
398
+ "requires at least 1 argument.");
399
+ }
400
+ VALUE url = argv[0];
401
+ Check_Type(url, T_STRING);
402
+ if (RSTRING_LEN(url) > 4096) {
403
+ rb_raise(rb_eArgError, "Redis URL too long.");
404
+ }
405
+ FIOBJ port = FIOBJ_INVALID;
406
+ FIOBJ address = FIOBJ_INVALID;
407
+ FIOBJ auth = FIOBJ_INVALID;
408
+ uint8_t ping = 0;
409
+
410
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
411
+ if (!e) {
412
+ rb_raise(rb_eTypeError, "not a valid engine");
665
413
  return Qnil;
666
414
  }
667
415
 
668
- if (rb_ch == Qnil || rb_ch == Qfalse) {
669
- rb_raise(rb_eArgError,
670
- "a target (:to) subject / stream / channel is required.");
416
+ /* extract options */
417
+ if (argc == 2) {
418
+ Check_Type(argv[1], T_HASH);
419
+ VALUE tmp = rb_hash_aref(argv[1], rb_id2sym(rb_intern2("ping", 4)));
420
+ if (tmp != Qnil) {
421
+ Check_Type(tmp, T_FIXNUM);
422
+ if (NUM2SIZET(tmp) > 255) {
423
+ rb_raise(rb_eArgError,
424
+ ":ping must be a non-negative integer under 255 seconds.");
425
+ }
426
+ ping = (uint8_t)NUM2SIZET(tmp);
427
+ }
671
428
  }
672
429
 
673
- if (TYPE(rb_ch) == T_SYMBOL)
674
- rb_ch = rb_sym2str(rb_ch);
675
- Check_Type(rb_ch, T_STRING);
676
-
677
- if (rb_opt) {
678
- if (type == IODINE_PUBSUB_WEBSOCKET &&
679
- rb_hash_aref(rb_opt, as_sym_id) == binary_sym_id) {
680
- force_text = 0;
681
- force_binary = 1;
430
+ /* parse URL assume redis://redis:password@localhost:6379 */
431
+ {
432
+ size_t l = RSTRING_LEN(url);
433
+ char *str = RSTRING_PTR(url);
434
+ char *pointers[5];
435
+ char *end = str + l;
436
+ uint8_t flag = 1;
437
+ uint8_t counter = 0;
438
+ for (size_t i = 0; i < l; i++) {
439
+ if (counter > 4)
440
+ goto finish;
441
+ if (str[i] == ':' && str[i + 1] == '/' && str[i + 2] == '/') {
442
+ pointers[counter++] = str + i + 3;
443
+ i = i + 2;
444
+ flag = 0;
445
+ continue;
446
+ }
447
+ if (str[i] == '@' && counter == 1 - flag) {
448
+ rb_raise(rb_eArgError, "malformed URL");
449
+ }
450
+ if (str[i] == ':' || str[i] == '@') {
451
+ pointers[counter++] = str + i + 1;
452
+ continue;
453
+ }
454
+ if (str[i] == '/') {
455
+ end = str + i;
456
+ break;
457
+ }
682
458
  }
683
- if (rb_hash_aref(rb_opt, match_sym_id) == redis_sym_id) {
684
- use_pattern = 1;
459
+ if (flag) {
460
+ if (counter > 3) {
461
+ rb_raise(rb_eArgError, "malformed URL");
462
+ }
463
+ /* move pointers one step forward and set 0 to str... */
464
+ char *pointers_2[5];
465
+ for (size_t i = 0; i < counter; ++i) {
466
+ pointers_2[i + 1] = pointers[i];
467
+ }
468
+ pointers_2[0] = str;
469
+ ++counter;
470
+ for (size_t i = 0; i < counter; ++i) {
471
+ pointers[i] = pointers_2[i];
472
+ }
473
+ }
474
+ /* review results */
475
+ switch (counter) {
476
+ case 1:
477
+ /* redis://localhost */
478
+ if (pointers[0] == end) {
479
+ goto finish;
480
+ }
481
+ address = fiobj_str_new(pointers[0], end - pointers[0]);
482
+ break;
483
+ case 2:
484
+ /* redis://localhost:6379 */
485
+ if (pointers[1] - pointers[0] - 1 == 0) {
486
+ goto finish;
487
+ }
488
+ address = fiobj_str_new(pointers[0], pointers[1] - pointers[0] - 1);
489
+ if (pointers[1] != end) {
490
+ port = fiobj_str_new(pointers[1], end - pointers[1]);
491
+ }
492
+ break;
493
+ case 3:
494
+ /* redis://redis:password@localhost */
495
+ if (pointers[2] - pointers[1] - 1 == 0 || end - pointers[2] == 0) {
496
+ goto finish;
497
+ }
498
+ address = fiobj_str_new(pointers[2], end - pointers[2]);
499
+ auth = fiobj_str_new(pointers[1], pointers[2] - pointers[1] - 1);
500
+ break;
501
+ case 4:
502
+ /* redis://redis:password@localhost:6379 */
503
+ if (pointers[2] - pointers[1] - 1 == 0 ||
504
+ pointers[3] - pointers[2] - 1 == 0 || end - pointers[3] == 0) {
505
+ goto finish;
506
+ }
507
+ port = fiobj_str_new(pointers[3], end - pointers[3]);
508
+ address = fiobj_str_new(pointers[2], pointers[3] - pointers[2] - 1);
509
+ auth = fiobj_str_new(pointers[1], pointers[2] - pointers[1] - 1);
510
+ break;
511
+ default:
512
+ goto finish;
685
513
  }
686
- block = rb_hash_aref(rb_opt, handler_sym_id);
687
- if (block != Qnil)
688
- Registry.add(block);
514
+ }
515
+ fprintf(
516
+ stderr,
517
+ "INFO: Initializing Redis engine for address: %s - port: %s - auth %s\n",
518
+ fiobj_obj2cstr(address).data, fiobj_obj2cstr(port).data,
519
+ fiobj_obj2cstr(auth).data);
520
+ /* create engine */
521
+ e->engine = redis_engine_create(
522
+ .address = fiobj_obj2cstr(address)
523
+ .data,
524
+ .port = (port == FIOBJ_INVALID ? "6379" : fiobj_obj2cstr(port).data),
525
+ .ping_interval = ping,
526
+ .auth = (auth == FIOBJ_INVALID ? NULL : fiobj_obj2cstr(auth).data),
527
+ .auth_len = (auth == FIOBJ_INVALID ? 0 : fiobj_obj2cstr(auth).len));
528
+ if (!e->engine) {
529
+ e->engine = &e->do_not_touch;
530
+ } else {
531
+ e->dealloc = redis_engine_destroy;
689
532
  }
690
533
 
534
+ finish:
535
+ fiobj_free(port);
536
+ fiobj_free(address);
537
+ fiobj_free(auth);
538
+ if (e->engine == &e->do_not_touch) {
539
+ rb_raise(rb_eArgError,
540
+ "Error initializing the Redis engine - malformed URL?");
541
+ }
542
+ return self;
543
+ (void)self;
544
+ (void)argc;
545
+ (void)argv;
546
+ }
547
+
548
+ /** A callback for Redis commands. */
549
+ static void iodine_pubsub_redis_callback(pubsub_engine_s *e, FIOBJ response,
550
+ void *udata) {
551
+ VALUE block = (VALUE)udata;
691
552
  if (block == Qnil) {
692
- if (rb_block_given_p()) {
693
- block = rb_block_proc();
694
- Registry.add(block);
695
- } else if (type == IODINE_PUBSUB_GLOBAL) {
696
- rb_need_block();
697
- return Qnil;
698
- }
553
+ return;
699
554
  }
700
- if (block == Qnil)
701
- block = 0;
702
-
703
- FIOBJ ch = fiobj_str_new(RSTRING_PTR(rb_ch), RSTRING_LEN(rb_ch));
704
-
705
- uintptr_t sub = 0;
706
- switch (type) {
707
- case IODINE_PUBSUB_GLOBAL:
708
- sub = (uintptr_t)pubsub_subscribe(.channel = ch, .use_pattern = use_pattern,
709
- .on_message = on_pubsub_notificationin,
710
- .on_unsubscribe = iodine_on_unsubscribe,
711
- .udata1 = (void *)block);
712
- break;
713
- case IODINE_PUBSUB_WEBSOCKET:
714
- uuid = websocket_uuid(owner);
715
- sub = websocket_subscribe(
716
- owner, .channel = ch, .use_pattern = use_pattern,
717
- .force_text = force_text, .force_binary = force_binary,
718
- .on_message = (block ? on_pubsub_notificationin_ws : NULL),
719
- .on_unsubscribe = (block ? iodine_on_unsubscribe_ws : NULL),
720
- .udata = (void *)block);
721
- break;
722
- case IODINE_PUBSUB_SSE:
723
- uuid = http_sse2uuid(owner);
724
- sub = http_sse_subscribe(
725
- owner, .channel = ch, .use_pattern = use_pattern,
726
- .on_message = (block ? on_pubsub_notificationin_sse : NULL),
727
- .on_unsubscribe = (block ? iodine_on_unsubscribe_ws : NULL),
728
- .udata = (void *)block);
729
-
730
- break;
555
+ VALUE rb = Qnil;
556
+ if (!FIOBJ_IS_NULL(response)) {
557
+ rb = IodineStore.add(fiobj2rb_deep(response, 0));
731
558
  }
732
-
733
- fiobj_free(ch);
734
- if (!sub)
735
- return Qnil;
736
- return subscription_initialize(sub, uuid, owner, type, rb_ch);
559
+ IodineCaller.call2(block, call_id, 1, &rb);
560
+ IodineStore.remove(rb);
561
+ IodineStore.remove(block);
562
+ (void)e;
737
563
  }
738
564
 
739
565
  // clang-format off
740
566
  /**
741
- Subscribes to a Pub/Sub channel.
567
+ Sends a Redis command. Accepts an optional block that will recieve the response.
742
568
 
743
- The method accepts 1-2 arguments and an optional block. These are all valid ways
744
- to call the method:
569
+ i.e.:
745
570
 
746
- subscribe("my_stream") {|from, msg| p msg }
747
- subscribe("my_stream", match: :redis) {|from, msg| p msg }
748
- subscribe(to: "my_stream") {|from, msg| p msg }
749
- subscribe to: "my_stream", match: :redis, handler: MyProc
750
-
751
- The first argument must be either a String or a Hash.
752
-
753
- The second, optional, argument must be a Hash (if given).
754
-
755
- The options Hash supports the following possible keys (other keys are ignored, all keys are Symbols):
756
-
757
- :match :: The channel / subject name matching type to be used. Valid value is: `:redis`. Future versions hope to support `:nats` and `:rabbit` patern matching as well.
758
-
759
- :to :: The channel / subject to subscribe to.
760
-
761
- Returns an {Iodine::PubSub::Subscription} object that answers to:
762
-
763
- close :: closes the connection.
764
- to_s :: returns the subscription's target (stream / channel / subject).
765
- ==(str) :: returns true if the string is an exact match for the target (even if the target itself is a pattern).
766
-
767
- */
768
- static VALUE iodine_subscribe_global(int argc, VALUE *argv, VALUE self) {
769
- // clang-format on
770
- return iodine_subscribe(argc, argv, NULL, IODINE_PUBSUB_GLOBAL);
771
- (void)self;
571
+ REDIS_URL = "redis://redis:password@localhost:6379/"
572
+ redis = Iodine::PubSub::Redis.new(REDIS_URL, ping: 50) #pings every 50 seconds
573
+ Iodine::PubSub.default = redis
574
+ redis.cmd("KEYS", "*") {|result| p result
772
575
  }
773
576
 
774
- /**
775
- Publishes a message to a channel.
776
-
777
- Can be used using two Strings:
778
-
779
- publish(to, message)
780
-
781
- The method accepts an optional `engine` argument:
782
-
783
- publish(to, message, my_pubsub_engine)
784
-
785
-
786
- Alternatively, accepts the following named arguments:
787
-
788
- :to :: The channel to publish to (required).
789
-
790
- :message :: The message to be published (required).
791
-
792
- :engine :: If provided, the engine to use for pub/sub. Otherwise the default
793
- engine is used.
794
577
 
795
578
  */
796
- VALUE iodine_publish(int argc, VALUE *argv, VALUE self) {
797
- VALUE rb_ch, rb_msg, rb_engine = Qnil;
798
- const pubsub_engine_s *engine = NULL;
799
- switch (argc) {
800
- case 3:
801
- /* fallthrough */
802
- rb_engine = argv[2];
803
- case 2:
804
- rb_ch = argv[0];
805
- rb_msg = argv[1];
806
- break;
807
- case 1: {
808
- /* single argument must be a Hash */
809
- Check_Type(argv[0], T_HASH);
810
- rb_ch = rb_hash_aref(argv[0], to_sym_id);
811
- if (rb_ch == Qnil || rb_ch == Qfalse) {
812
- rb_ch = rb_hash_aref(argv[0], channel_sym_id);
813
- }
814
- rb_msg = rb_hash_aref(argv[0], message_sym_id);
815
- rb_engine = rb_hash_aref(argv[0], engine_varid);
816
- } break;
817
- default:
818
- rb_raise(rb_eArgError, "method accepts 1-3 arguments.");
579
+ static VALUE iodine_pubsub_redis_cmd(int argc, VALUE *argv, VALUE self) {
580
+ // clang-format on
581
+ if (argc <= 0) {
582
+ rb_raise(rb_eArgError, "Iodine::PubSub::Redis#cmd(command, ...) is missing "
583
+ "the required command argument.");
819
584
  }
820
-
821
- if (rb_msg == Qnil || rb_msg == Qfalse) {
822
- rb_raise(rb_eArgError, "message is required.");
585
+ iodine_pubsub_s *e = iodine_pubsub_CData(self);
586
+ if (!e || !e->engine || e->engine == &e->do_not_touch) {
587
+ rb_raise(rb_eTypeError,
588
+ "Iodine::PubSub::Redis internal error - obsolete object?");
823
589
  }
824
- Check_Type(rb_msg, T_STRING);
825
-
826
- if (rb_ch == Qnil || rb_ch == Qfalse)
827
- rb_raise(rb_eArgError, "target / channel is required .");
828
- if (TYPE(rb_ch) == T_SYMBOL)
829
- rb_ch = rb_sym2str(rb_ch);
830
- Check_Type(rb_ch, T_STRING);
831
-
832
- if (rb_engine == Qfalse) {
833
- engine = PUBSUB_PROCESS_ENGINE;
834
- } else if (rb_engine == Qnil) {
835
- engine = NULL;
836
- } else {
837
- engine = iodine_engine_ruby2facil(rb_engine);
590
+ VALUE block = Qnil;
591
+ if (rb_block_given_p()) {
592
+ block = IodineStore.add(rb_block_proc());
838
593
  }
594
+ FIOBJ data = fiobj_ary_new2((size_t)argc);
595
+ for (int i = 0; i < argc; ++i) {
596
+ switch (TYPE(argv[i])) {
597
+ case T_SYMBOL:
598
+ argv[i] = rb_sym2str(argv[i]);
599
+ /* overflow */
600
+ case T_STRING:
601
+ fiobj_ary_push(data,
602
+ fiobj_str_new(RSTRING_PTR(argv[i]), RSTRING_LEN(argv[i])));
603
+ break;
604
+ case T_FIXNUM:
605
+ fiobj_ary_push(data, fiobj_num_new(NUM2SSIZET(argv[i])));
606
+ break;
607
+ case T_FLOAT:
608
+ fiobj_ary_push(data, fiobj_float_new(rb_float_value(argv[i])));
609
+ break;
610
+ case T_NIL:
611
+ fiobj_ary_push(data, fiobj_null());
612
+ break;
613
+ case T_TRUE:
614
+ fiobj_ary_push(data, fiobj_true());
615
+ break;
616
+ case T_FALSE:
617
+ fiobj_ary_push(data, fiobj_false());
618
+ break;
619
+ default:
620
+ goto wrong_type;
621
+ }
622
+ }
623
+ FIOBJ cmd = fiobj_ary_shift(data);
624
+ if (redis_engine_send(e->engine, cmd, data, iodine_pubsub_redis_callback,
625
+ (void *)block)) {
626
+ iodine_pubsub_redis_callback(e->engine, fiobj_null(), (void *)block);
627
+ }
628
+ fiobj_free(cmd);
629
+ fiobj_free(data);
630
+ return self;
839
631
 
840
- FIOBJ ch = fiobj_str_new(RSTRING_PTR(rb_ch), RSTRING_LEN(rb_ch));
841
- FIOBJ msg = fiobj_str_new(RSTRING_PTR(rb_msg), RSTRING_LEN(rb_msg));
842
-
843
- intptr_t ret =
844
- pubsub_publish(.engine = engine, .channel = ch, .message = msg);
845
- fiobj_free(ch);
846
- fiobj_free(msg);
847
- if (!ret)
848
- return Qfalse;
849
- return Qtrue;
850
- (void)self;
632
+ wrong_type:
633
+ fiobj_free(data);
634
+ rb_raise(rb_eArgError,
635
+ "only String, Number (with limits), Symbol, true, false and nil "
636
+ "arguments can be used.");
851
637
  }
852
638
 
853
639
  /* *****************************************************************************
854
- Initialization
640
+ Module initialization
855
641
  ***************************************************************************** */
856
- void Iodine_init_pubsub(void) {
857
- default_pubsubid = rb_intern("default_pubsub");
858
- engine_subid = rb_intern("subscribe");
859
- engine_unsubid = rb_intern("unsubscribe");
860
- engine_varid = rb_intern("engine");
861
- iodine_engine_pubid = rb_intern("publish");
862
- to_str_shadow_id = rb_intern("@to_s");
863
-
864
- as_sym_id = ID2SYM(rb_intern("as"));
865
- binary_sym_id = ID2SYM(rb_intern("binary"));
866
- handler_sym_id = ID2SYM(rb_intern("handler"));
867
- match_sym_id = ID2SYM(rb_intern("match"));
868
- message_sym_id = ID2SYM(rb_intern("message"));
869
- redis_sym_id = ID2SYM(rb_intern("redis"));
870
- text_sym_id = ID2SYM(rb_intern("text"));
871
- to_sym_id = ID2SYM(rb_intern("to"));
872
-
873
- channel_sym_id = ID2SYM(rb_intern("channel")); /* bawards compatibility */
874
-
875
- IodinePubSub = rb_define_module_under(Iodine, "PubSub");
876
- IodineEngine = rb_define_class_under(IodinePubSub, "Engine", rb_cObject);
877
- IodinePubSubSubscription =
878
- rb_define_class_under(IodinePubSub, "Subscription", rb_cObject);
879
-
880
- rb_define_method(IodinePubSubSubscription, "close", close_subscription, 0);
881
- rb_define_method(IodinePubSubSubscription, "==", subscription_eq_s, 1);
882
- rb_attr(IodinePubSubSubscription, rb_intern("to_s"), 1, 0, 1);
883
- rb_define_method(IodinePubSubSubscription, "to_s", subscription_to_s, 1);
884
-
885
- rb_define_alloc_func(IodineEngine, engine_alloc_c);
886
- rb_define_method(IodineEngine, "initialize", engine_initialize, 0);
887
-
888
- rb_define_method(IodineEngine, "subscribe", engine_sub_placeholder, 2);
889
- rb_define_method(IodineEngine, "unsubscribe", engine_sub_placeholder, 2);
890
- rb_define_method(IodineEngine, "publish", engine_pub_placeholder, 2);
891
- rb_define_method(IodineEngine, "register", iodine_engine_register, 0);
892
- rb_define_method(IodineEngine, "deregister", iodine_engine_deregister, 0);
893
- rb_define_method(IodineEngine, "reset", iodine_engine_reset, 0);
894
-
895
- rb_define_module_function(Iodine, "subscribe", iodine_subscribe_global, -1);
896
- rb_define_module_function(Iodine, "publish", iodine_publish, -1);
897
- rb_define_module_function(Iodine, "default_engine=", ips_set_default, 1);
898
- rb_define_module_function(Iodine, "default_engine", ips_get_default, 0);
899
-
900
- rb_define_module_function(IodinePubSub, "default_engine=", ips_set_default,
901
- 1);
902
- rb_define_module_function(IodinePubSub, "default_engine", ips_get_default, 0);
903
- rb_define_method(IodinePubSub, "register", iodine_engine_register2, 1);
904
- rb_define_method(IodinePubSub, "deregister", iodine_engine_deregister2, 1);
905
- rb_define_method(IodinePubSub, "reset", iodine_engine_reset2, 1);
906
-
907
- rb_define_module_function(IodinePubSub, "subscribe", iodine_subscribe_global,
908
- -1);
909
- rb_define_module_function(IodinePubSub, "publish", iodine_publish, -1);
910
642
 
911
- /* deprecated */
643
+ /** Initializes the Connection Ruby class. */
644
+ void iodine_pubsub_init(void) {
645
+ subscribe_id = rb_intern2("subscribe", 9);
646
+ unsubscribe_id = rb_intern2("unsubscribe", 11);
647
+ publish_id = rb_intern2("publish", 7);
648
+ default_id = rb_intern2("default_engine", 14);
649
+ redis_id = rb_intern2("redis", 5);
650
+ call_id = rb_intern2("call", 4);
912
651
 
913
- rb_define_module_function(Iodine, "default_pubsub=", ips_set_default_dep, 1);
914
- rb_define_module_function(Iodine, "default_pubsub", ips_get_default_dep, 0);
652
+ /* Define the PubSub module and it's methods */
915
653
 
916
- /* *************************
917
- Initialize C pubsub engines
918
- ************************** */
919
- VALUE engine_in_c;
920
- iodine_engine_s *engine;
921
-
922
- engine_in_c = rb_funcallv(IodineEngine, iodine_new_func_id, 0, NULL);
923
- Data_Get_Struct(engine_in_c, iodine_engine_s, engine);
924
- engine->p = (pubsub_engine_s *)PUBSUB_CLUSTER_ENGINE;
925
-
926
- /** This is the (currently) default pub/sub engine. It will distribute
927
- * messages to all subscribers in the process cluster. */
928
- rb_define_const(IodinePubSub, "CLUSTER", engine_in_c);
929
-
930
- // rb_const_set(IodineEngine, rb_intern("CLUSTER"), e);
931
-
932
- engine_in_c = rb_funcallv(IodineEngine, iodine_new_func_id, 0, NULL);
933
- Data_Get_Struct(engine_in_c, iodine_engine_s, engine);
934
- engine->p = (pubsub_engine_s *)PUBSUB_PROCESS_ENGINE;
935
-
936
- /** This is a single process pub/sub engine. It will distribute messages to
937
- * all subscribers sharing the same process. */
938
- rb_define_const(IodinePubSub, "SINGLE_PROCESS", engine_in_c);
939
-
940
- // rb_const_set(IodineEngine, rb_intern("SINGLE_PROCESS"), e);
941
-
942
- engine_in_c =
943
- rb_define_class_under(IodinePubSub, "RedisEngine", IodineEngine);
944
- rb_define_method(engine_in_c, "initialize", redis_engine_initialize, -1);
945
- rb_define_method(engine_in_c, "send", redis_send, -1);
654
+ VALUE PubSubModule = rb_define_module_under(IodineModule, "PubSub");
655
+ rb_define_module_function(PubSubModule, "default=", iodine_pubsub_default_set,
656
+ 1);
657
+ rb_define_module_function(PubSubModule, "default", iodine_pubsub_default_get,
658
+ 0);
659
+ rb_define_module_function(PubSubModule, "attach", iodine_pubsub_attach, 1);
660
+ rb_define_module_function(PubSubModule, "dettach", iodine_pubsub_dettach, 1);
661
+ rb_define_module_function(PubSubModule, "reset", iodine_pubsub_reset, 1);
662
+
663
+ /* Define the Engine class and it's methods */
664
+
665
+ /**
666
+ The {Iodine::PubSub::Engine} class is the parent for all engines to inherit
667
+ from.
668
+
669
+ Engines should inherit this class and override the `subscribe`, `unsubscribe`
670
+ and `publish` callbacks (which shall be called by {Iodine}).
671
+
672
+ After creation, Engines should attach themselves to Iodine using
673
+ {Iodine::PubSub.attach} or their callbacks will never get called.
674
+
675
+ Engines can also set themselves to be the default engine using
676
+ {Iodine::PubSub.default=}.
677
+ */
678
+ EngineClass = rb_define_class_under(PubSubModule, "Engine", rb_cObject);
679
+ rb_define_alloc_func(EngineClass, iodine_pubsub_data_alloc_c);
680
+ rb_define_method(EngineClass, "subscribe", iodine_pubsub_subscribe, 2);
681
+ rb_define_method(EngineClass, "unsubscribe", iodine_pubsub_unsubscribe, 2);
682
+ rb_define_method(EngineClass, "publish", iodine_pubsub_publish, 2);
683
+
684
+ /* Define the CLUSTER and PROCESS engines */
685
+
686
+ /* CLUSTER publishes data to all the subscribers in the process cluster. */
687
+ rb_define_const(PubSubModule, "CLUSTER",
688
+ iodine_pubsub_make_C_engine(PUBSUB_CLUSTER_ENGINE));
689
+ /* PROCESS publishes data to all the subscribers in a single process. */
690
+ rb_define_const(PubSubModule, "PROCESS",
691
+ iodine_pubsub_make_C_engine(PUBSUB_PROCESS_ENGINE));
692
+
693
+ VALUE RedisClass = rb_define_class_under(PubSubModule, "Redis", EngineClass);
694
+ rb_define_method(RedisClass, "initialize", iodine_pubsub_redis_new, -1);
695
+ rb_define_method(RedisClass, "cmd", iodine_pubsub_redis_cmd, -1);
946
696
  }