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
@@ -0,0 +1,55 @@
1
+ #ifndef H_IODINE_CONNECTION_H
2
+ #define H_IODINE_CONNECTION_H
3
+
4
+ #include "iodine.h"
5
+
6
+ typedef enum {
7
+ IODINE_CONNECTION_RAW,
8
+ IODINE_CONNECTION_WEBSOCKET,
9
+ IODINE_CONNECTION_SSE
10
+ } iodine_connection_type_e;
11
+
12
+ typedef struct {
13
+ iodine_connection_type_e type;
14
+ intptr_t uuid;
15
+ void *arg;
16
+ VALUE handler;
17
+ VALUE env;
18
+ } iodine_connection_s;
19
+
20
+ /**
21
+ * Creates a new connection object.
22
+ */
23
+ VALUE iodine_connection_new(iodine_connection_s args);
24
+ #define iodine_connection_new(...) \
25
+ iodine_connection_new((iodine_connection_s){__VA_ARGS__})
26
+
27
+ typedef enum {
28
+ IODINE_CONNECTION_ON_OPEN,
29
+ IODINE_CONNECTION_ON_MESSAGE,
30
+ IODINE_CONNECTION_ON_DRAINED,
31
+ IODINE_CONNECTION_PING,
32
+ IODINE_CONNECTION_ON_SHUTDOWN,
33
+ IODINE_CONNECTION_ON_CLOSE
34
+ } iodine_connection_event_type_e;
35
+
36
+ /**
37
+ * Fires a connection object's event. `data` is only for the on_message event.
38
+ */
39
+ void iodine_connection_fire_event(VALUE connection,
40
+ iodine_connection_event_type_e ev,
41
+ VALUE data);
42
+
43
+ /** Initializes the Connection Ruby class. */
44
+ void iodine_connection_init(void);
45
+
46
+ extern const rb_data_type_t iodine_connection_data_type;
47
+
48
+ static inline iodine_connection_s *iodine_connection_CData(VALUE self) {
49
+ iodine_connection_s *c = NULL;
50
+ TypedData_Get_Struct(self, iodine_connection_s, &iodine_connection_data_type,
51
+ c);
52
+ return c;
53
+ }
54
+
55
+ #endif
@@ -0,0 +1,391 @@
1
+ #include "iodine.h"
2
+
3
+ #include <ruby/thread.h>
4
+
5
+ #include <stdint.h>
6
+ // clang-format on
7
+
8
+ #include "facil.h"
9
+ #include <spnlock.inc>
10
+
11
+ #include <pthread.h>
12
+
13
+ /* *****************************************************************************
14
+ IO flushing dedicated thread for protection against blocking code
15
+ ***************************************************************************** */
16
+
17
+ static spn_lock_i sock_io_thread = 0;
18
+ static pthread_t sock_io_pthread;
19
+ typedef struct {
20
+ size_t threads;
21
+ size_t processes;
22
+ } iodine_start_settings_s;
23
+
24
+ static void *iodine_io_thread(void *arg) {
25
+ (void)arg;
26
+ struct timespec tm;
27
+ while (sock_io_thread) {
28
+ sock_flush_all();
29
+ tm = (struct timespec){.tv_nsec = 0, .tv_sec = 1};
30
+ nanosleep(&tm, NULL);
31
+ }
32
+ return NULL;
33
+ }
34
+ static void iodine_start_io_thread(void *a_, void *b_) {
35
+ if (!spn_trylock(&sock_io_thread)) {
36
+ pthread_create(&sock_io_pthread, NULL, iodine_io_thread, NULL);
37
+ }
38
+ (void)a_;
39
+ (void)b_;
40
+ }
41
+ static void iodine_join_io_thread(void) {
42
+ if (spn_unlock(&sock_io_thread)) {
43
+ sock_io_thread = 0;
44
+ pthread_join(sock_io_pthread, NULL);
45
+ sock_io_pthread = NULL;
46
+ }
47
+ }
48
+
49
+ /* *****************************************************************************
50
+ The Defer library overriding functions
51
+ ***************************************************************************** */
52
+
53
+ /* used to create Ruby threads and pass them the information they need */
54
+ struct CreateThreadArgs {
55
+ void *(*thread_func)(void *);
56
+ void *arg;
57
+ spn_lock_i lock;
58
+ };
59
+
60
+ /* used for GVL signalling */
61
+ void call_async_signal(void *pool) { defer_pool_stop((pool_pt)pool); }
62
+
63
+ static void *defer_thread_start(void *args_) {
64
+ struct CreateThreadArgs *args = args_;
65
+ IodineCaller.set_GVL(0);
66
+ args->thread_func(args->arg);
67
+ return NULL;
68
+ }
69
+
70
+ /* the thread's GVL release */
71
+ static VALUE defer_thread_inGVL(void *args_) {
72
+ struct CreateThreadArgs *old_args = args_;
73
+ struct CreateThreadArgs args = *old_args;
74
+ IodineCaller.set_GVL(1);
75
+ spn_unlock(&old_args->lock);
76
+ rb_thread_call_without_gvl(defer_thread_start, &args,
77
+ (void (*)(void *))call_async_signal, args.arg);
78
+ return Qnil;
79
+ }
80
+
81
+ /* Within the GVL, creates a Ruby thread using an API call */
82
+ static void *create_ruby_thread_gvl(void *args) {
83
+ return (void *)IodineStore.add(rb_thread_create(defer_thread_inGVL, args));
84
+ }
85
+
86
+ /* Runs the before / after fork callbacks (if `before` is true, before runs) */
87
+ static void iodine_perform_fork_callbacks(uint8_t before);
88
+
89
+ static void *fork_using_ruby(void *ignr) {
90
+ // stop IO thread and call before_fork callbacks
91
+ if (sock_io_pthread) {
92
+ iodine_join_io_thread();
93
+ }
94
+ iodine_perform_fork_callbacks(1);
95
+ // fork
96
+ const VALUE ProcessClass = rb_const_get(rb_cObject, rb_intern2("Process", 7));
97
+ const VALUE rb_pid = IodineCaller.call(ProcessClass, rb_intern2("fork", 4));
98
+ intptr_t pid = 0;
99
+ if (rb_pid != Qnil) {
100
+ pid = NUM2INT(rb_pid);
101
+ } else {
102
+ pid = 0;
103
+ }
104
+ // manage post forking state
105
+ IodineCaller.set_GVL(1); /* enforce GVL state in thread storage */
106
+ if (!pid) {
107
+ IodineStore.after_fork();
108
+ }
109
+ iodine_perform_fork_callbacks(0);
110
+ // re-initiate IO thread
111
+ defer(iodine_start_io_thread, NULL, NULL);
112
+ return (void *)pid;
113
+ (void)ignr;
114
+ }
115
+
116
+ /**
117
+ OVERRIDE THIS to replace the default pthread implementation.
118
+ */
119
+ void *defer_new_thread(void *(*thread_func)(void *), void *arg) {
120
+ struct CreateThreadArgs data = (struct CreateThreadArgs){
121
+ .thread_func = thread_func, .arg = arg, .lock = SPN_LOCK_INIT,
122
+ };
123
+ spn_lock(&data.lock);
124
+ void *thr = IodineCaller.enterGVL(create_ruby_thread_gvl, &data);
125
+ if (!thr || thr == (void *)Qnil || thr == (void *)Qfalse) {
126
+ thr = NULL;
127
+ } else {
128
+ /* wait for thread to signal it's alive. */
129
+ spn_lock(&data.lock);
130
+ }
131
+ return thr;
132
+ }
133
+
134
+ /**
135
+ OVERRIDE THIS to replace the default pthread implementation.
136
+ */
137
+ int defer_join_thread(void *thr) {
138
+ if (!thr || (VALUE)thr == Qfalse || (VALUE)thr == Qnil)
139
+ return -1;
140
+ IodineCaller.call((VALUE)thr, rb_intern("join"));
141
+ IodineStore.remove((VALUE)thr);
142
+ return 0;
143
+ }
144
+
145
+ // void defer_free_thread(void *thr) { (void)thr; }
146
+ void defer_free_thread(void *thr) { IodineStore.remove((VALUE)thr); }
147
+
148
+ /**
149
+ OVERRIDE THIS to replace the default `fork` implementation or to inject hooks
150
+ into the forking function.
151
+
152
+ Behaves like the system's `fork`.
153
+ */
154
+ int facil_fork(void) {
155
+ intptr_t pid = (intptr_t)IodineCaller.enterGVL(fork_using_ruby, NULL);
156
+ return (int)pid;
157
+ }
158
+
159
+ /* *****************************************************************************
160
+ Task performance
161
+ ***************************************************************************** */
162
+
163
+ static ID call_id;
164
+
165
+ static void iodine_defer_performe_once(void *block, void *ignr) {
166
+ IodineCaller.call((VALUE)block, call_id);
167
+ IodineStore.remove((VALUE)block);
168
+ (void)ignr;
169
+ }
170
+
171
+ static void iodine_defer_run_timer(void *block) {
172
+ IodineCaller.call((VALUE)block, call_id);
173
+ }
174
+
175
+ /* *****************************************************************************
176
+ Defer API
177
+ ***************************************************************************** */
178
+
179
+ /**
180
+ * Runs a block of code asyncronously (adds the code to the event queue).
181
+ *
182
+ * Always returns the block of code to executed (Proc object).
183
+ *
184
+ * Code will be executed only while Iodine is running (after {Iodine.start}).
185
+ *
186
+ * Code blocks that where scheduled to run before Iodine enters cluster mode
187
+ * will run on all child processes.
188
+ */
189
+ static VALUE iodine_defer_run(VALUE self) {
190
+ rb_need_block();
191
+ VALUE block = IodineStore.add(rb_block_proc());
192
+ defer(iodine_defer_performe_once, (void *)block, NULL);
193
+ return block;
194
+ (void)self;
195
+ }
196
+
197
+ /**
198
+ Runs the required block after the specified number of milliseconds have passed.
199
+ Time is counted only once Iodine started running (using {Iodine.start}).
200
+
201
+ Tasks scheduled before calling {Iodine.start} will run once for every process.
202
+
203
+ Always returns a copy of the block object.
204
+ */
205
+ static VALUE iodine_defer_run_after(VALUE self, VALUE milliseconds) {
206
+ (void)(self);
207
+ if (milliseconds == Qnil) {
208
+ return iodine_defer_run(self);
209
+ }
210
+ if (TYPE(milliseconds) != T_FIXNUM) {
211
+ rb_raise(rb_eTypeError, "milliseconds must be a number");
212
+ return Qnil;
213
+ }
214
+ size_t milli = FIX2UINT(milliseconds);
215
+ if (milli == 0) {
216
+ return iodine_defer_run(self);
217
+ }
218
+ // requires a block to be passed
219
+ rb_need_block();
220
+ VALUE block = rb_block_proc();
221
+ if (block == Qnil)
222
+ return Qfalse;
223
+ IodineStore.add(block);
224
+ if (facil_run_every(milli, 1, iodine_defer_run_timer, (void *)block,
225
+ (void (*)(void *))IodineStore.remove) == -1) {
226
+ perror("ERROR: Iodine couldn't initialize timer");
227
+ return Qnil;
228
+ }
229
+ return block;
230
+ }
231
+ /**
232
+ Runs the required block after the specified number of milliseconds have passed.
233
+ Time is counted only once Iodine started running (using {Iodine.start}).
234
+
235
+ Accepts:
236
+
237
+ milliseconds:: the number of milliseconds between event repetitions.
238
+
239
+ repetitions:: the number of event repetitions. Defaults to 0 (never ending).
240
+
241
+ block:: (required) a block is required, as otherwise there is nothing to
242
+ perform.
243
+
244
+ The event will repeat itself until the number of repetitions had been delpeted.
245
+
246
+ Always returns a copy of the block object.
247
+ */
248
+ static VALUE iodine_defer_run_every(int argc, VALUE *argv, VALUE self) {
249
+ (void)(self);
250
+ VALUE milliseconds, repetitions, block;
251
+
252
+ rb_scan_args(argc, argv, "11&", &milliseconds, &repetitions, &block);
253
+
254
+ if (TYPE(milliseconds) != T_FIXNUM) {
255
+ rb_raise(rb_eTypeError, "milliseconds must be a number.");
256
+ return Qnil;
257
+ }
258
+ if (repetitions != Qnil && TYPE(repetitions) != T_FIXNUM) {
259
+ rb_raise(rb_eTypeError, "repetitions must be a number or `nil`.");
260
+ return Qnil;
261
+ }
262
+
263
+ size_t milli = FIX2UINT(milliseconds);
264
+ size_t repeat = (repetitions == Qnil) ? 0 : FIX2UINT(repetitions);
265
+ // requires a block to be passed
266
+ rb_need_block();
267
+ IodineStore.add(block);
268
+ if (facil_run_every(milli, repeat, iodine_defer_run_timer, (void *)block,
269
+ (void (*)(void *))IodineStore.remove) == -1) {
270
+ perror("ERROR: Iodine couldn't initialize timer");
271
+ return Qnil;
272
+ }
273
+ return block;
274
+ }
275
+
276
+ /* *****************************************************************************
277
+ Pre/Post `fork`
278
+ ***************************************************************************** */
279
+ #include "fio_llist.h"
280
+ #include "spnlock.inc"
281
+
282
+ static spn_lock_i iodine_before_fork_lock = SPN_LOCK_INIT;
283
+ static fio_ls_s iodine_before_fork_list = FIO_LS_INIT(iodine_before_fork_list);
284
+ static spn_lock_i iodine_after_fork_lock = SPN_LOCK_INIT;
285
+ static fio_ls_s iodine_after_fork_list = FIO_LS_INIT(iodine_after_fork_list);
286
+ static spn_lock_i iodine_on_shutdown_lock = SPN_LOCK_INIT;
287
+ static fio_ls_s iodine_on_shutdown_list = FIO_LS_INIT(iodine_on_shutdown_list);
288
+
289
+ /**
290
+ Sets a block of code to run before a new worker process is forked (cluster mode
291
+ only).
292
+ */
293
+ VALUE iodine_before_fork_add(VALUE self) {
294
+ rb_need_block();
295
+ VALUE block = rb_block_proc();
296
+ IodineStore.add(block);
297
+ spn_lock(&iodine_before_fork_lock);
298
+ fio_ls_push(&iodine_before_fork_list, (void *)block);
299
+ spn_unlock(&iodine_before_fork_lock);
300
+ return block;
301
+ (void)self;
302
+ }
303
+
304
+ /**
305
+ Sets a block of code to run after a new worker process is forked (cluster mode
306
+ only).
307
+ */
308
+ VALUE iodine_after_fork_add(VALUE self) {
309
+ rb_need_block();
310
+ VALUE block = rb_block_proc();
311
+ IodineStore.add(block);
312
+ spn_lock(&iodine_after_fork_lock);
313
+ fio_ls_push(&iodine_after_fork_list, (void *)block);
314
+ spn_unlock(&iodine_after_fork_lock);
315
+ return block;
316
+ (void)self;
317
+ }
318
+
319
+ // clang-format off
320
+ /**
321
+ Sets a block of code to run once a Worker process shuts down (both in single process mode and cluster mode).
322
+ */
323
+ VALUE iodine_on_shutdown_add(VALUE self) {
324
+ // clang-format on
325
+ rb_need_block();
326
+ VALUE block = rb_block_proc();
327
+ IodineStore.add(block);
328
+ spn_lock(&iodine_on_shutdown_lock);
329
+ fio_ls_push(&iodine_on_shutdown_list, (void *)block);
330
+ spn_unlock(&iodine_on_shutdown_lock);
331
+ return block;
332
+ (void)self;
333
+ }
334
+
335
+ /* Runs the before / after fork callbacks (if `before` is true, before runs) */
336
+ static void iodine_perform_fork_callbacks(uint8_t before) {
337
+ fio_ls_s *ls = before ? &iodine_before_fork_list : &iodine_after_fork_list;
338
+ spn_lock_i *lock =
339
+ before ? &iodine_before_fork_lock : &iodine_after_fork_lock;
340
+ spn_lock(lock);
341
+ FIO_LS_FOR(ls, pos) { IodineCaller.call((VALUE)(pos->obj), call_id); }
342
+ spn_unlock(lock);
343
+ }
344
+
345
+ /* Performs any cleanup before worker dies */
346
+ void iodine_defer_on_finish(void) {
347
+ iodine_join_io_thread();
348
+ /* perform and clear away shutdown Procs */
349
+ spn_lock(&iodine_on_shutdown_lock);
350
+ while (fio_ls_any(&iodine_on_shutdown_list)) {
351
+ void *obj = fio_ls_shift(&iodine_on_shutdown_list);
352
+ IodineCaller.call((VALUE)(obj), call_id);
353
+ IodineStore.remove((VALUE)(obj));
354
+ }
355
+ spn_unlock(&iodine_on_shutdown_lock);
356
+ /* clear away forking Procs */
357
+ spn_lock(&iodine_before_fork_lock);
358
+ while (fio_ls_any(&iodine_before_fork_list)) {
359
+ IodineStore.remove((VALUE)fio_ls_shift(&iodine_before_fork_list));
360
+ }
361
+ spn_unlock(&iodine_before_fork_lock);
362
+
363
+ spn_lock(&iodine_after_fork_lock);
364
+ while (fio_ls_any(&iodine_after_fork_list)) {
365
+ IodineStore.remove((VALUE)fio_ls_shift(&iodine_after_fork_list));
366
+ void *obj = fio_ls_shift(&iodine_after_fork_list);
367
+ }
368
+ spn_unlock(&iodine_after_fork_lock);
369
+ }
370
+
371
+ /* *****************************************************************************
372
+ Add defer API to Iodine
373
+ ***************************************************************************** */
374
+
375
+ void iodine_defer_initialize(void) {
376
+ call_id = rb_intern2("call", 4);
377
+ rb_define_module_function(IodineModule, "run", iodine_defer_run, 0);
378
+ rb_define_module_function(IodineModule, "defer", iodine_defer_run, 0);
379
+
380
+ rb_define_module_function(IodineModule, "run_after", iodine_defer_run_after,
381
+ 1);
382
+ rb_define_module_function(IodineModule, "run_every", iodine_defer_run_every,
383
+ -1);
384
+ rb_define_module_function(IodineModule, "before_fork", iodine_before_fork_add,
385
+ 0);
386
+ rb_define_module_function(IodineModule, "after_fork", iodine_after_fork_add,
387
+ 0);
388
+ rb_define_module_function(IodineModule, "on_shutdown", iodine_on_shutdown_add,
389
+ 0);
390
+ defer(iodine_start_io_thread, NULL, NULL);
391
+ }
@@ -0,0 +1,7 @@
1
+ #ifndef H_IODINE_DEFER_H
2
+ #define H_IODINE_DEFER_H
3
+
4
+ void iodine_defer_initialize(void);
5
+ void iodine_defer_on_finish(void);
6
+
7
+ #endif