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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +12 -32
- data/bin/ws-echo +1 -1
- data/ext/iodine/iodine_core.c +12 -2
- data/ext/iodine/iodine_http.c +0 -10
- data/ext/iodine/libserver.c +25 -3
- data/ext/iodine/libserver.h +24 -10
- data/lib/iodine/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 252241f38288180ae589bbe3787c5214a62cef10
|
4
|
+
data.tar.gz: 39f4936e62585c3f95dc9910de67a82d43093776
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c41af300fdffcc3e5721a2c2da23841686b19b1eb6bae2f7c70e3b2bfb954d0182bffa0610846d656b4812ff6abd0b2f678767c908fda305e266cc2b847e2679
|
7
|
+
data.tar.gz: 1f17f2dd33c1a95ab5e4e5e64afbbea417743b7364fb2826a4d628eea9dabf33f71056a9184892c92c063f2ff6df9e86654fa4199da60cfab017b850fe5baabe
|
data/CHANGELOG.md
CHANGED
@@ -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')
|
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
|
-
|
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
|
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
|
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
|
|
data/bin/ws-echo
CHANGED
data/ext/iodine/iodine_core.c
CHANGED
@@ -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
|
-
|
557
|
-
|
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;
|
data/ext/iodine/iodine_http.c
CHANGED
@@ -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
|
data/ext/iodine/libserver.c
CHANGED
@@ -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 %
|
521
|
+
fprintf(stderr, "* Running %u processes"
|
522
522
|
" in single thread mode.\n",
|
523
523
|
settings.processes);
|
524
524
|
else
|
525
|
-
fprintf(stderr, "* Running %
|
526
|
-
" X %
|
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.
|
data/ext/iodine/libserver.h
CHANGED
@@ -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
|
-
|
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
|
-
|
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`
|
418
|
-
|
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.
|
data/lib/iodine/version.rb
CHANGED
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
|
+
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-
|
11
|
+
date: 2016-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|