iodine 0.2.6 → 0.2.7

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: 227ead92403069fa835295863ddaf99c1ba87ca8
4
- data.tar.gz: 78a7b0dda1d37631471bc77018f985181ab20ff8
3
+ metadata.gz: 252241f38288180ae589bbe3787c5214a62cef10
4
+ data.tar.gz: 39f4936e62585c3f95dc9910de67a82d43093776
5
5
  SHA512:
6
- metadata.gz: b28193f978e8ea93aab821e7109386a0df6e617396c6588732724e96e52587a0a0a48ecf742f5fa3c474f2c00bed38442db9bb53520cf725311d3c3464437f28
7
- data.tar.gz: 852b315197992b6fd55213015b6dc5bd0ae06b98c6420a6fde671c373c436676a09e89287c15307224dbb9d8ea467e2ce78bfa1a728c40797184ffabfe8e9baa
6
+ metadata.gz: c41af300fdffcc3e5721a2c2da23841686b19b1eb6bae2f7c70e3b2bfb954d0182bffa0610846d656b4812ff6abd0b2f678767c908fda305e266cc2b847e2679
7
+ data.tar.gz: 1f17f2dd33c1a95ab5e4e5e64afbbea417743b7364fb2826a4d628eea9dabf33f71056a9184892c92c063f2ff6df9e86654fa4199da60cfab017b850fe5baabe
@@ -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.7
12
+
13
+ **Minor Fix**: fixed an issue where a negative number or processes or threads would initiate a very large number of forks, promoting a resource choke to the system. limited the number of threads (1023) and processes (127).
14
+
15
+ **Update**: Automated the number of processes (forks) and threads used when these are not explicitly specified. These follow the number of cores / 2.
16
+
17
+ ***
18
+
11
19
  Change log v.0.2.6
12
20
 
13
21
  **Update**: The IO reactor review will now be delayed until all events scheduled are done. This means that is events schedule future events, no IO data will be reviewed until all scheduled data is done. Foolish use might cause infinite loops that skip the IO reactor, but otherwise performance is improved (since the IO reactor might cause a thread to "sleep", delaying event execution).
data/README.md CHANGED
@@ -10,21 +10,15 @@ Iodine makes writing Object Oriented **Network Services** easy to write.
10
10
 
