iodine 0.2.4 → 0.2.5

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5dd781b994de37f715d28a0bb3d2116d0130ec7
4
- data.tar.gz: ef6e1fb31118a909c479c5b3a5eca55984794cfa
3
+ metadata.gz: 765b6fa27478c971c926fe4010ebde8c95d970a3
4
+ data.tar.gz: 740963e010629bb812d6de4e4167375bed0b0bf3
5
5
  SHA512:
6
- metadata.gz: dcc16cec26d57a181e7883fef1772abff857f07c5d20ef7900f3ea1c63e953061567185d0456be14b6dfa8e827f7637720555455e38b5146ddcba26ca2d41eee
7
- data.tar.gz: 1ca3bb3ccd3d45ad069e9093af6cecdd73c08ccb9b6018394f4ca1467cc7ae157cdcee248da32c5f5e82e91f5042a98d6173f10eeecf073225415c67bf9c7d97
6
+ metadata.gz: 9ebd38a0c3442d787af596ca73a26481673697727e891026355722a33e897d3a631c4569669bfb041c61653df0c2248a3b51a20780002c9603e36fa538ea67e0
7
+ data.tar.gz: 2e31973b2bc6ec931001c8cdac8a09bd3e3d3ac6fb74e8c7749d80f0daf4419057f95db0151c3205eb441c4a3ac07c3938ea5e9a52c98944ea1b39c8a86328fc
@@ -5,9 +5,10 @@ os:
5
5
  before_install:
6
6
  - gem install bundler -v 1.10.6
7
7
  rvm:
8
- - 2.2.4
9
- - 2.3.0
8
+ - ruby-2.4.0-rc1
10
9
  - 2.3.1
10
+ - 2.3.0
11
+ - 2.2.4
11
12
  - 2.2.2
12
13
  notifications:
13
14
  email: false
@@ -8,6 +8,14 @@ Please notice that this change log contains changes for upcoming releases as wel
8
8
 
9
9
  ***
10
10
 
11
+ Change log v.0.2.5
12
+
13
+ **Fix:**: fix for issue #9 (credit to Jack Christensen for exposing the issue) caused by an unlocked critical section's "window of opportunity" that allowed asynchronous Websocket `each` blocks to run during the tail of the Websocket handshake (while the `on_open` callback was running in parallel).
14
+
15
+ **Minor Fix**: Fix Iodine::Rack's startup message's `fprint` call to fit correct argument sizes (Linux warnings).
16
+
17
+ ***
18
+
11
19
  Change log v.0.2.4
12
20
 
13
21
  **Minor Fix**: Patched Iodine against Apple's broken `getrlimit` on macOS. This allows correct auto-setting of open file limits for the socket layer.
@@ -60,11 +60,11 @@ Server settings **MAY** (not required) be provided to allow for customization an
60
60
 
61
61
  ## Upgrading
62
62
 
63
- * **Server**: When an upgrade request is received, the server will set the `env['upgrade.websockets?']` flag to `true`, indicating that: 1. this specific request is upgradable; and 2. this server supports specification.
63
+ * **Server**: When an upgrade request is received, the server will set the `env['upgrade.websocket?']` flag to `true`, indicating that: 1. this specific request is upgradable; and 2. this server supports specification.
64
64
 
65
- * **Client**: When a client decides to upgrade a request, they will place a Websocket Callback Object (either a class or an instance) in the `env['upgrade.websockets']` Hash key.
65
+ * **Client**: When a client decides to upgrade a request, they will place a Websocket Callback Object (either a class or an instance) in the `env['upgrade.websocket']` Hash key.
66
66
 
67
- * **Server**: The server will review the `env` Hash *before* sending the response. If the `env['upgrade.websockets']` was set, the server will perform the upgrade.
67
+ * **Server**: The server will review the `env` Hash *before* sending the response. If the `env['upgrade.websocket']` was set, the server will perform the upgrade.
68
68
 
69
69
  * **Server**: The server will send the correct response status and headers, as will as any headers present in the response. The server will also perform any required housekeeping, such as closing the response body, if exists.
70
70
 
@@ -553,22 +553,22 @@ int iodine_http_review(void) {
553
553
  if (public_folder)
554
554
  fprintf(stderr, "Starting up Iodine Http Server:\n"
555
555
  " * Ruby v.%s\n * Iodine v.%s \n"
556
- " * %d processes X %d thread%s\n"
557
- " * %d max concurrent connections / open files\n"
556
+ " * %lu processes X %lu thread%s\n"
557
+ " * %lu max concurrent connections / open files\n"
558
558
  " * Serving static files from:\n"
559
559
  " %s\n\n",
560
560
  StringValueCStr(ruby_version), StringValueCStr(iodine_version),
561
- processes, threads, (threads > 1 ? "s" : ""), sock_max_capacity(),
562
- public_folder);
561
+ (size_t)processes, (size_t)threads, (threads > 1 ? "s" : ""),
562
+ (size_t)sock_max_capacity(), public_folder);
563
563
  else
