iodine 0.4.19 → 0.5.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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
@@ -0,0 +1,304 @@
1
+ #include "iodine.h"
2
+
3
+ #include "fio_ary.h"
4
+ #include "fio_json_parser.h"
5
+ #include "fio_mem.h"
6
+ #include "fiobj.h"
7
+ #include "rb-fiobj2rb.h"
8
+ #include "rb-registry.h"
9
+
10
+ static VALUE max_nesting;
11
+ static VALUE allow_nan;
12
+ static VALUE symbolize_names;
13
+ static VALUE create_additions;
14
+ static VALUE object_class;
15
+ static VALUE array_class;
16
+
17
+ /* *****************************************************************************
18
+ JSON Callacks - these must be implemented in the C file that uses the parser
19
+ ***************************************************************************** */
20
+
21
+ /* *****************************************************************************
22
+ JSON Parser handling (practically copied from the FIOBJ library)
23
+ ***************************************************************************** */
24
+
25
+ typedef struct {
26
+ json_parser_s p;
27
+ VALUE key;
28
+ VALUE top;
29
+ VALUE target;
30
+ fio_ary_s stack;
31
+ uint8_t is_hash;
32
+ uint8_t symbolize;
33
+ } iodine_json_parser_s;
34
+
35
+ static inline void iodine_json_add2parser(iodine_json_parser_s *p, VALUE o) {
36
+ if (p->top) {
37
+ if (p->is_hash) {
38
+ if (p->key) {
39
+ rb_hash_aset(p->top, p->key, o);
40
+ Registry.remove(p->key);
41
+ p->key = (VALUE)0;
42
+ } else {
43
+ // if (p->symbolize) {
44
+ // o = rb_to_symbol(o);
45
+ // }
46
+ p->key = o;
47
+ Registry.add(o);
48
+ }
49
+ } else {
50
+ rb_ary_push(p->top, o);
51
+ }
52
+ } else {
53
+ Registry.add(o);
54
+ p->top = o;
55
+ }
56
+ }
57
+
58
+ /** a NULL object was detected */
59
+ static void fio_json_on_null(json_parser_s *p) {
60
+ iodine_json_add2parser((iodine_json_parser_s *)p, Qnil);
61
+ }
62
+ /** a TRUE object was detected */
63
+ static void fio_json_on_true(json_parser_s *p) {
64
+ iodine_json_add2parser((iodine_json_parser_s *)p, Qtrue);
65
+ }
66
+ /** a FALSE object was detected */
67
+ static void fio_json_on_false(json_parser_s *p) {
68
+ iodine_json_add2parser((iodine_json_parser_s *)p, Qfalse);
69
+ }
70
+ /** a Numberl was detected (long long). */
71
+ static void fio_json_on_number(json_parser_s *p, long long i) {
72
+ iodine_json_add2parser((iodine_json_parser_s *)p, LONG2NUM(i));
73
+ }
74
+ /** a Float was detected (double). */
75
+ static void fio_json_on_float(json_parser_s *p, double f) {
76
+ iodine_json_add2parser((iodine_json_parser_s *)p, DBL2NUM(f));
77
+ }
78
+ /** a String was detected (int / float). update `pos` to point at ending */
79
+ static void fio_json_on_string(json_parser_s *p, void *start, size_t length) {
80
+ /* Ruby overhead for a rb_str_buf_new is very high. Double copy is faster. */
81
+ char *tmp = fio_malloc(length);
82
+ size_t new_len = fio_json_unescape_str(tmp, start, length);
83
+ VALUE buf;
84
+ if (((iodine_json_parser_s *)p)->symbolize &&
85
+ ((iodine_json_parser_s *)p)->is_hash &&
86
+ !((iodine_json_parser_s *)p)->key) {
87
+ ID id = rb_intern2(tmp, new_len);
88
+ buf = rb_id2sym(id);
89
+ } else {
90
+ buf = rb_str_new(tmp, new_len);
91
+ }
92
+ iodine_json_add2parser((iodine_json_parser_s *)p, buf);
93
+ fio_free(tmp);
94
+ }
95
+ /** a dictionary object was detected, should return 0 unless error occurred. */
96
+ static int fio_json_on_start_object(json_parser_s *p) {
97
+ iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
98
+ if (pr->target) {
99
+ /* push NULL, don't free the objects */
100
+ fio_ary_push(&pr->stack, (void *)pr->top);
101
+ pr->top = pr->target;
102
+ pr->target = 0;
103
+ } else {
104
+ VALUE h = rb_hash_new();
105
+ iodine_json_add2parser(pr, h);
106
+ fio_ary_push(&pr->stack, (void *)pr->top);
107
+ pr->top = h;
108
+ }
109
+ pr->is_hash = 1;
110
+ return 0;
111
+ }
112
+ /** a dictionary object closure detected */
113
+ static void fio_json_on_end_object(json_parser_s *p) {
114
+ iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
115
+ if (pr->key) {
116
+ fprintf(stderr, "WARNING: (JSON parsing) malformed JSON, "
117
+ "ignoring dangling Hash key.\n");
118
+ Registry.remove(pr->key);
119
+ pr->key = (VALUE)0;
120
+ }
121
+ pr->top = (VALUE)fio_ary_pop(&pr->stack);
122
+ pr->is_hash = (TYPE(pr->top) == T_HASH);
123
+ }
124
+ /** an array object was detected */
125
+ static int fio_json_on_start_array(json_parser_s *p) {
126
+ iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
127
+ if (pr->target)
128
+ return -1;
129
+ VALUE ary = rb_ary_new();
130
+ iodine_json_add2parser(pr, ary);
131
+ fio_ary_push(&pr->stack, (void *)pr->top);
132
+ pr->top = ary;
133
+ pr->is_hash = 0;
134
+ return 0;
135
+ }
136
+ /** an array closure was detected */
137
+ static void fio_json_on_end_array(json_parser_s *p) {
138
+ iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
139
+ pr->top = (VALUE)fio_ary_pop(&pr->stack);
140
+ pr->is_hash = (TYPE(pr->top) == T_HASH);
141
+ }
142
+ /** the JSON parsing is complete */
143
+ static void fio_json_on_json(json_parser_s *p) { (void)p; /* do nothing */ }
144
+ /** the JSON parsing is complete */
145
+ static void fio_json_on_error(json_parser_s *p) {
146
+ iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
147
+ #if DEBUG
148
+ fprintf(stderr, "ERROR: JSON on error called.\n");
149
+ #endif
150
+ Registry.remove((VALUE)fio_ary_index(&pr->stack, 0));
151
+ Registry.remove(pr->key);
152
+ fio_ary_free(&pr->stack);
153
+ *pr = (iodine_json_parser_s){.top = 0};
154
+ }
155
+
156
+ /* *****************************************************************************
157
+ Iodine JSON Implementation
158
+ ***************************************************************************** */
159
+
160
+ static inline VALUE iodine_json_convert(VALUE str, fiobj2rb_settings_s s) {
161
+
162
+ iodine_json_parser_s p = {.top = 0, .symbolize = s.str2sym};
163
+ size_t consumed = fio_json_parse(&p.p, RSTRING_PTR(str), RSTRING_LEN(str));
164
+ if (!consumed || p.p.depth) {
165
+ Registry.remove((VALUE)fio_ary_index(&p.stack, 0));
166
+ p.top = FIOBJ_INVALID;
167
+ }
168
+ fio_ary_free(&p.stack);
169
+ if (p.key) {
170
+ Registry.remove((VALUE)p.key);
171
+ }
172
+ if (!p.top) {
173
+ rb_raise(rb_eEncodingError, "Malformed JSON format.");
174
+ }
175
+ Registry.remove(p.top);
176
+ return p.top;
177
+ }
178
+
179
+ // static inline VALUE iodine_json_convert2(VALUE str, fiobj2rb_settings_s s) {
180
+ // FIOBJ json;
181
+ // if (!fiobj_json2obj(&json, RSTRING_PTR(str), RSTRING_LEN(str)) || !json) {
182
+ // rb_raise(rb_eRuntimeError, "JSON parsing failed. Not JSON?");
183
+ // return Qnil;
184
+ // }
185
+ // VALUE ret = fiobj2rb_deep(json, s.str2sym);
186
+ // fiobj_free(json);
187
+ // return ret;
188
+ // }
189
+
190
+ static inline void iodine_json_update_settings(VALUE h,
191
+ fiobj2rb_settings_s *s) {
192
+ VALUE tmp;
193
+ if (rb_hash_aref(h, max_nesting) != Qnil)
194
+ fprintf(stderr,
195
+ "WARNING: max_nesting ignored on this JSON implementation.\n");
196
+ if (rb_hash_aref(h, allow_nan) != Qnil)
197
+ fprintf(stderr, "WARNING: allow_nan ignored on this JSON implementation. "
198
+ "NaN always allowed.\n");
199
+ if (rb_hash_aref(h, create_additions) != Qnil)
200
+ fprintf(stderr,
201
+ "WARNING: create_additions ignored on this JSON implementation.\n");
202
+ if (rb_hash_aref(h, object_class) != Qnil)
203
+ fprintf(stderr,
204
+ "WARNING: object_class ignored on this JSON implementation.\n");
205
+ if (rb_hash_aref(h, array_class) != Qnil)
206
+ fprintf(stderr,
207
+ "WARNING: array_class ignored on this JSON implementation.\n");
208
+ if ((tmp = rb_hash_aref(h, symbolize_names)) != Qnil) {
209
+ if (tmp == Qtrue)
210
+ s->str2sym = 1;
211
+ else if (tmp == Qfalse)
212
+ s->str2sym = 0;
213
+ }
214
+ }
215
+
216
+ /**
217
+ Parse a JSON string using the iodine lenient parser (it's also faster).
218
+ */
219
+ static VALUE iodine_json_parse(int argc, VALUE *argv, VALUE self) {
220
+ fiobj2rb_settings_s s = {.str2sym = 0};
221
+ if (argc > 2)
222
+ rb_raise(rb_eTypeError, "function requires supports up to two arguments.");
223
+ if (argc == 2) {
224
+ Check_Type(argv[1], T_HASH);
225
+ iodine_json_update_settings(argv[1], &s);
226
+ }
227
+ if (argc >= 1)
228
+ Check_Type(argv[0], T_STRING);
229
+ else
230
+ rb_raise(rb_eTypeError, "function requires at least one argument.");
231
+ return iodine_json_convert(argv[0], s);
232
+ (void)self;
233
+ }
234
+
235
+ /**
236
+ Parse a JSON string using the iodine lenient parser with a default Symbol
237
+ rather than String key (this is often faster than the regular
238
+ {Iodine::JSON.parse} function).
239
+ */
240
+ static VALUE iodine_json_parse_bang(int argc, VALUE *argv, VALUE self) {
241
+ fiobj2rb_settings_s s = {.str2sym = 0};
242
+ if (argc > 2)
243
+ rb_raise(rb_eTypeError, "function requires supports up to two arguments.");
244
+ if (argc == 2) {
245
+ Check_Type(argv[1], T_HASH);
246
+ iodine_json_update_settings(argv[1], &s);
247
+ }
248
+ if (argc >= 1)
249
+ Check_Type(argv[0], T_STRING);
250
+ else
251
+ rb_raise(rb_eTypeError, "function requires at least one argument.");
252
+ return iodine_json_convert(argv[0], s);
253
+ (void)self;
254
+ }
255
+
256
+ void Iodine_init_json(void) {
257
+ /**
258
+ Iodine::JSON offers a fast(er) JSON parser that is also lenient and supports
259
+ some JSON extensions such as Hex number recognition and comments.
260
+
261
+ You can test the parser using:
262
+
263
+ JSON_FILENAME="foo.json"
264
+
265
+ require 'json'
266
+ require 'iodine'
267
+ TIMES = 100
268
+ STR = IO.binread(JSON_FILENAME); nil
269
+
270
+ JSON.parse(STR) == Iodine::JSON.parse(STR) # => true
271
+ JSON.parse(STR,
272
+ symbolize_names: true) == Iodine::JSON.parse(STR,
273
+ symbolize_names: true) # => true
274
+ JSON.parse!(STR) == Iodine::JSON.parse!(STR) # => true/false (unknown)
275
+
276
+ # warm-up
277
+ TIMES.times { JSON.parse STR }
278
+ TIMES.times { Iodine::JSON.parse STR }
279
+
280
+ Benchmark.bm do |b|
281
+ sys = b.report("system") { TIMES.times { JSON.parse STR } }
282
+ sys_sym = b.report("system sym") { TIMES.times { JSON.parse STR,
283
+ symbolize_names: true } }
284
+ iodine = b.report("iodine") { TIMES.times { Iodine::JSON.parse STR } }
285
+ iodine_sym = b.report("iodine sym") do
286
+ TIMES.times { Iodine::JSON.parse STR,
287
+ symbolize_names: true }
288
+ end
289
+ puts "System / Iodine: #{sys/iodine}"
290
+ puts "System-sym/Iodine-sym: #{sys_sym/iodine_sym}"
291
+ end; nil
292
+
293
+
294
+ */
295
+ VALUE tmp = rb_define_module_under(Iodine, "JSON");
296
+ max_nesting = ID2SYM(rb_intern("max_nesting"));
297
+ allow_nan = ID2SYM(rb_intern("allow_nan"));
298
+ symbolize_names = ID2SYM(rb_intern("symbolize_names"));
299
+ create_additions = ID2SYM(rb_intern("create_additions"));
300
+ object_class = ID2SYM(rb_intern("object_class"));
301
+ array_class = ID2SYM(rb_intern("array_class"));
302
+ rb_define_module_function(tmp, "parse", iodine_json_parse, -1);
303
+ rb_define_module_function(tmp, "parse!", iodine_json_parse_bang, -1);
304
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef H_IODINE_JSON_H
2
+ #define H_IODINE_JSON_H
3
+
4
+ void Iodine_init_json(void);
5
+
6
+ #endif
@@ -5,9 +5,16 @@ License: MIT
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
7
  #include "iodine_protocol.h"
8
+ #include "iodine_pubsub.h"
9
+
10
+ #include "fio_llist.h"
11
+ #include "fiobj4sock.h"
12
+ #include "pubsub.h"
8
13
 
9
14
  #include <ruby/io.h>
10
15
 
16
+ static ID iodine_close_func_id;
17
+
11
18
  /* *****************************************************************************
12
19
  The protocol object
13
20
  ***************************************************************************** */
@@ -15,6 +22,7 @@ The protocol object
15
22
  typedef struct {
16
23
  protocol_s protocol;
17
24
  VALUE handler;
25
+ fio_ls_s subscriptions;
18
26
  } iodine_protocol_s;
19
27
 
20
28
  VALUE IodineProtocol;
@@ -30,12 +38,15 @@ static void iodine_clear_task(intptr_t origin, void *block_) {
30
38
  (void)origin;
31
39
  }
32
40
 
33
- static void iodine_perform_task(intptr_t uuid, protocol_s *pr, void *block_) {
34
- if (pr->service == iodine_protocol_service) {
35
- RubyCaller.call2((VALUE)block_, iodine_call_proc_id, 1, (VALUE *)(pr + 1));
36
- }
37
- (void)uuid;
38
- }
41
+ // static void iodine_perform_task(intptr_t uuid, protocol_s *pr, void *block_)
42
+ // {
43
+ // if (pr->service == iodine_protocol_service) {
44
+ // RubyCaller.call2((VALUE)block_, iodine_call_proc_id, 1, (VALUE *)(pr +
45
+ // 1));
46
+ // }
47
+ // (void)uuid;
48
+ // }
49
+
39
50
  static void iodine_perform_task_and_free(intptr_t uuid, protocol_s *pr,
40
51
  void *block_) {
41
52
  if (pr->service == iodine_protocol_service) {
@@ -65,6 +76,20 @@ static VALUE not_implemented(VALUE self) {
65
76
  return Qnil;
66
77
  }
67
78
 
79
+ /** DEPRECATED! Please override {on_drained} instead. */
80
+ static VALUE not_implemented_on_ready(VALUE self, VALUE data) {
81
+ (void)(self);
82
+ (void)(data);
83
+ return Qnil;
84
+ }
85
+
86
+ /** Override this callback to handle the event. */
87
+ static VALUE not_implemented_drained(VALUE self) {
88
+ RubyCaller.call(self, rb_intern2("on_ready", 8));
89
+ (void)(self);
90
+ return Qnil;
91
+ }
92
+
68
93
  /** Override this callback to handle the event. */
69
94
  static VALUE not_implemented2(VALUE self, VALUE data) {
70
95
  (void)(self);
@@ -95,42 +120,62 @@ static VALUE default_on_data(VALUE self) {
95
120
  }
96
121
 
97
122
  /* *****************************************************************************
98
- Published functions
123
+ Pub/Sub functions
99
124
  ***************************************************************************** */
100
125
 
101
- /** Returns the number of total connections managed by Iodine. */
102
- static VALUE dyn_count(VALUE self) {
103
- size_t count = facil_count(iodine_protocol_service);
104
- return ULL2NUM(count);
105
- (void)self;
106
- }
107
-
126
+ // clang-format off
108
127
  /**
109
- Runs a task for each dynamic connection (not websockets or HTTP).
128
+ Subscribes the connection to a Pub/Sub channel.
129
+
130
+ Since this connection's data packaging is unknown, a block (or handler) is required to handle pub/sub events.
131
+
132
+ The method accepts 1-2 arguments and an optional block. These are all valid ways
133
+ to call the method:
134
+
135
+ subscribe("my_stream") {|from, msg| p msg }
136
+ subscribe("my_stream", match: :redis) {|from, msg| p msg }
137
+ subscribe(to: "my_stream") {|from, msg| p msg }
138
+ subscribe to: "my_stream", match: :redis, handler: MyProc
110
139
 
111
- Requires a block and returns it as an object.
140
+ The first argument must be either a String or a Hash.
112
141
 
113
- i.e., will write to every open dynamic connection:
142
+ The second, optional, argument must be a Hash (if given).
114
143
 
115
- Iodine.each {|obj| obj.write "hello!" }
144
+ The options Hash supports the following possible keys (other keys are ignored, all keys are Symbols):
145
+
146
+ :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.
147
+
148
+ :to :: The channel / subject to subscribe to.
149
+
150
+ :handler :: and Proc like object, must answer to `call(from, msg)`.
151
+
152
+ Returns an {Iodine::PubSub::Subscription} object that answers to:
153
+
154
+ close :: closes the connection.
155
+ to_s :: returns the subscription's target (stream / channel / subject).
156
+ ==(str) :: returns true if the string is an exact match for the target (even if the target itself is a pattern).
116
157
 
117
158
  */
118
- static VALUE dyn_run_each(VALUE self) {
119
- rb_need_block();
120
- VALUE block = rb_block_proc();
121
- if (block == Qnil)
159
+ static VALUE iodine_proto_subscribe(int argc, VALUE *argv, VALUE self) {
160
+ // clang-format on
161
+ intptr_t uuid = iodine_get_fd(self);
162
+ if (!uuid || (VALUE)uuid == Qnil || uuid < 0)
163
+ return Qfalse;
164
+ VALUE sub = iodine_subscribe(argc, argv, NULL, IODINE_PUBSUB_GLOBAL);
165
+ if (sub == Qnil || sub == Qfalse)
122
166
  return Qfalse;
123
- intptr_t origin = iodine_get_fd(self);
124
- if (!origin)
125
- origin = -1;
167
+ Registry.add(sub);
126
168
 
127
- Registry.add(block);
128
- facil_each(.arg = (void *)block, .service = "IodineDynamic", .origin = origin,
129
- .task_type = FIO_PR_LOCK_TASK, .task = iodine_perform_task,
130
- .on_complete = iodine_clear_task);
131
- return block;
169
+ iodine_protocol_s *pr = iodine_get_cdata(self);
170
+
171
+ fio_ls_push(&pr->subscriptions, (void *)sub);
172
+ return sub;
132
173
  }
133
174
 
175
+ /* *****************************************************************************
176
+ Published functions
177
+ ***************************************************************************** */
178
+
134
179
  /**
135
180
  Reads up to `n` bytes from the network connection.
136
181
  The number of bytes to be read (n) is:
@@ -222,7 +267,7 @@ static VALUE dyn_write_move(VALUE self, VALUE data) {
222
267
  Registry.add(data);
223
268
  intptr_t fd = iodine_get_fd(self);
224
269
  if (sock_write2(.uuid = fd, .buffer = RSTRING_PTR(data),
225
- .length = RSTRING_LEN(data), .move = 1,
270
+ .length = RSTRING_LEN(data),
226
271
  .dealloc = (void (*)(void *))Registry.remove))
227
272
  return Qfalse;
228
273
  return self;
@@ -352,7 +397,7 @@ static VALUE dyn_defer(int argc, VALUE *argv, VALUE self) {
352
397
  return Qfalse;
353
398
  Registry.add(block);
354
399
  facil_defer(.uuid = fd, .task = iodine_perform_task_and_free,
355
- .task_type = FIO_PR_LOCK_TASK, .arg = (void *)block,
400
+ .type = FIO_PR_LOCK_TASK, .arg = (void *)block,
356
401
  .fallback = iodine_clear_task);
357
402
  return block;
358
403
  }
@@ -370,7 +415,7 @@ static void dyn_protocol_on_data(intptr_t fduuid, protocol_s *protocol) {
370
415
  /** called when the socket is ready to be written to. */
371
416
  static void dyn_protocol_on_ready(intptr_t fduuid, protocol_s *protocol) {
372
417
  (void)(fduuid);
373
- RubyCaller.call(dyn_prot(protocol)->handler, iodine_on_ready_func_id);
418
+ RubyCaller.call(dyn_prot(protocol)->handler, iodine_on_drained_func_id);
374
419
  }
375
420
  /** called when the server is shutting down,
376
421
  * but before closing the connection. */
@@ -378,11 +423,23 @@ static void dyn_protocol_on_shutdown(intptr_t fduuid, protocol_s *protocol) {
378
423
  (void)(fduuid);
379
424
  RubyCaller.call(dyn_prot(protocol)->handler, iodine_on_shutdown_func_id);
380
425
  }
426
+
427
+ static void *clear_subscriptions_inGVL(void *pr_) {
428
+ iodine_protocol_s *pr = pr_;
429
+ while (fio_ls_any(&pr->subscriptions)) {
430
+ RubyCaller.call((VALUE)fio_ls_pop(&pr->subscriptions),
431
+ iodine_close_func_id);
432
+ }
433
+ return NULL;
434
+ }
435
+
381
436
  /** called when the connection was closed, but will not run concurrently */
382
437
  static void dyn_protocol_on_close(intptr_t uuid, protocol_s *protocol) {
383
438
  RubyCaller.call(dyn_prot(protocol)->handler, iodine_on_close_func_id);
384
439
  iodine_set_fd(dyn_prot(protocol)->handler, 0);
440
+ iodine_set_cdata(dyn_prot(protocol)->handler, NULL);
385
441
  Registry.remove(dyn_prot(protocol)->handler);
442
+ RubyCaller.call_c(clear_subscriptions_inGVL, protocol);
386
443
  free(protocol);
387
444
  (void)uuid;
388
445
  }
@@ -401,7 +458,6 @@ Connection management API
401
458
  static inline protocol_s *dyn_set_protocol(intptr_t fduuid, VALUE handler,
402
459
  uint8_t timeout) {
403
460
  Registry.add(handler);
404
- iodine_set_fd(handler, fduuid);
405
461
  iodine_protocol_s *protocol = malloc(sizeof(*protocol));
406
462
  if (protocol == NULL) {
407
463
  Registry.remove(handler);
@@ -416,16 +472,23 @@ static inline protocol_s *dyn_set_protocol(intptr_t fduuid, VALUE handler,
416
472
  .protocol.ping = dyn_protocol_ping,
417
473
  .protocol.service = iodine_protocol_service,
418
474
  .handler = handler,
475
+ .subscriptions = FIO_LS_INIT(protocol->subscriptions),
419
476
  };
477
+ iodine_set_fd(handler, fduuid);
478
+ iodine_set_cdata(handler, protocol);
420
479
  RubyCaller.call(handler, iodine_on_open_func_id);
421
480
  return &protocol->protocol;
422
481
  }
423
482
 
424
- static protocol_s *on_open_dyn_protocol(intptr_t fduuid, void *udata) {
483
+ static void on_open_dyn_protocol(intptr_t fduuid, void *udata) {
425
484
  VALUE rb_tout = rb_ivar_get((VALUE)udata, iodine_timeout_var_id);
426
485
  uint8_t timeout = (TYPE(rb_tout) == T_FIXNUM) ? FIX2UINT(rb_tout) : 0;
427
486
  VALUE handler = RubyCaller.call((VALUE)udata, iodine_new_func_id);
428
- return dyn_set_protocol(fduuid, handler, timeout);
487
+ if (handler == Qnil) {
488
+ sock_close(fduuid);
489
+ return;
490
+ }
491
+ facil_attach(fduuid, dyn_set_protocol(fduuid, handler, timeout));
429
492
  }
430
493
 
431
494
  /** Sets up a listening socket. Conncetions received at the assigned port will
@@ -483,12 +546,12 @@ VALUE dyn_switch_prot(VALUE self, VALUE handler) {
483
546
  return handler;
484
547
  }
485
548
 
486
- static protocol_s *on_open_dyn_protocol_instance(intptr_t fduuid, void *udata) {
549
+ static void on_open_dyn_protocol_instance(intptr_t fduuid, void *udata) {
487
550
  VALUE rb_tout = rb_ivar_get((VALUE)udata, iodine_timeout_var_id);
488
551
  uint8_t timeout = (TYPE(rb_tout) == T_FIXNUM) ? FIX2UINT(rb_tout) : 0;
489
552
  protocol_s *pr = dyn_set_protocol(fduuid, (VALUE)udata, timeout);
490
553
  Registry.remove((VALUE)udata);
491
- return pr;
554
+ facil_attach(fduuid, pr);
492
555
  }
493
556
 
494
557
  /**
@@ -587,9 +650,9 @@ Library Initialization
587
650
  // between Lib-Server and Ruby.
588
651
  void Iodine_init_protocol(void) {
589
652
 
653
+ iodine_close_func_id = rb_intern("close");
654
+
590
655
  /* add Iodine module functions */
591
- // rb_define_module_function(Iodine, "defer", dyn_defer, -1);
592
- // rb_define_module_function(Iodine, "each", dyn_run_each, 0);
593
656
  rb_define_module_function(Iodine, "listen", iodine_listen, 2);
594
657
  rb_define_module_function(Iodine, "connect", iodine_connect, 3);
595
658
  rb_define_module_function(Iodine, "attach_io", iodine_attach_io, 2);
@@ -601,20 +664,17 @@ void Iodine_init_protocol(void) {
601
664
  rb_define_method(IodineProtocol, "on_close", not_implemented, 0);
602
665
  rb_define_method(IodineProtocol, "on_message", not_implemented2, 1);
603
666
  rb_define_method(IodineProtocol, "on_data", default_on_data, 0);
604
- rb_define_method(IodineProtocol, "on_ready", not_implemented, 0);
667
+ rb_define_method(IodineProtocol, "on_ready", not_implemented_on_ready, 0);
668
+ rb_define_method(IodineProtocol, "on_drained", not_implemented_drained, 0);
605
669
  rb_define_method(IodineProtocol, "on_shutdown", not_implemented, 0);
606
670
  rb_define_method(IodineProtocol, "ping", not_implemented_ping, 0);
607
671
 
608
672
  /* Add module functions */
609
673
  rb_define_singleton_method(IodineProtocol, "defer", dyn_defer, -1);
610
- rb_define_singleton_method(IodineProtocol, "count", dyn_count, 0);
611
- rb_define_singleton_method(IodineProtocol, "each", dyn_run_each, 0);
612
674
 
613
675
  /* Add module instance methods */
614
- // rb_define_method(IodineProtocol, "each", dyn_run_each, 0);
615
676
  rb_define_method(IodineProtocol, "open?", dyn_is_open, 0);
616
677
  rb_define_method(IodineProtocol, "conn_id", dyn_uuid, 0);
617
- rb_define_method(IodineProtocol, "count", dyn_count, 0);
618
678
  rb_define_method(IodineProtocol, "read", dyn_read, -1);
619
679
  rb_define_method(IodineProtocol, "write", dyn_write, 1);
620
680
  rb_define_method(IodineProtocol, "write!", dyn_write_move, 1);
@@ -624,4 +684,6 @@ void Iodine_init_protocol(void) {
624
684
  rb_define_method(IodineProtocol, "switch_protocol", dyn_switch_prot, 1);
625
685
  rb_define_method(IodineProtocol, "timeout=", dyn_set_timeout, 1);
626
686
  rb_define_method(IodineProtocol, "timeout", dyn_get_timeout, 0);
687
+ rb_define_method(IodineProtocol, "subscribe", iodine_proto_subscribe, -1);
688
+ rb_define_method(IodineProtocol, "publish", iodine_publish, -1);
627
689
  }