11
11
  Iodine is an **evented** framework with a simple API that builds off the low level [C code library facil.io](https://github.com/boazsegev/facil.io) with support for **epoll** and **kqueue** - this means that:
12
12
 
13
- * Iodine can handle **thousands of concurrent connections** (tested with 20K connections).
13
+ * Iodine can handle **thousands of concurrent connections** (tested with more then 20K connections).
14
14
 
15
15
  That's right, Iodine isn't subject to the 1024 connection limit imposed by native Ruby and `select`/`poll` based applications.
16
16
 
17
17
  This makes Iodine ideal for writing HTTP/2 and Websocket servers (which is what started this whole thing).
18
18
 
19
- * Iodine supports only **Linux/Unix** based systems (i.e. OS X, Ubuntu, FreeBSD etc'). This allows us to:
19
+ * Iodine supports only **Linux/Unix** based systems (i.e. OS X, Ubuntu, FreeBSD etc'), which are ideal for evented IO (while Windows and Solaris are better at IO *completion* events, which are totally different).
20
20
 
21
- * Optimize our code for the production environment.
22
-
23
- * Have our testing and development machines behave the same as our ultimate production environment.
24
-
25
- * Catch any issues (read: bugs) while in development - just ask AT&T about how important this is ;-)
26
-
27
- Iodine is a C extension for Ruby, developed for Ruby MRI 2.2.2 and up... it should support the whole Ruby 2.0 family, but Rack requires Ruby 2.2.2, and so Iodine matches this requirement.
21
+ Iodine is a C extension for Ruby, developed for Ruby MRI 2.2.2 and up... it should support the whole Ruby 2.0 MRI family, but Rack requires Ruby 2.2.2, and so Iodine matches this requirement.
28
22
 
29
23
  ## Iodine::Rack - an HTTP and Websockets server
30
24
 
@@ -219,13 +213,15 @@ Of course, if you still want to use Rack's `hijack` API, Iodine will support you
219
213
 
220
214
  ### How does it compare to other servers?
221
215
 
216
+ Personally, after looking around, the only comparable servers are Puma and Passenger (the open source version), which Iodine significantly outperformed on my tests.
217
+
222
218
  Since the HTTP and Websocket parsers are written in C (with no RegExp), they're fairly fast.
223
219
 
224
- Also, Iodine's core and parsers are running outside of Ruby's global lock, meaning that they enjoy true concurrency before entering the Ruby layer (your application) - this offers Iodine a big advantage over other servers.
220
+ Also, Iodine's core and parsers are running outside of Ruby's global lock, meaning that they enjoy true concurrency before entering the Ruby layer (your application) - this offers Iodine a big advantage over other Ruby servers.
225
221
 
226
- Another assumption Iodine makes is that it is behind a load balancer / proxy (which is the normal way Ruby applications are deployed) - this allows Iodine to disregard header validity checks (we're not checking for invalid characters) which speeds up the parsing process even more.
222
+ Another assumption Iodine makes is that it is behind a load balancer / proxy (which is the normal way Ruby applications are deployed) - this allows Iodine to disregard header validity checks (we're not checking for invalid characters) which speeds up the parsing process even further.
227
223
 
228
- I'm not posting any data because Iodine is still under development and things are somewhat dynamic - but you can compare the performance for yourself using `wrk` or `ab`:
224
+ I recommend benchmarking the performance for yourself using `wrk` or `ab`:
229
225
 
230
226
  ```bash
231
227
  $ wrk -c200 -d4 -t12 http://localhost:3000/
@@ -246,31 +242,15 @@ end
246
242
  run App
247
243
  ```
248
244
 
249
- Then start comparing servers:
250
-
251
- ```bash
252
- $ rackup -p 3000 -E production -s iodine
253
- ```
254
-
255
- vs.
245
+ Then start comparing servers. Here are the settings I used to compare Iodine and Puma (4 processes, 16 threads):
256
246
 
257
247
  ```bash
258
- $ rackup -p 3000 -E production -s <Other_Server_Here>
259
- ```
260
-
261
- Puma has ~16 threads by default, so when comparing against Puma, consider using an equal number of threads:
262
-
263
- ```bash
264
- # (t - threads, w - worker processes)
265
248
  $ RACK_ENV=production iodine -p 3000 -t 16 -w 4
249
+ # vs.
250
+ $ RACK_ENV=production puma -p 3000 -t 16 -w 4
266
251
  ```
267
252
 
268
- vs.
269
-
270
- ```bash
271
- # (t - threads, w - worker processes)
272
- $ RACK_ENV=production puma -p 3000 -w 4 -q
273
- ```
253
+ Iodine performed almost twice as well, (~90K req/sec vs. ~44K req/sec) while keeping a memory foot print that was more then 20% lower (~65Mb vs. ~85Mb).
274
254
 
275
255
  Review the `iodine -?` help for more data.
276
256
 
@@ -36,7 +36,7 @@ class WSEcho
36
36
  end
37
37
 
38
38
  def on_ready
39
- puts "on_ready called foe #{self}"
39
+ puts "on_ready called for #{self}"
40
40
  end
41
41
 
42
42
  def on_message(data)
@@ -553,11 +553,16 @@ static void *srv_start_no_gvl(void *_) {
553
553
  // collect requested settings
554
554
  VALUE rb_th_i = rb_iv_get(Iodine, "@threads");
555
555
  VALUE rb_pr_i = rb_iv_get(Iodine, "@processes");
556
- size_t threads = (TYPE(rb_th_i) == T_FIXNUM) ? FIX2ULONG(rb_th_i) : 0;
557
- size_t processes = (TYPE(rb_pr_i) == T_FIXNUM) ? FIX2ULONG(rb_pr_i) : 0;
556
+ ssize_t threads = (TYPE(rb_th_i) == T_FIXNUM) ? FIX2LONG(rb_th_i) : 0;
557
+ ssize_t processes = (TYPE(rb_pr_i) == T_FIXNUM) ? FIX2LONG(rb_pr_i) : 0;
558
558
  // print a warnning if settings are sub optimal
559
559
  #ifdef _SC_NPROCESSORS_ONLN
560
560
  size_t cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
561
+ if (processes <= 0)
562
+ processes = (cpu_count >> 1) ? (cpu_count >> 1) : 1;
563
+ if (threads <= 0)
564
+ threads = (cpu_count >> 1) ? (cpu_count >> 1) : 1;
565
+
561
566
  if (cpu_count > 0 &&
562
567
  ((processes << 1) < cpu_count || processes > (cpu_count << 1)))
563
568
  fprintf(
@@ -571,6 +576,11 @@ static void *srv_start_no_gvl(void *_) {
571
576
  ? "Some CPUs won't be utilized, inhibiting performance."
572
577
  : "This causes excessive context switches, wasting resources."),
573
578
  cpu_count, cpu_count);
579
+ #else
580
+ if (processes <= 0)
581
+ processes = 1;
582
+ if (threads <= 0)
583
+ threads = 1;
574
584
  #endif
575
585
  server_run(.threads = threads, .processes = processes);
576
586
  return NULL;
@@ -541,33 +541,23 @@ int iodine_http_review(void) {
541
541
  // initialize the Rack env template
542
542
  init_env_template();
543
543
 
544
- // get Iodine concurrency info
545
- VALUE rb_threads = rb_ivar_get(Iodine, rb_intern("@threads"));
546
- int threads = rb_threads == Qnil ? 1 : (FIX2INT(rb_threads));
547
- VALUE rb_processes = rb_ivar_get(Iodine, rb_intern("@processes"));
548
- int processes = rb_processes == Qnil ? 1 : (FIX2INT(rb_processes));
549
-
550
544
  // Write message
551
545
  VALUE iodine_version = rb_const_get(Iodine, rb_intern("VERSION"));
552
546
  VALUE ruby_version = rb_const_get(Iodine, rb_intern("RUBY_VERSION"));
553
547
  if (public_folder)
554
548
  fprintf(stderr, "Starting up Iodine Http Server:\n"
555
549
  " * Ruby v.%s\n * Iodine v.%s \n"
556
- " * %lu processes X %lu thread%s\n"
557
550
  " * %lu max concurrent connections / open files\n"
558
551
  " * Serving static files from:\n"
559
552
  " %s\n\n",
560
553
  StringValueCStr(ruby_version), StringValueCStr(iodine_version),
561
- (size_t)processes, (size_t)threads, (threads > 1 ? "s" : ""),
562
554
  (size_t)sock_max_capacity(), public_folder);
563
555
  else
564
556
  fprintf(stderr, "Starting up Iodine Http Server:\n"
565
557
  " * Ruby v.%s\n * Iodine v.%s \n"
566
- " * %lu processes X %lu thread%s\n"
567
558
  " * %lu max concurrent connections / open files\n"
568
559
  "\n",
569
560
  StringValueCStr(ruby_version), StringValueCStr(iodine_version),
570
- (size_t)processes, (size_t)threads, (threads > 1 ? "s" : ""),
571
561
  (size_t)sock_max_capacity());
572
562
 
573
563
  // listen
@@ -518,12 +518,12 @@ ssize_t server_run(struct ServerSettings settings) {
518
518
 
519
519
  #if defined(SERVER_PRINT_STATE) && SERVER_PRINT_STATE == 1
520
520
  if (settings.threads == 0)
521
- fprintf(stderr, "* Running %lu processes"
521
+ fprintf(stderr, "* Running %u processes"
522
522
  " in single thread mode.\n",
523
523
  settings.processes);
524
524
  else
525
- fprintf(stderr, "* Running %lu processes"
526
- " X %lu threads.\n",
525
+ fprintf(stderr, "* Running %u processes"
526
+ " X %u threads.\n",
527
527
  settings.processes, settings.threads);
528
528
  #endif
529
529
 
@@ -814,6 +814,28 @@ static void perform_each_task(void *task) {
814
814
  API
815
815
  ******* */
816
816
 
817
+ /**
818
+ Performs a task for each connection except the origin connection, unsafely and
819
+ synchronously.
820
+ */
821
+ void server_each_unsafe(intptr_t origin_uuid,
822
+ void (*task)(intptr_t origin_uuid, intptr_t target_uuid,
823
+ protocol_s *target_protocol, void *arg),
824
+ void *arg) {
825
+ intptr_t target;
826
+ protocol_s *protocol;
827
+ for (size_t i = 0; i < server_data.capacity; i++) {
828
+ target = sock_fd2uuid(p2task(task).target);
829
+ if (target == -1)
830
+ continue;
831
+ protocol = protocol_uuid(target);
832
+ if (protocol == NULL || protocol->service == listener_protocol_name ||
833
+ protocol->service == timer_protocol_name)
834
+ continue;
835
+ task(origin_uuid, target, protocol, arg);
836
+ }
837
+ }
838
+
817
839
  /**
818
840
  Schedules a specific task to run asyncronously for each connection (except the
819
841
  origin connection) on a specific protocol.
@@ -265,12 +265,12 @@ struct ServerSettings {
265
265
  /**
266
266
  Sets the amount of threads to be created for the server's thread-pool.
267
267
  Defaults to 1 - the reactor and all callbacks will work using a single working
268
- thread, allowing for an evented single threaded design.
268
+ thread, allowing for an evented single threaded design. Limited to 1024.
269
269
  */
270
- size_t threads;
270
+ unsigned threads : 10;
271
271
  /** Sets the amount of processes to be used (processes will be forked).
272
- Defaults to 1 working processes (no forking). */
273
- size_t processes;
272
+ Defaults to 1 working processes (no forking), limited to 127.*/
273
+ unsigned processes : 7;
274
274
  };
275
275
 
276
276
  /* *****************************************************************************
@@ -400,10 +400,27 @@ long server_count(char *service);
400
400
  * Tasks + Async
401
401
  */
402
402
 
403
+ /**
404
+ Performs a task for each connection except the origin connection, unsafely and
405
+ synchronously.
406
+
407
+ The task will be performed synchronously (blocking), without waiting for a lock.
408
+
409
+ The task will be performed unsafely. For example, the protocol object might be
410
+ invalid midway through (or at the beginning) the execution, as there is no
411
+ protection against memory deallocation.
412
+
413
+ This function should probably be avoided except when implementing publication or
414
+ broadcast algorithms.
415
+ */
416
+ void server_each_unsafe(intptr_t origin_uuid,
417
+ void (*task)(intptr_t origin_uuid, intptr_t target_uuid,
418
+ protocol_s *target_protocol, void *arg),
419
+ void *arg);
420
+
403
421
  /**
404
422
  Schedules a specific task to run asyncronously for each connection (except the
405
423
  origin connection).
406
- a NULL service identifier == all connections (all protocols).
407
424
 
408
425
  The task is performed within each target connection's busy "lock", meanning no
409
426
  two tasks (or `on_data` events) should be performed at the same time
@@ -414,11 +431,8 @@ The `on_finish` callback will be called once the task is finished and it will
414
431
  receive the originating connection's UUID (could be 0). The originating
415
432
  connection might have been closed by that time.
416
433
 
417
- The `service` string (pointer) identifier MUST be a constant string object OR
418
- a string that will persist until the `on_finish` callback is called. In other
419
- words, either hardcode the string or use `malloc` to allocate it before
420
- calling `each` and `free` to release the string from within the `on_finish`
421
- callback.
434
+ The `service` identifier is required. The comparison is pointer value comparison
435
+ and isn't related to the content of the service string.
422
436
 
423
437
  It is recommended the `on_finish` callback is only used to perform any
424
438
  resource cleanup necessary.
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '0.2.6'.freeze
2
+ VERSION = '0.2.7'.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.6
4
+ version: 0.2.7
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-19 00:00:00.000000000 Z
11
+ date: 2016-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack