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,17 +0,0 @@
1
- #ifndef H_IODINE_WEBSOCKETS_H
2
- #define H_IODINE_WEBSOCKETS_H
3
- /*
4
- Copyright: Boaz segev, 2016-2017
5
- License: MIT
6
-
7
- Feel free to copy, use and enjoy according to the license provided.
8
- */
9
- #include "iodine.h"
10
-
11
- #include "http.h"
12
-
13
- void Iodine_init_websocket(void);
14
-
15
- void iodine_upgrade_websocket(http_s *request, VALUE handler);
16
- void iodine_upgrade_sse(http_s *h, VALUE handler);
17
- #endif
@@ -1,156 +0,0 @@
1
- /*
2
- Copyright: Boaz segev, 2016-2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
- */
7
- #include "rb-call.h"
8
- #include <pthread.h>
9
- #include <ruby.h>
10
- #include <ruby/thread.h>
11
-
12
- #if __STDC_VERSION__ < 201112L || __STDC_NO_THREADS__
13
- #define _Thread_local __thread
14
- #endif
15
-
16
- typedef enum {
17
- RUBY_TASK,
18
- C_TASK,
19
- } iodine_task_type_en;
20
-
21
- typedef struct {
22
- iodine_task_type_en type;
23
- VALUE obj;
24
- int argc;
25
- VALUE *argv;
26
- ID method;
27
- int exception;
28
- } iodine_rb_task_s;
29
-
30
- typedef struct {
31
- iodine_task_type_en type;
32
- void *(*func)(void *);
33
- void *arg;
34
- } iodine_c_task_s;
35
-
36
- // running the actual method call
37
- static VALUE iodine_ruby_caller_perform(VALUE tsk_) {
38
- switch (*(iodine_task_type_en *)tsk_) {
39
- case RUBY_TASK: {
40
- iodine_rb_task_s *task = (void *)tsk_;
41
- return rb_funcall2(task->obj, task->method, task->argc, task->argv);
42
- }
43
- case C_TASK: {
44
- iodine_c_task_s *task = (void *)tsk_;
45
- return (VALUE)task->func(task->arg);
46
- }
47
- }
48
- return Qnil;
49
- }
50
-
51
- ////////////////////////////////////////////////////////////////////////////
52
- // Handling exceptions (printing the backtrace doesn't really work well).
53
- static void *handle_exception(void *ignr) {
54
- (void)ignr;
55
- VALUE exc = rb_errinfo();
56
- if (exc != Qnil) {
57
- VALUE msg = RubyCaller.call(exc, rb_intern("message"));
58
- VALUE exc_class = rb_class_name(CLASS_OF(exc));
59
- VALUE bt = RubyCaller.call(exc, rb_intern("backtrace"));
60
- if (TYPE(bt) == T_ARRAY) {
61
- bt = rb_ary_join(bt, rb_str_new_literal("\n"));
62
- fprintf(stderr, "Iodine caught an unprotected exception - %.*s: %.*s\n%s",
63
- (int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class),
64
- (int)RSTRING_LEN(msg), RSTRING_PTR(msg), StringValueCStr(bt));
65
- } else {
66
- fprintf(stderr,
67
- "Iodine caught an unprotected exception - %.*s: %.*s\n"
68
- "No backtrace available.\n",
69
- (int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class),
70
- (int)RSTRING_LEN(msg), RSTRING_PTR(msg));
71
- }
72
- rb_backtrace();
73
- fprintf(stderr, "\n\n");
74
- rb_set_errinfo(Qnil);
75
- }
76
- return (void *)Qnil;
77
- }
78
-
79
- /* wrap the function call in the exception handling code */
80
- static void *iodine_protected_call(void *tsk_) {
81
- int state = 0;
82
- VALUE ret = rb_protect(iodine_ruby_caller_perform, (VALUE)(tsk_), &state);
83
- if (state) {
84
- handle_exception(NULL);
85
- }
86
- return (void *)ret;
87
- }
88
-
89
- ////////////////////////////////////////////////////////////////////////////
90
- // GVL state.
91
-
92
- // a thread specific global variable that lets us know if we're in the GVL
93
- static _Thread_local char in_gvl = 0;
94
- static char iodine_rb_check_in_gvl(void) { return in_gvl; }
95
-
96
- static void iodine_rb_set_gvl_state(char state) { in_gvl = state; }
97
-
98
- ////////////////////////////////////////////////////////////////////////////
99
- // Calling C functions.
100
- static inline void *iodine_rb_call_c(void *(*func)(void *), void *arg) {
101
- if (in_gvl) {
102
- return func(arg);
103
- }
104
- iodine_c_task_s task = {.type = C_TASK, .func = func, .arg = arg};
105
- void *ret;
106
- in_gvl = 1;
107
- ret = rb_thread_call_with_gvl(iodine_protected_call, &task);
108
- in_gvl = 0;
109
- return ret;
110
- }
111
-
112
- static void *iodine_rb_leave_gvl(void *(*func)(void *), void *arg) {
113
- if (!in_gvl) {
114
- return func(arg);
115
- }
116
- in_gvl = 0;
117
- void *ret = rb_thread_call_without_gvl(func, arg, NULL, NULL);
118
- in_gvl = 1;
119
- return ret;
120
- }
121
-
122
- ////////////////////////////////////////////////////////////////////////////
123
- // A heavier (memory) design for when we're passing arguments around.
124
-
125
- // wrapping any API calls for exception management AND GVL entry
126
- static VALUE iodin_rb_call_arg(VALUE obj, ID method, int argc, VALUE *argv) {
127
- iodine_rb_task_s task = {.type = RUBY_TASK,
128
- .obj = obj,
129
- .method = method,
130
- .argc = argc,
131
- .argv = argv};
132
- void *ret;
133
- if (in_gvl) {
134
- return (VALUE)iodine_protected_call(&task);
135
- }
136
- in_gvl = 1;
137
- ret = rb_thread_call_with_gvl(iodine_protected_call, &task);
138
- in_gvl = 0;
139
- return (VALUE)ret;
140
- }
141
-
142
- // wrapping any API calls for exception management AND GVL entry
143
- static VALUE iodin_rb_call(VALUE obj, ID method) {
144
- return iodin_rb_call_arg(obj, method, 0, NULL);
145
- }
146
-
147
- ////////////////////////////////////////////////////////////////////////////
148
- // the API interface
149
- struct _Ruby_Method_Caller_Class_ RubyCaller = {
150
- .call = iodin_rb_call,
151
- .call2 = iodin_rb_call_arg,
152
- .call_c = iodine_rb_call_c,
153
- .leave_gvl = iodine_rb_leave_gvl,
154
- .in_gvl = iodine_rb_check_in_gvl,
155
- .set_gvl_state = iodine_rb_set_gvl_state,
156
- };
@@ -1,70 +0,0 @@
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
- #ifndef RB_CALL_H
8
- #define RB_CALL_H
9
- #include <ruby.h>
10
-
11
- #define RB_CALL_VERSION "0.2.0"
12
-
13
- /**
14
- The Ruby framework manages it's own contextc switching and memory...
15
- this means that when we make calls to Ruby (i.e. creating Ruby objects),
16
- we risk currupting the Ruby framework. Also, Ruby uses all these signals (for
17
- it's context switchings) and long-jumps (i.e. Ruby exceptions) that can drive
18
- the C code a little nuts...
19
-
20
- Seperation is reqiured :-)
21
-
22
- This is a simple helper that calls Ruby methods on Ruby objects and C
23
- functions that require acess to the Ruby API, while managing the GVL lock
24
- status as required.
25
-
26
- To call a Ruby object's method, simply use:
27
-
28
- RubyCaller.call(object, method_id);
29
-
30
- To pass arguments to the ruby object's method, use the `call2` method. i.e.:
31
-
32
- RubyCaller.call2(object, method_id, argc, argv);
33
-
34
-
35
- This library keeps track of the thread, adjusting the method to be called in
36
- case the thread is already within the GVL.
37
-
38
- The library assums that it is within a Ruby thread that is was released of
39
- the GVL using `rb_thread_call_without_gvl`. If this isn t the case, use the
40
- `RubyCaller.set_gvl_state` function to manually set the GVL state assumption.
41
- */
42
- extern struct _Ruby_Method_Caller_Class_ {
43
- /** calls a Object's ruby method, adjusting for the GVL if needed */
44
- VALUE (*call)(VALUE object, ID method_id);
45
- /**
46
- * calls a Object's ruby method with arguments, adjusting for the GVL if
47
- * needed
48
- */
49
- VALUE (*call2)(VALUE obj, ID method, int argc, VALUE *argv);
50
- /**
51
- * calls a C method that requires the GVL for access to the Ruby API, managing
52
- * the GVL state as required.
53
- */
54
- void *(*call_c)(void *(*func)(void *), void *arg);
55
- /**
56
- * Exits the GVL (if the thread is withing the GVL) and calls a C function.
57
- */
58
- void *(*leave_gvl)(void *(*func)(void *), void *arg);
59
- /**
60
- * returns the thread's GVL status (1 if inside a GVL and 0 if free from the
61
- * GVL).
62
- */
63
- char (*in_gvl)(void);
64
- /**
65
- * forces a thread's GVL state flag (ignoring the actual GVL state) - careful!
66
- */
67
- void (*set_gvl_state)(char state);
68
- } RubyCaller;
69
-
70
- #endif /* RB_CALL_H */
@@ -1,124 +0,0 @@
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
- // clang-format off
8
- #include "rb-registry.h"
9
- #include "rb-call.h"
10
- #include "iodine.h"
11
- #include <ruby.h>
12
- #include <ruby/thread.h>
13
-
14
- #include <stdint.h>
15
- // clang-format on
16
-
17
- #include <spnlock.inc>
18
- #include "defer.h"
19
-
20
- #include <pthread.h>
21
-
22
- /* *****************************************************************************
23
- Local helpers
24
- ***************************************************************************** */
25
- /* used to create Ruby threads and pass them the information they need */
26
- struct CreateThreadArgs {
27
- void *(*thread_func)(void *);
28
- void *arg;
29
- };
30
-
31
- /* used here but declared elsewhere */
32
- void call_async_signal(void *pool) { defer_pool_stop((pool_pt)pool); }
33
-
34
- static void *defer_thread_start(void *args_) {
35
- struct CreateThreadArgs *args = args_;
36
- void *(*thread_func)(void *) = args->thread_func;
37
- void *arg = args->arg;
38
- free(args);
39
- RubyCaller.set_gvl_state(0);
40
- thread_func(arg);
41
- return NULL;
42
- }
43
-
44
- /* the thread's GVL release */
45
- static VALUE defer_thread_inGVL(void *args_) {
46
- struct CreateThreadArgs *args = args_;
47
- rb_thread_call_without_gvl(defer_thread_start, args_,
48
- (void (*)(void *))call_async_signal, args->arg);
49
- return Qnil;
50
- }
51
-
52
- /* Within the GVL, creates a Ruby thread using an API call */
53
- static void *create_ruby_thread_gvl(void *args) {
54
- return (void *)Registry.add(rb_thread_create(defer_thread_inGVL, args));
55
- }
56
-
57
- /* used during fork */
58
- void iodine_start_io_thread(void *a1, void *a2);
59
-
60
- static void *fork_using_ruby(void *ignr) {
61
- RubyCaller.call(Iodine, rb_intern("before_fork"));
62
- const VALUE ProcessClass = rb_const_get(rb_cObject, rb_intern("Process"));
63
- const VALUE rb_pid = RubyCaller.call(ProcessClass, rb_intern("fork"));
64
- intptr_t pid = 0;
65
- if (rb_pid != Qnil) {
66
- pid = NUM2INT(rb_pid);
67
- } else {
68
- pid = 0;
69
- }
70
- RubyCaller.set_gvl_state(1); /* enforce GVL state in thread storage */
71
- if (!pid) {
72
- Registry.on_fork();
73
- RubyCaller.call(Iodine, rb_intern("after_fork"));
74
- iodine_start_io_thread(NULL, NULL);
75
- }
76
- return (void *)pid;
77
- (void)ignr;
78
- }
79
-
80
- /* *****************************************************************************
81
- The Defer library overriding functions
82
- ***************************************************************************** */
83
-
84
- /**
85
- OVERRIDE THIS to replace the default pthread implementation.
86
- */
87
- void *defer_new_thread(void *(*thread_func)(void *), void *arg) {
88
- struct CreateThreadArgs *data = malloc(sizeof(*data));
89
- if (!data)
90
- return NULL;
91
- *data = (struct CreateThreadArgs){
92
- .thread_func = thread_func, .arg = arg,
93
- };
94
- void *thr = RubyCaller.call_c(create_ruby_thread_gvl, data);
95
- if (!thr || thr == (void *)Qnil || thr == (void *)Qfalse) {
96
- thr = NULL;
97
- }
98
- return thr;
99
- }
100
-
101
- /**
102
- OVERRIDE THIS to replace the default pthread implementation.
103
- */
104
- int defer_join_thread(void *thr) {
105
- if (!thr || (VALUE)thr == Qfalse || (VALUE)thr == Qnil)
106
- return -1;
107
- RubyCaller.call((VALUE)thr, rb_intern("join"));
108
- Registry.remove((VALUE)thr);
109
- return 0;
110
- }
111
-
112
- // void defer_free_thread(void *thr) { (void)thr; }
113
- void defer_free_thread(void *thr) { Registry.remove((VALUE)thr); }
114
-
115
- /**
116
- OVERRIDE THIS to replace the default `fork` implementation or to inject hooks
117
- into the forking function.
118
-
119
- Behaves like the system's `fork`.
120
- */
121
- int facil_fork(void) {
122
- intptr_t pid = (intptr_t)RubyCaller.call_c(fork_using_ruby, NULL);
123
- return (int)pid;
124
- }
@@ -1,150 +0,0 @@
1
- /*
2
- Copyright: Boaz segev, 2016-2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
- */
7
- #include "rb-registry.h"
8
- #include <ruby.h>
9
-
10
- #include "spnlock.inc"
11
-
12
- #define FIO_OVERRIDE_MALLOC 1
13
- #include "fio_mem.h"
14
-
15
- #include "fio_hashmap.h"
16
- #include <signal.h>
17
-
18
- #ifndef RUBY_REG_DBG
19
- #define RUBY_REG_DBG 0
20
- #endif
21
-
22
- // the registry state keeper
23
- static struct {
24
- fio_hash_s store;
25
- VALUE owner;
26
- spn_lock_i lock;
27
- } registry = {.store = {.capa = 0}, .owner = 0, .lock = SPN_LOCK_INIT};
28
-
29
- #define try_lock_registry() spn_trylock(&registry.lock)
30
- #define unlock_registry() spn_unlock(&registry.lock)
31
- #define lock_registry() spn_lock(&registry.lock)
32
-
33
- /** adds an object to the registry or increases it's reference count. */
34
- static VALUE register_object(VALUE ruby_obj) {
35
- if (!ruby_obj || ruby_obj == Qnil || ruby_obj == Qfalse)
36
- return 0;
37
- lock_registry();
38
- uintptr_t count = (uintptr_t)fio_hash_find(&registry.store, ruby_obj);
39
- #if RUBY_REG_DBG == 1
40
- fprintf(stderr, "Ruby Registry: register %p ref: %" PRIu64 " + 1\n",
41
- (void *)ruby_obj, (uint64_t)count);
42
- #endif
43
- fio_hash_insert(&registry.store, (uint64_t)ruby_obj, (void *)(count + 1));
44
- unlock_registry();
45
- return ruby_obj;
46
- }
47
-
48
- /** decreases an object's reference count or removes if from the registry. */
49
- static void unregister_object(VALUE ruby_obj) {
50
- if (!ruby_obj || ruby_obj == Qnil)
51
- return;
52
- lock_registry();
53
- uintptr_t count = (uintptr_t)fio_hash_find(&registry.store, ruby_obj);
54
- #if RUBY_REG_DBG == 1
55
- fprintf(stderr, "Ruby Registry: unregister %p ref: %" PRIu64 " - 1\n",
56
- (void *)ruby_obj, (uint64_t)count);
57
- #endif
58
- if (count) {
59
- fio_hash_insert(&registry.store, (uint64_t)ruby_obj, (void *)(count - 1));
60
- }
61
- unlock_registry();
62
- }
63
-
64
- /* a callback for the GC (marking active objects) */
65
- static void registry_mark(void *ignore) {
66
- (void)ignore;
67
- #if RUBY_REG_DBG == 1
68
- Registry.print();
69
- #endif
70
- lock_registry();
71
- fio_hash_compact(&registry.store);
72
- FIO_HASH_FOR_LOOP(&registry.store, pos) {
73
- if (pos->obj) {
74
- rb_gc_mark((VALUE)pos->key);
75
- }
76
- }
77
- unlock_registry();
78
- }
79
-
80
- /* clear the registry (end of lifetime) */
81
- static void registry_clear(void *ignore) {
82
- (void)ignore;
83
- #if RUBY_REG_DBG == 1
84
- fprintf(stderr, "Ruby Registry: Clear!!!\n");
85
- #endif
86
- lock_registry();
87
- fio_hash_free(&registry.store);
88
- registry.owner = 0;
89
- registry.store = (fio_hash_s){.capa = 0};
90
- unlock_registry();
91
- }
92
-
93
- /*
94
- the data-type used to identify the registry
95
- this sets the callbacks.
96
- */
97
- static struct rb_data_type_struct my_registry_type_struct = {
98
- .wrap_struct_name = "RubyReferencesIn_C_Land",
99
- .function.dfree = (void (*)(void *))registry_clear,
100
- .function.dmark = (void (*)(void *))registry_mark,
101
- };
102
-
103
- /* initialize the registry */
104
- static void init(VALUE owner) {
105
- lock_registry();
106
- // only one registry
107
- if (registry.owner)
108
- goto finish;
109
- if (!owner)
110
- owner = rb_cObject;
111
- registry.owner = owner;
112
- VALUE rReferences =
113
- rb_define_class_under(owner, "RubyObjectRegistry_for_C_land", rb_cData);
114
- VALUE r_registry =
115
- TypedData_Wrap_Struct(rReferences, &my_registry_type_struct, &registry);
116
- rb_ivar_set(owner, rb_intern("registry"), r_registry);
117
- finish:
118
- unlock_registry();
119
- }
120
-
121
- static void registry_on_fork(void) { unlock_registry(); }
122
-
123
- /* print data, for testing */
124
- static void print(void) {
125
- lock_registry();
126
- fprintf(stderr, "Registry owner is %lu\n", registry.owner);
127
- uintptr_t index = 0;
128
- FIO_HASH_FOR_LOOP(&registry.store, pos) {
129
- if (pos->obj) {
130
- fprintf(stderr, "[%" PRIuPTR " ] => %" PRIuPTR " X obj %p type %d\n",
131
- index++, (uintptr_t)pos->obj, (void *)pos->key, TYPE(pos->key));
132
- }
133
- }
134
- fprintf(stderr, "Total of %" PRIuPTR " registered objects being marked\n",
135
- index);
136
- fprintf(stderr,
137
- "Registry uses %" PRIuPTR " Hash bins for %" PRIuPTR " objects\n",
138
- registry.store.capa, registry.store.count);
139
- unlock_registry();
140
- }
141
-
142
- ////////////////////////////////////////////
143
- // The API gateway
144
- struct ___RegistryClass___ Registry = {
145
- .init = init,
146
- .remove = unregister_object,
147
- .add = register_object,
148
- .print = print,
149
- .on_fork = registry_on_fork,
150
- };