564
564
  fprintf(stderr, "Starting up Iodine Http Server:\n"
565
565
  " * Ruby v.%s\n * Iodine v.%s \n"
566
- " * %d processes X %d thread%s\n"
567
- " * %d max concurrent connections / open files\n"
566
+ " * %lu processes X %lu thread%s\n"
567
+ " * %lu max concurrent connections / open files\n"
568
568
  "\n",
569
569
  StringValueCStr(ruby_version), StringValueCStr(iodine_version),
570
- processes, threads, (threads > 1 ? "s" : ""),
571
- sock_max_capacity());
570
+ (size_t)processes, (size_t)threads, (threads > 1 ? "s" : ""),
571
+ (size_t)sock_max_capacity());
572
572
 
573
573
  // listen
574
574
  return http1_listen(port, address, .on_request = on_rack_request,
@@ -1,10 +1,10 @@
1
+ #include "iodine_websocket.h"
1
2
  #include "iodine_core.h"
2
3
  #include "iodine_http.h"
3
- #include "iodine_websocket.h"
4
4
  #include "rb-call.h"
5
5
  #include "rb-registry.h"
6
- #include <ruby/io.h>
7
6
  #include <arpa/inet.h>
7
+ #include <ruby/io.h>
8
8
 
9
9
  /* *****************************************************************************
10
10
  Core helpers and data
@@ -31,6 +31,8 @@ inline static intptr_t get_uuid(VALUE obj) {
31
31
 
32
32
  inline static ws_s *get_ws(VALUE obj) {
33
33
  VALUE i = rb_ivar_get(obj, ws_var_id);
34
+ if (i == Qnil)
35
+ return NULL;
34
36
  return (ws_s *)FIX2ULONG(i);
35
37
  }
36
38
 
@@ -126,8 +128,14 @@ static VALUE iodine_ws_close(VALUE self) {
126
128
  * global `write` buffer is full, `write` will block until a buffer "packet"
127
129
  * becomes available and can be assigned to the socket. */
128
130
  static VALUE iodine_ws_write(VALUE self, VALUE data) {
131
+ Check_Type(data, T_STRING);
129
132
  ws_s *ws = get_ws(self);
130
- if (((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
133
+ // if ((void *)ws == (void *)0x04 || (void *)data == (void *)0x04 ||
134
+ // RSTRING_PTR(data) == (void *)0x04)
135
+ // fprintf(stderr, "iodine_ws_write: self = %p ; data = %p\n"
136
+ // "\t\tString ptr: %p, String length: %lu\n",
137
+ // (void *)ws, (void *)data, RSTRING_PTR(data), RSTRING_LEN(data));
138
+ if (!ws || ((protocol_s *)ws)->service != WEBSOCKET_ID_STR)
131
139
  return Qfalse;
132
140
  websocket_write(ws, RSTRING_PTR(data), RSTRING_LEN(data),
133
141
  rb_enc_get(data) == UTF8Encoding);
@@ -252,6 +260,11 @@ i.e.:
252
260
  msg = data.dup; # data will be overwritten once the function exists.
253
261
  each {|ws| ws.write msg}
254
262
  end
263
+
264
+
265
+ The block of code will be executed asynchronously, to avoid having two blocks
266
+ of code running at the same time and minimizing race conditions when using
267
+ multilple threads.
255
268
  */
256
269
  static VALUE iodine_ws_each(VALUE self) {
257
270
  // requires a block to be passed
@@ -268,7 +281,8 @@ static VALUE iodine_ws_each(VALUE self) {
268
281
  /**
269
282
  Runs the required block for each dynamic protocol connection.
270
283
 
271
- Tasks will be performed within each connections lock, so no connection will have
284
+ Tasks will be performed asynchronously, within each connections lock, so no
285
+ connection will have
272
286
  more then one task being performed at the same time (similar to {#defer}).
273
287
 
274
288
  Also, unlike {Iodine.run}, the block will **not** be called unless the
@@ -425,8 +439,8 @@ static VALUE empty_func(VALUE self) { return Qnil; }
425
439
  // initialize the class and the whole of the Iodine/http library
426
440
  void Init_iodine_websocket(void) {
427
441
  // get IDs and data that's used often
428
- ws_var_id = rb_intern("ws_ptr"); // when upgrading
429
- dup_func_id = rb_intern("dup"); // when upgrading
442
+ ws_var_id = rb_intern("iodine_ws_ptr"); // when upgrading
443
+ dup_func_id = rb_intern("dup"); // when upgrading
430
444
 
431
445
  // the Ruby websockets protocol class.
432
446
  rWebsocket = rb_define_module_under(Iodine, "Websocket");
@@ -11,17 +11,17 @@ Feel free to copy, use and enjoy according to the license provided.
11
11
 
12
12
  #include "libasync.h"
13
13
 
14
- #include <stdlib.h>
15
- #include <stdio.h>
16
14
  #include <errno.h>
17
- #include <signal.h>
18
- #include <unistd.h>
19
15
  #include <execinfo.h>
20
- #include <pthread.h>
21
16
  #include <fcntl.h>
17
+ #include <pthread.h>
22
18
  #include <sched.h>
23
- #include <sys/mman.h>
19
+ #include <signal.h>
20
+ #include <stdio.h>
21
+ #include <stdlib.h>
24
22
  #include <string.h>
23
+ #include <sys/mman.h>
24
+ #include <unistd.h>
25
25
 
26
26
  /* *****************************************************************************
27
27
  Performance options.
@@ -43,7 +43,7 @@ Performance options.
43
43
 
44
44
  /* Sentinal thread to respawn crashed threads - limited crash resistance. */
45
45
  #ifndef ASYNC_USE_SENTINEL
46
- #define ASYNC_USE_SENTINEL 0
46
+ #define ASYNC_USE_SENTINEL 1
47
47
  #endif
48
48
 
49
49
  /* *****************************************************************************
@@ -457,11 +457,13 @@ Test
457
457
 
458
458
  #define ASYNC_SPEED_TEST_THREAD_COUNT 120
459
459
 
460
- static size_t _Atomic i_count = 0;
460
+ static spn_lock_i i_lock = SPN_LOCK_INIT;
461
+ static size_t i_count = 0;
461
462
 
462
463
  static void sample_task(void *_) {
463
- __asm__ volatile("" ::: "memory");
464
- atomic_fetch_add(&i_count, 1);
464
+ spn_lock(&i_lock);
465
+ i_count++;
466
+ spn_unlock(&i_lock);
465
467
  }
466
468
 
467
469
  static void sched_sample_task(void *_) {
@@ -471,8 +473,9 @@ static void sched_sample_task(void *_) {
471
473
  }
472
474
 
473
475
  static void text_task_text(void *_) {
474
- __asm__ volatile("" ::: "memory");
476
+ spn_lock(&i_lock);
475
477
  fprintf(stderr, "this text should print before async_finish returns\n");
478
+ spn_unlock(&i_lock);
476
479
  }
477
480
 
478
481
  static void text_task(void *_) {
@@ -491,7 +494,9 @@ static void evil_task(void *_) {
491
494
  #endif
492
495
 
493
496
  void async_test_library_speed(void) {
494
- atomic_store(&i_count, 0);
497
+ spn_lock(&i_lock);
498
+ i_count = 0;
499
+ spn_unlock(&i_lock);
495
500
  time_t start, end;
496
501
  fprintf(stderr, "Starting Async testing\n");
497
502
  if (async_start(ASYNC_SPEED_TEST_THREAD_COUNT) == 0) {
@@ -506,7 +511,7 @@ void async_test_library_speed(void) {
506
511
  async_finish();
507
512
  end = clock();
508
513
  fprintf(stderr, "Async performance test %lu cycles with i_count = %lu\n",
509
- end - start, atomic_load(&i_count));
514
+ end - start, i_count);
510
515
  } else {
511
516
  fprintf(stderr, "Async test couldn't be initialized\n");
512
517
  exit(-1);
@@ -8,12 +8,12 @@ Feel free to copy, use and enjoy according to the license provided.
8
8
  #define _GNU_SOURCE
9
9
  #endif
10
10
  #include "libserver.h"
11
- #include <string.h>
12
- #include <signal.h>
11
+ #include <errno.h>
13
12
  #include <pthread.h>
13
+ #include <signal.h>
14
+ #include <string.h>
14
15
  #include <sys/mman.h>
15
16
  #include <sys/wait.h>
16
- #include <errno.h>
17
17
 
18
18
  /* *****************************************************************************
19
19
  Connection Data
@@ -1,18 +1,15 @@
1
1
  #include "rb-call.h"
2
+ #include <pthread.h>
2
3
  #include <ruby.h>
3
4
  #include <ruby/thread.h>
4
- #include <pthread.h>
5
+
6
+ #if __STDC_VERSION__ < 201112L || __STDC_NO_THREADS__
7
+ #define _Thread_local __thread
8
+ #endif
5
9
 
6
10
  ///////////////
7
11
  // this is a simple helper that calls Ruby methods on Ruby objects while within
8
12
  // a non-GVL ruby thread zone.
9
-
10
- // a structure for Ruby API calls
11
- struct RubySimpleCall {
12
- VALUE obj;
13
- VALUE returned;
14
- ID method;
15
- };
16
13
  struct RubyArgCall {
17
14
  VALUE obj;
18
15
  int argc;
@@ -21,25 +18,10 @@ struct RubyArgCall {
21
18
  ID method;
22
19
  };
23
20
 
24
- #if __STDC_VERSION__ < 201112L || __STDC_NO_THREADS__
25
- #define _Thread_local __thread
26
- #endif
27
-
28
- // a thread specific global variable that lets us know if we're in the GVL
29
- static _Thread_local char in_gvl = 0;
30
- static char check_in_gvl(void) { return in_gvl; }
31
-
32
- ////////////////////////////////////////////////////////////////////////////
33
- // Calling C functions.
34
- static void *call_c(void *(*func)(void *), void *arg) {
35
- if (in_gvl) {
36
- return func(arg);
37
- }
38
- void *ret;
39
- in_gvl = 1;
40
- ret = rb_thread_call_with_gvl(func, arg);
41
- in_gvl = 0;
42
- return ret;
21
+ // running the actual method call
22
+ static VALUE run_ruby_method_unsafe(VALUE _tsk) {
23
+ struct RubyArgCall *task = (void *)_tsk;
24
+ return rb_funcall2(task->obj, task->method, task->argc, task->argv);
43
25
  }
44
26
 
45
27
  ////////////////////////////////////////////////////////////////////////////
@@ -67,18 +49,9 @@ static void *handle_exception(void *_) {
67
49
  return (void *)Qnil;
68
50
  }
69
51
 
70
- ////////////////////////////////////////////////////////////////////////////
71
- // A simple (and a bit lighter) design for when there's no need for arguments.
72
-
73
- // running the actual method call
74
- static VALUE run_ruby_method_unsafe(VALUE _tsk) {
75
- struct RubySimpleCall *task = (void *)_tsk;
76
- return rb_funcall2(task->obj, task->method, 0, NULL);
77
- }
78
-
79
52
  // GVL gateway
80
53
  static void *run_ruby_method_within_gvl(void *_tsk) {
81
- struct RubySimpleCall *task = _tsk;
54
+ struct RubyArgCall *task = _tsk;
82
55
  int state = 0;
83
56
  task->returned = rb_protect(run_ruby_method_unsafe, (VALUE)(task), &state);
84
57
  if (state)
@@ -86,9 +59,32 @@ static void *run_ruby_method_within_gvl(void *_tsk) {
86
59
  return task;
87
60
  }
88
61
 
62
+ ////////////////////////////////////////////////////////////////////////////
63
+ // GVL state.
64
+
65
+ // a thread specific global variable that lets us know if we're in the GVL
66
+ static _Thread_local char in_gvl = 0;
67
+ static char check_in_gvl(void) { return in_gvl; }
68
+
69
+ ////////////////////////////////////////////////////////////////////////////
70
+ // Calling C functions.
71
+ static void *call_c(void *(*func)(void *), void *arg) {
72
+ if (in_gvl) {
73
+ return func(arg);
74
+ }
75
+ void *ret;
76
+ in_gvl = 1;
77
+ ret = rb_thread_call_with_gvl(func, arg);
78
+ in_gvl = 0;
79
+ return ret;
80
+ }
81
+
82
+ ////////////////////////////////////////////////////////////////////////////
83
+ // A simple (and a bit lighter) design for when there's no need for arguments.
84
+
89
85
  // wrapping any API calls for exception management AND GVL entry
90
86
  static VALUE call(VALUE obj, ID method) {
91
- struct RubySimpleCall task = {.obj = obj, .method = method};
87
+ struct RubyArgCall task = {.obj = obj, .method = method};
92
88
  call_c(run_ruby_method_within_gvl, &task);
93
89
  return task.returned;
94
90
  }
@@ -96,32 +92,20 @@ static VALUE call(VALUE obj, ID method) {
96
92
  ////////////////////////////////////////////////////////////////////////////
97
93
  // A heavier (memory) design for when we're passing arguments around.
98
94
 
99
- // running the actual method call
100
- static VALUE run_argv_method_unsafe(VALUE _tsk) {
101
- struct RubyArgCall *task = (void *)_tsk;
102
- return rb_funcall2(task->obj, task->method, task->argc, task->argv);
103
- }
104
-
105
- // GVL gateway
106
- static void *run_argv_method_within_gvl(void *_tsk) {
107
- struct RubyArgCall *task = _tsk;
108
- int state = 0;
109
- task->returned = rb_protect(run_argv_method_unsafe, (VALUE)(task), &state);
110
- if (state)
111
- handle_exception(NULL);
112
- return task;
113
- }
114
-
115
95
  // wrapping any API calls for exception management AND GVL entry
116
96
  static VALUE call_arg(VALUE obj, ID method, int argc, VALUE *argv) {
117
97
  struct RubyArgCall task = {
118
98
  .obj = obj, .method = method, .argc = argc, .argv = argv};
119
- call_c(run_argv_method_within_gvl, &task);
99
+ call_c(run_ruby_method_within_gvl, &task);
120
100
  return task.returned;
121
101
  }
122
102
 
123
103
  ////////////////////////////////////////////////////////////////////////////
124
104
  // the API interface
125
105
  struct _Ruby_Method_Caller_Class_ RubyCaller = {
126
- .call = call, .call2 = call_arg, .call_c = call_c, .in_gvl = check_in_gvl,
106
+ .call = call,
107
+ .call2 = call_arg,
108
+ .call_c = call_c,
109
+ // .leave_gvl = leave_gvl,
110
+ .in_gvl = check_in_gvl,
127
111
  };
@@ -0,0 +1,127 @@
1
+ #include "rb-call.h"
2
+ #include <ruby.h>
3
+ #include <ruby/thread.h>
4
+ #include <pthread.h>
5
+
6
+ ///////////////
7
+ // this is a simple helper that calls Ruby methods on Ruby objects while within
8
+ // a non-GVL ruby thread zone.
9
+
10
+ // a structure for Ruby API calls
11
+ struct RubySimpleCall {
12
+ VALUE obj;
13
+ VALUE returned;
14
+ ID method;
15
+ };
16
+ struct RubyArgCall {
17
+ VALUE obj;
18
+ int argc;
19
+ VALUE *argv;
20
+ VALUE returned;
21
+ ID method;
22
+ };
23
+
24
+ #if __STDC_VERSION__ < 201112L || __STDC_NO_THREADS__
25
+ #define _Thread_local __thread
26
+ #endif
27
+
28
+ // a thread specific global variable that lets us know if we're in the GVL
29
+ static _Thread_local char in_gvl = 0;
30
+ static char check_in_gvl(void) { return in_gvl; }
31
+
32
+ ////////////////////////////////////////////////////////////////////////////
33
+ // Calling C functions.
34
+ static void *call_c(void *(*func)(void *), void *arg) {
35
+ if (in_gvl) {
36
+ return func(arg);
37
+ }
38
+ void *ret;
39
+ in_gvl = 1;
40
+ ret = rb_thread_call_with_gvl(func, arg);
41
+ in_gvl = 0;
42
+ return ret;
43
+ }
44
+
45
+ ////////////////////////////////////////////////////////////////////////////
46
+ // Handling exceptions (printing the backtrace doesn't really work well).
47
+ static void *handle_exception(void *_) {
48
+ VALUE exc = rb_errinfo();
49
+ if (exc != Qnil) {
50
+ VALUE msg = RubyCaller.call(exc, rb_intern("message"));
51
+ VALUE exc_class = rb_class_name(CLASS_OF(exc));
52
+ VALUE bt = RubyCaller.call(exc, rb_intern("backtrace"));
53
+ if (TYPE(bt) == T_ARRAY) {
54
+ bt = rb_ary_join(bt, rb_str_new_literal("\n"));
55
+ fprintf(stderr, "Iodine caught an unprotected exception - %.*s: %.*s\n%s",
56
+ (int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class),
57
+ (int)RSTRING_LEN(msg), RSTRING_PTR(msg), StringValueCStr(bt));
58
+ } else {
59
+ fprintf(stderr, "Iodine caught an unprotected exception - %.*s: %.*s\n"
60
+ "No backtrace available.\n",
61
+ (int)RSTRING_LEN(exc_class), RSTRING_PTR(exc_class),
62
+ (int)RSTRING_LEN(msg), RSTRING_PTR(msg));
63
+ }
64
+ rb_backtrace();
65
+ rb_set_errinfo(Qnil);
66
+ }
67
+ return (void *)Qnil;
68
+ }
69
+
70
+ ////////////////////////////////////////////////////////////////////////////
71
+ // A simple (and a bit lighter) design for when there's no need for arguments.
72
+
73
+ // running the actual method call
74
+ static VALUE run_ruby_method_unsafe(VALUE _tsk) {
75
+ struct RubySimpleCall *task = (void *)_tsk;
76
+ return rb_funcall2(task->obj, task->method, 0, NULL);
77
+ }
78
+
79
+ // GVL gateway
80
+ static void *run_ruby_method_within_gvl(void *_tsk) {
81
+ struct RubySimpleCall *task = _tsk;
82
+ int state = 0;
83
+ task->returned = rb_protect(run_ruby_method_unsafe, (VALUE)(task), &state);
84
+ if (state)
85
+ handle_exception(NULL);
86
+ return task;
87
+ }
88
+
89
+ // wrapping any API calls for exception management AND GVL entry
90
+ static VALUE call(VALUE obj, ID method) {
91
+ struct RubySimpleCall task = {.obj = obj, .method = method};
92
+ call_c(run_ruby_method_within_gvl, &task);
93
+ return task.returned;
94
+ }
95
+
96
+ ////////////////////////////////////////////////////////////////////////////
97
+ // A heavier (memory) design for when we're passing arguments around.
98
+
99
+ // running the actual method call
100
+ static VALUE run_argv_method_unsafe(VALUE _tsk) {
101
+ struct RubyArgCall *task = (void *)_tsk;
102
+ return rb_funcall2(task->obj, task->method, task->argc, task->argv);
103
+ }
104
+
105
+ // GVL gateway
106
+ static void *run_argv_method_within_gvl(void *_tsk) {
107
+ struct RubyArgCall *task = _tsk;
108
+ int state = 0;
109
+ task->returned = rb_protect(run_argv_method_unsafe, (VALUE)(task), &state);
110
+ if (state)
111
+ handle_exception(NULL);
112
+ return task;
113
+ }
114
+
115
+ // wrapping any API calls for exception management AND GVL entry
116
+ static VALUE call_arg(VALUE obj, ID method, int argc, VALUE *argv) {
117
+ struct RubyArgCall task = {
118
+ .obj = obj, .method = method, .argc = argc, .argv = argv};
119
+ call_c(run_argv_method_within_gvl, &task);
120
+ return task.returned;
121
+ }
122
+
123
+ ////////////////////////////////////////////////////////////////////////////
124
+ // the API interface
125
+ struct _Ruby_Method_Caller_Class_ RubyCaller = {
126
+ .call = call, .call2 = call_arg, .call_c = call_c, .in_gvl = check_in_gvl,
127
+ };
@@ -1,11 +1,20 @@
1
1
  #include "rb-registry.h"
2
- #include <ruby.h>
3
2
  #include "spnlock.h"
3
+ #include <ruby.h>
4
4
 
5
5
  // #define RUBY_REG_DBG
6
6
 
7
+ #define REGISTRY_POOL_SIZE 1024
8
+ // the references struct (bin-tree)
9
+ struct Object {
10
+ struct Object *next;
11
+ VALUE obj;
12
+ int count;
13
+ };
14
+
7
15
  // the registry global
8
16
  static struct Registry {
17
+ struct Object pool_mem[REGISTRY_POOL_SIZE];
9
18
  struct Object *obj_pool;
10
19
  struct Object *first;
11
20
  VALUE owner;
@@ -17,28 +26,14 @@ static struct Registry {
17
26
  #define unlock_registry() spn_unlock(&registry.lock)
18
27
  #define lock_registry() spn_lock(&registry.lock)
19
28
 
20
- // the references struct (bin-tree)
21
- struct Object {
22
- struct Object *next;
23
- VALUE obj;
24
- int count;
25
- };
26
-
27
- // manage existing objects - add a reference
28
- int add_reference(VALUE obj) {
29
- struct Object *line;
30
- lock_registry();
31
- line = registry.first;
32
- while (line) {
33
- if (line->obj == obj) {
34
- line->count++;
35
- unlock_registry();
36
- return 1;
37
- }
38
- line = line->next;
29
+ inline static void free_node(struct Object *to_free) {
30
+ if (to_free >= registry.pool_mem &&
31
+ (intptr_t)to_free <= (intptr_t)(&registry.obj_pool)) {
32
+ to_free->next = registry.obj_pool;
33
+ registry.obj_pool = to_free;
34
+ } else {
35
+ free(to_free);
39
36
  }
40
- unlock_registry();
41
- return 0;
42
37
  }
43
38
 
44
39
  // add an object to the registry
@@ -47,25 +42,30 @@ int add_reference(VALUE obj) {
47
42
  static VALUE register_object(VALUE obj) {
48
43
  if (!obj || obj == Qnil)
49
44
  return 0;
50
- if (add_reference(obj))
51
- return obj;
52
- struct Object *line;
45
+ struct Object *line = registry.first;
53
46
  lock_registry();
47
+ while (line) {
48
+ if (line->obj == obj) {
49
+ line->count++;
50
+ goto finish;
51
+ }
52
+ line = line->next;
53
+ }
54
54
  if (registry.obj_pool) {
55
55
  line = registry.obj_pool;
56
56
  registry.obj_pool = registry.obj_pool->next;
57
57
  } else {
58
58
  line = malloc(sizeof(struct Object));
59
59
  }
60
- if (!line) {
61
- perror("No Memory");
62
- unlock_registry();
63
- return 0;
60
+ if (line == NULL) {
61
+ perror("No Memory!");
62
+ exit(1);
64
63
  }
65
64
  line->obj = obj;
66
65
  line->next = registry.first;
67
66
  line->count = 1;
68
67
  registry.first = line;
68
+ finish:
69
69
  unlock_registry();
70
70
  return obj;
71
71
  }
@@ -77,24 +77,18 @@ static void unregister_object(VALUE obj) {
77
77
  if (!obj || obj == Qnil)
78
78
  return;
79
79
  lock_registry();
80
- struct Object *line = registry.first;
81
- struct Object *prev = NULL;
82
- while (line) {
83
- if (line->obj == obj) {
84
- line->count--;
85
- if (!line->count) {
86
- if (line == registry.first)
87
- registry.first = line->next;
88
- else if (prev) // must be true, really
89
- prev->next = line->next;
90
- // move the object container to the discarded object pool
91
- line->next = registry.obj_pool;
92
- registry.obj_pool = line;
80
+ struct Object **line = &registry.first;
81
+ while (*line) {
82
+ if ((*line)->obj == obj) {
83
+ (*line)->count -= 1;
84
+ if ((*line)->count <= 0) {
85
+ struct Object *to_free = *line;
86
+ *line = (*line)->next;
87
+ free_node(to_free);
88
+ goto finish;
93
89
  }
94
- goto finish;
95
90
  }
96
- prev = line;
97
- line = line->next;
91
+ line = &((*line)->next);
98
92
  }
99
93
  finish:
100
94
  unlock_registry();
@@ -148,17 +142,9 @@ static void registry_clear(void *ignore) {
148
142
  while (line) {
149
143
  to_free = line;
150
144
  line = line->next;
151
- free(to_free);
145
+ free_node(to_free);
152
146
  }
153
147
  registry.first = NULL;
154
- // free container pool
155
- line = registry.obj_pool;
156
- while (line) {
157
- to_free = line;
158
- line = line->next;
159
- free(to_free);
160
- }
161
- registry.obj_pool = NULL;
162
148
  registry.owner = 0;
163
149
  unlock_registry();
164
150
  }
@@ -174,6 +160,7 @@ static struct rb_data_type_struct my_registry_type_struct = {
174
160
  // initialize the registry
175
161
  static void init(VALUE owner) {
176
162
  lock_registry();
163
+ // only one registry
177
164
  if (registry.owner)
178
165
  goto finish;
179
166
  if (!owner)
@@ -184,6 +171,12 @@ static void init(VALUE owner) {
184
171
  VALUE r_registry =
185
172
  TypedData_Wrap_Struct(rReferences, &my_registry_type_struct, &registry);
186
173
  rb_ivar_set(owner, rb_intern("registry"), r_registry);
174
+ // initialize memory pool
175
+ for (size_t i = 0; i < REGISTRY_POOL_SIZE - 1; i++) {
176
+ registry.pool_mem[i].next = registry.pool_mem + i + 1;
177
+ }
178
+ registry.pool_mem[REGISTRY_POOL_SIZE - 1].next = NULL;
179
+ registry.obj_pool = registry.pool_mem;
187
180
  finish:
188
181
  unlock_registry();
189
182
  }
@@ -0,0 +1,213 @@
1
+ #include "rb-registry.h"
2
+ #include "spnlock.h"
3
+ #include <ruby.h>
4
+
5
+ // #define RUBY_REG_DBG
6
+
7
+ // the registry global
8
+ static struct Registry {
9
+ struct Object *obj_pool;
10
+ struct Object *first;
11
+ VALUE owner;
12
+ spn_lock_i lock;
13
+ } registry = {
14
+ .obj_pool = NULL, .first = NULL, .owner = 0, .lock = SPN_LOCK_INIT};
15
+
16
+ #define try_lock_registry() spn_trylock(&registry.lock)
17
+ #define unlock_registry() spn_unlock(&registry.lock)
18
+ #define lock_registry() spn_lock(&registry.lock)
19
+
20
+ // the references struct (bin-tree)
21
+ struct Object {
22
+ struct Object *next;
23
+ VALUE obj;
24
+ int count;
25
+ };
26
+
27
+ // manage existing objects - add a reference
28
+ int add_reference(VALUE obj) {
29
+ struct Object *line;
30
+ lock_registry();
31
+ line = registry.first;
32
+ while (line) {
33
+ if (line->obj == obj) {
34
+ line->count++;
35
+ unlock_registry();
36
+ return 1;
37
+ }
38
+ line = line->next;
39
+ }
40
+ unlock_registry();
41
+ return 0;
42
+ }
43
+
44
+ // add an object to the registry
45
+ //
46
+ // allow multiple registrartions (bag)
47
+ static VALUE register_object(VALUE obj) {
48
+ if (!obj || obj == Qnil)
49
+ return 0;
50
+ if (add_reference(obj))
51
+ return obj;
52
+ struct Object *line;
53
+ lock_registry();
54
+ if (registry.obj_pool) {
55
+ line = registry.obj_pool;
56
+ registry.obj_pool = registry.obj_pool->next;
57
+ } else {
58
+ line = malloc(sizeof(struct Object));
59
+ }
60
+ if (!line) {
61
+ perror("No Memory!");
62
+ unlock_registry();
63
+ return 0;
64
+ }
65
+ line->obj = obj;
66
+ line->next = registry.first;
67
+ line->count = 1;
68
+ registry.first = line;
69
+ unlock_registry();
70
+ return obj;
71
+ }
72
+
73
+ // free a single registry
74
+ //
75
+ // free only one.
76
+ static void unregister_object(VALUE obj) {
77
+ if (!obj || obj == Qnil)
78
+ return;
79
+ lock_registry();
80
+ struct Object *line = registry.first;
81
+ struct Object *prev = NULL;
82
+ while (line) {
83
+ if (line->obj == obj) {
84
+ line->count--;
85
+ if (!line->count) {
86
+ if (line == registry.first)
87
+ registry.first = line->next;
88
+ else if (prev) // must be true, really
89
+ prev->next = line->next;
90
+ // move the object container to the discarded object pool
91
+ line->next = registry.obj_pool;
92
+ registry.obj_pool = line;
93
+ }
94
+ goto finish;
95
+ }
96
+ prev = line;
97
+ line = line->next;
98
+ }
99
+ finish:
100
+ unlock_registry();
101
+ }
102
+
103
+ // // Replaces one registry object with another,
104
+ // // allowing updates to the Registry with no memory allocations.
105
+ // //
106
+ // // returns 0 if all OK, returns -1 if it couldn't replace the object.
107
+ // static int replace_object(VALUE obj, VALUE new_obj) {
108
+ // int ret = -1;
109
+ // if (obj == new_obj)
110
+ // return 0;
111
+ // pthread_mutex_lock(&registry_lock);
112
+ // struct Object* line = registry.first;
113
+ // while (line) {
114
+ // if (line->obj == obj) {
115
+ // line->obj = new_obj;
116
+ // ret = 0;
117
+ // goto finish;
118
+ // }
119
+ // line = line->next;
120
+ // }
121
+ // finish:
122
+ // pthread_mutex_unlock(&registry_lock);
123
+ // return ret;
124
+ // }
125
+
126
+ // a callback for the GC (marking active objects)
127
+ static void registry_mark(void *ignore) {
128
+ #ifdef RUBY_REG_DBG
129
+ Registry.print();
130
+ #endif
131
+ lock_registry();
132
+ struct Object *line = registry.first;
133
+ while (line) {
134
+ if (line->obj)
135
+ rb_gc_mark(line->obj);
136
+ line = line->next;
137
+ }
138
+ unlock_registry();
139
+ }
140
+
141
+ // clear the registry (end of lifetime)
142
+ static void registry_clear(void *ignore) {
143
+ lock_registry();
144
+ struct Object *line;
145
+ struct Object *to_free;
146
+ // free active object references
147
+ line = registry.first;
148
+ while (line) {
149
+ to_free = line;
150
+ line = line->next;
151
+ free(to_free);
152
+ }
153
+ registry.first = NULL;
154
+ // free container pool
155
+ line = registry.obj_pool;
156
+ while (line) {
157
+ to_free = line;
158
+ line = line->next;
159
+ free(to_free);
160
+ }
161
+ registry.obj_pool = NULL;
162
+ registry.owner = 0;
163
+ unlock_registry();
164
+ }
165
+
166
+ // the data-type used to identify the registry
167
+ // this sets the callbacks.
168
+ static struct rb_data_type_struct my_registry_type_struct = {
169
+ .wrap_struct_name = "RubyReferencesIn_C_Land",
170
+ .function.dfree = (void (*)(void *))registry_clear,
171
+ .function.dmark = (void (*)(void *))registry_mark,
172
+ };
173
+
174
+ // initialize the registry
175
+ static void init(VALUE owner) {
176
+ lock_registry();
177
+ if (registry.owner)
178
+ goto finish;
179
+ if (!owner)
180
+ owner = rb_cObject;
181
+ registry.owner = owner;
182
+ VALUE rReferences =
183
+ rb_define_class_under(owner, "RubyObjectRegistry_for_C_land", rb_cData);
184
+ VALUE r_registry =
185
+ TypedData_Wrap_Struct(rReferences, &my_registry_type_struct, &registry);
186
+ rb_ivar_set(owner, rb_intern("registry"), r_registry);
187
+ finish:
188
+ unlock_registry();
189
+ }
190
+
191
+ // print data, for testing
192
+ static void print(void) {
193
+ lock_registry();
194
+ struct Object *line = registry.first;
195
+ fprintf(stderr, "Registry owner is %lu\n", registry.owner);
196
+ long index = 0;
197
+ while (line) {
198
+ fprintf(stderr, "[%lu] => %d X obj %lu type %d at %p\n", index++,
199
+ line->count, line->obj, TYPE(line->obj), line);
200
+ line = line->next;
201
+ }
202
+ fprintf(stderr, "Total of %lu registered objects being marked\n", index);
203
+ unlock_registry();
204
+ }
205
+
206
+ ////////////////////////////////////////////
207
+ // The API gateway
208
+ struct ___RegistryClass___ Registry = {
209
+ .init = init,
210
+ .remove = unregister_object,
211
+ .add = register_object,
212
+ .print = print,
213
+ };
@@ -1,10 +1,10 @@
1
- #include "libserver.h"
2
1
  #include "websockets.h"
3
2
  #include "bscrypt.h"
4
- #include <stdlib.h>
3
+ #include "libserver.h"
4
+ #include <arpa/inet.h>
5
5
  #include <stdio.h>
6
+ #include <stdlib.h>
6
7
  #include <string.h>
7
- #include <arpa/inet.h>
8
8
 
9
9
  #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
10
10
  #include <endian.h>
@@ -593,8 +593,12 @@ refuse:
593
593
  // set the negative response
594
594
  response->status = 400;
595
595
  cleanup:
596
- http_response_finish(response);
597
596
  if (response->status == 101) {
597
+ // set the protocol lock
598
+ ws->protocol.callback_lock = SPN_LOCK_INIT;
599
+ spn_lock(&ws->protocol.callback_lock);
600
+ // send the response
601
+ http_response_finish(response);
598
602
  // update the protocol object, cleanning up the old one
599
603
  server_switch_protocol(ws->fd, (void *)ws);
600
604
  // we have an active websocket connection - prep the connection buffer
@@ -604,8 +608,10 @@ cleanup:
604
608
  // call the on_open callback
605
609
  if (settings.on_open)
606
610
  server_task(ws->fd, on_open, settings.on_open, NULL);
611
+ spn_unlock(&ws->protocol.callback_lock);
607
612
  return 0;
608
613
  }
614
+ http_response_finish(response);
609
615
  destroy_ws(ws);
610
616
  return -1;
611
617
  }
@@ -37,7 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.requirements << 'A Unix based system: Linux / macOS / BSD.'
38
38
  spec.requirements << 'An updated C compiler.'
39
39
  spec.requirements << 'Ruby >= 2.2.2'
40
- spec.requirements << 'Ruby >= 2.0.0 is recommended.'
40
+ spec.requirements << 'Ruby >= 2.3.0 is recommended.'
41
41
 
42
42
  spec.add_development_dependency 'bundler', '~> 1.10'
43
43
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '0.2.4'.freeze
2
+ VERSION = '0.2.5'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iodine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-14 00:00:00.000000000 Z
11
+ date: 2016-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -150,12 +150,14 @@ files:
150
150
  - ext/iodine/random.c
151
151
  - ext/iodine/random.h
152
152
  - ext/iodine/rb-call.c
153
+ - ext/iodine/rb-call.c_old
153
154
  - ext/iodine/rb-call.h
154
155
  - ext/iodine/rb-libasync.h
155
156
  - ext/iodine/rb-rack-io.c
156
157
  - ext/iodine/rb-rack-io.h
157
158
  - ext/iodine/rb-registry.c
158
159
  - ext/iodine/rb-registry.h
160
+ - ext/iodine/rb-registry_old.c_old
159
161
  - ext/iodine/sha1.c
160
162
  - ext/iodine/sha1.h
161
163
  - ext/iodine/sha2.c
@@ -198,7 +200,7 @@ requirements:
198
200
  - 'A Unix based system: Linux / macOS / BSD.'
199
201
  - An updated C compiler.
200
202
  - Ruby >= 2.2.2
201
- - Ruby >= 2.0.0 is recommended.
203
+ - Ruby >= 2.3.0 is recommended.
202
204
  rubyforge_project:
203
205
  rubygems_version: 2.5.2
204
206
  signing_key: