iodine 0.4.14 → 0.4.15
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 +61 -33
- data/README.md +7 -5
- data/ext/iodine/base64.c +12 -0
- data/ext/iodine/defer.c +211 -108
- data/ext/iodine/defer.h +7 -0
- data/ext/iodine/facil.c +5 -1
- data/ext/iodine/facil.h +1 -1
- data/ext/iodine/fio2resp.c +19 -30
- data/ext/iodine/fio2resp.h +2 -1
- data/ext/iodine/fio_cli_helper.c +2 -2
- data/ext/iodine/fiobj.h +11 -624
- data/ext/iodine/fiobj_ary.c +65 -26
- data/ext/iodine/fiobj_ary.h +106 -0
- data/ext/iodine/fiobj_hash.c +175 -115
- data/ext/iodine/fiobj_hash.h +128 -0
- data/ext/iodine/fiobj_internal.c +189 -0
- data/ext/iodine/{fiobj_types.h → fiobj_internal.h} +126 -136
- data/ext/iodine/fiobj_json.c +161 -207
- data/ext/iodine/fiobj_json.h +43 -0
- data/ext/iodine/fiobj_numbers.c +53 -35
- data/ext/iodine/fiobj_numbers.h +49 -0
- data/ext/iodine/fiobj_primitives.c +103 -70
- data/ext/iodine/fiobj_primitives.h +55 -0
- data/ext/iodine/fiobj_str.c +171 -59
- data/ext/iodine/fiobj_str.h +113 -0
- data/ext/iodine/fiobj_sym.c +46 -124
- data/ext/iodine/fiobj_sym.h +60 -0
- data/ext/iodine/fiobject.c +589 -0
- data/ext/iodine/fiobject.h +276 -0
- data/ext/iodine/pubsub.h +1 -1
- data/ext/iodine/resp.c +2 -2
- data/ext/iodine/siphash.c +11 -0
- data/ext/iodine/spnlock.inc +7 -5
- data/ext/iodine/websocket_parser.h +44 -7
- data/lib/iodine/version.rb +1 -1
- metadata +13 -8
- data/ext/iodine/fiobj_alloc.c +0 -81
- data/ext/iodine/fiobj_generic.c +0 -260
- data/ext/iodine/fiobj_io.c +0 -58
- data/ext/iodine/fiobj_misc.c +0 -213
- data/ext/iodine/fiobj_tests.c +0 -474
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fae6d0a20c82608a04c0a8845ccc4992810ac6c
|
4
|
+
data.tar.gz: eec0b8600e70903a2433022c7d236c8a315f08d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c455f2313d471371eefacf262855f01c5efcffff54e46e06f966eee46cc3de1ea482a9e8547d40d7c04aa631cfd8889070fbd532d6372ea6a70f0b5b8f18d56b
|
7
|
+
data.tar.gz: bbf799a3051dac65311d12dbf287530b36ab873f53b256181a087235ee6f3fbe35fe52528d16364ca169105c3d3b076f3e17bedc960d238889f0395f6df4fbf0
|
data/CHANGELOG.md
CHANGED
@@ -6,7 +6,35 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
6
6
|
|
7
7
|
## Changes:
|
8
8
|
|
9
|
-
####
|
9
|
+
#### Change log v.0.4.15
|
10
|
+
|
11
|
+
**Update**: (`facil.io`) updating the facil.io library version to use the 0.5.8 released version.
|
12
|
+
|
13
|
+
This includes the following changes (as well as other minor changes), as detailed in facil.io's CHANGELOG:
|
14
|
+
|
15
|
+
**Fix**: (`defer`, `fiobj`) fix Linux compatibility concerns (when using GCC). Credit goes to @kotocom.
|
16
|
+
|
17
|
+
**Fix**: (`defer`) fixes the non-debug version of the new defer, which didn't define some debug macros.
|
18
|
+
|
19
|
+
**Updates**: minor updates to the boilerplate documentation and the "new application" creation process.
|
20
|
+
|
21
|
+
**Fix**: Added `cmake_minimum_required` and related CMake fixes to the CMake file and generator. Credit to David Morán (@david-moran) for [PR #22](https://github.com/boazsegev/facil.io/pull/22) fixing the CMakelist.txt.
|
22
|
+
|
23
|
+
**Compatibility**: (`websocket_parser`) removed unaligned memory access from the XOR logic in the parser, making it more compatible with older CPU systems that don't support unaligned memory access or 64 bit word lengths.
|
24
|
+
|
25
|
+
**Optimization**: (`defer`) rewrote the data structure to use a hybrid cyclic buffer and linked list for the task queue (instead of a simple linked list), optimizing locality and minimizing memory allocations.
|
26
|
+
|
27
|
+
**Misc**: minor updates and tweaks, such as adding the `fiobj_ary2prt` function for operations such as quick sort, updating some documentation etc'.
|
28
|
+
|
29
|
+
**Fix**: (`fiobj`) fixed an where `gcc` would complain about overwriting the `fio_cstr_s` struct due to `const` members. Credit to @vit1251 for exposing this issue.
|
30
|
+
|
31
|
+
**Fix**: (`fiobj`) fixed NULL pointer testing for `fiobj_free(NULL)`.
|
32
|
+
|
33
|
+
**Compatibility**: (`gcc-6`) Fix some compatibility concerns with `gcc` version 6, as well as some warnings that were exposed when testing with `gcc`.
|
34
|
+
|
35
|
+
**Optimization**: (`fiobj`) optimized the JSON parsing memory allocations as well as fixed some of the function declarations to add the `const` keyword where relevant.
|
36
|
+
|
37
|
+
#### Change log v.0.4.14
|
10
38
|
|
11
39
|
**Fix**: (`facil.io`) fixes an issue where timer monitoring would report failure when the timer exists and is being monitored.
|
12
40
|
|
@@ -92,7 +120,7 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
92
120
|
|
93
121
|
**Fix**: fixed an issue where Websocket `ping` timeouts were being ignored for the default `Iodine::Rack` server, causing the default (40 seconds) to persist over specified valued.
|
94
122
|
|
95
|
-
**Fix**: fixed a possible issue with
|
123
|
+
**Fix**: fixed a possible issue with high-jacking which might cause the server to hang.
|
96
124
|
|
97
125
|
***
|
98
126
|
|
@@ -233,13 +261,13 @@ Iodine.start
|
|
233
261
|
|
234
262
|
**Fix**: fixed another issue with `each_write` where a race condition review was performed outside the protected critical section, in some cases this would caused memory to be freed twice and crash the server. This issue is now resolved.
|
235
263
|
|
236
|
-
**Deprecation**: In version 0.2.1 we have notified that the the
|
264
|
+
**Deprecation**: In version 0.2.1 we have notified that the the Websocket method `uuid` was deprecated in favor of `conn_id`, as suggested by the [Rack Websocket Draft](https://github.com/rack/rack/pull/1107). This deprecation is now enforced.
|
237
265
|
|
238
266
|
***
|
239
267
|
|
240
268
|
#### Change log v.0.2.13
|
241
269
|
|
242
|
-
**Fix**: Fixed an issue presented in the C layer, where big fragmented
|
270
|
+
**Fix**: Fixed an issue presented in the C layer, where big fragmented Websocket messages sent by the client could cause parsing errors and potentially, in some cases, cause a server thread to spin in a loop (DoS). Credit to @Filly for exposing the issue in the [`facil.io`](https://github.com/boazsegev/facil.io) layer. It should be noted that Chrome is the only browser where this issue could be invoked for testing.
|
243
271
|
|
244
272
|
**Credit**: credit to Elia Schito (@elia) and Augusts Bautra (@Epigene) for fixing parts of the documentation (PR #11 , #12).
|
245
273
|
|
@@ -358,7 +386,7 @@ Iodine.start
|
|
358
386
|
|
359
387
|
**Update**: Websockets now support the `has_pending?` method and `on_ready` callback, as suggested by the [Rack Websocket Draft](https://github.com/rack/rack/pull/1107).
|
360
388
|
|
361
|
-
**Update**: deprecated the
|
389
|
+
**Update**: deprecated the Websocket method `uuid` in favor of `conn_id`, as suggested by the [Rack Websocket Draft](https://github.com/rack/rack/pull/1107).
|
362
390
|
|
363
391
|
**Fix**: fixed an issue were the server would crash when attempting to send a long enough websocket message.
|
364
392
|
|
@@ -378,7 +406,7 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
378
406
|
|
379
407
|
**Optimization**: Minor optimizations. i.e. - creates 1 less Time object per request (The logging still creates a Time object unless disabled using `Iodine.logger = nil`).
|
380
408
|
|
381
|
-
**Security**:
|
409
|
+
**Security**: HTTP/1 now reviews the Body's size as it grows (similar to HTTP/2), mitigating any potential attacks related to the size of the data sent.
|
382
410
|
|
383
411
|
**Logs**: Log the number of threads utilized when starting up the server.
|
384
412
|
|
@@ -388,15 +416,15 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
388
416
|
|
389
417
|
**Update/Fix**: Updated the `x-forwarded-for` header recognition, to accommodate an Array formatting sometimes used (`["ip1", "ip2", ...]`).
|
390
418
|
|
391
|
-
**Update**: native support for the `Forwarded` header
|
419
|
+
**Update**: native support for the `Forwarded` header HTTP.
|
392
420
|
|
393
|
-
**API Changes**: `Iodine::
|
421
|
+
**API Changes**: `Iodine::HTTP.max_http_buffer` was replaced with `Iodine::HTTP.max_body_size`, for a better understanding of the method's result.
|
394
422
|
|
395
423
|
***
|
396
424
|
|
397
425
|
#### Change log v.0.1.19
|
398
426
|
|
399
|
-
**Update**: added the `go_away` method to the
|
427
|
+
**Update**: added the `go_away` method to the HTTP/1 protocol, for seamless connection closeing across HTTP/2, HTTP/1 and Websockets.
|
400
428
|
|
401
429
|
***
|
402
430
|
|
@@ -408,11 +436,11 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
408
436
|
|
409
437
|
#### Change log v.0.1.17
|
410
438
|
|
411
|
-
**Credit**: thanks you @frozenfoxx for going through the readme and fixing my broken
|
439
|
+
**Credit**: thanks you @frozenfoxx for going through the readme and fixing my broken grammar.
|
412
440
|
|
413
441
|
**Fix**: fixed an issue where multiple Pings might get sent when pinging takes time. Now pings are exclusive (run within their own Mutex).
|
414
442
|
|
415
|
-
**Fix**:
|
443
|
+
**Fix**: HTTP/2 is back... sorry about breaking it in the 0.1.16 version. When I updated the write buffer I forgot to write the status of the response, causing a protocol error related with the headers. It's now working again.
|
416
444
|
|
417
445
|
**Update**: by default and for security reasons, session id's created through a secure connection (SSL) will NOT be available on a non secure connection (SSL/TLS). However, while upgrading to the encrypted connection, the non_encrypted session storage is now available for review using the `Response#session_old` method.
|
418
446
|
|
@@ -422,13 +450,13 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
422
450
|
|
423
451
|
#### Change log v.0.1.16
|
424
452
|
|
425
|
-
**Performance**:
|
453
|
+
**Performance**: HTTP/1 and HTTP/2 connections now share and recycle their write buffer when while reading the response body and writing it to the IO. This (hopefully) prevents excess `malloc` calls by the interpreter.
|
426
454
|
|
427
455
|
***
|
428
456
|
|
429
457
|
#### Change log v.0.1.15
|
430
458
|
|
431
|
-
**Update**: IO reactor will now update IO status even when tasks are pending. IO will still be read only when there are no more tasks to handle, but this allows chained tasks to relate to the updated IO status. i.e. this should improve
|
459
|
+
**Update**: IO reactor will now update IO status even when tasks are pending. IO will still be read only when there are no more tasks to handle, but this allows chained tasks to relate to the updated IO status. i.e. this should improve Websocket availability for broadcasting (delay from connection to availability might occur until IO is registered).
|
432
460
|
|
433
461
|
**Update**: Websockets now support the `on_ping` callback, which will be called whenever a ping was sent without error.
|
434
462
|
|
@@ -450,7 +478,7 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
450
478
|
|
451
479
|
**Fix**: The `flash` cookie-jar will now actively prevent Symbol and String keys from overlapping.
|
452
480
|
|
453
|
-
**Compatibility**: minor fixes and changes in
|
481
|
+
**Compatibility**: minor fixes and changes in preparation for Ruby 2.3.0. These may affect performance due to slower String initialization times.
|
454
482
|
|
455
483
|
***
|
456
484
|
|
@@ -458,7 +486,7 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
458
486
|
|
459
487
|
**Update**: Passing a hash as the cookie value will allow to set cookie parameters using the {Response#set_cookie} options. i.e.: `cookies['key']= {value: "lock", max_age: 20}`.
|
460
488
|
|
461
|
-
**Security**: set the
|
489
|
+
**Security**: set the HTTPOnly flag for session id cookies.
|
462
490
|
|
463
491
|
***
|
464
492
|
|
@@ -472,7 +500,7 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
472
500
|
|
473
501
|
**Fix**: make sure the WebsocketClient doesn't automatically renew the connection when the connection was manually closed by the client.
|
474
502
|
|
475
|
-
**Performance**: faster TimedEvent clearing when manually stopped. Minor improvements to direct big-file sending (recycle buffer to avoid malloc).
|
503
|
+
**Performance**: faster TimedEvent clearing when manually stopped. Minor improvements to direct big-file sending (recycle buffer to avoid `malloc`).
|
476
504
|
|
477
505
|
***
|
478
506
|
|
@@ -488,17 +516,17 @@ Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby
|
|
488
516
|
|
489
517
|
#### Change log v.0.1.8
|
490
518
|
|
491
|
-
**Fix**: Websocket broadcasts are now correctly executed within the IO's mutex locker. This maintains the idea that only one thread at a time should be executing code on
|
519
|
+
**Fix**: Websocket broadcasts are now correctly executed within the IO's mutex locker. This maintains the idea that only one thread at a time should be executing code on behalf of any given Protocol object ("yes" to concurrency between objects but "no" to concurrency within objects).
|
492
520
|
|
493
521
|
**Fix** fixed an issue where manually setting the number of threads for Rack applications (when using Iodine as a Rack server), the setting was mistakenly ignored.
|
494
522
|
|
495
|
-
**Fix** fixed an issue where sometimes
|
523
|
+
**Fix** fixed an issue where sometimes extracting the HTTP response's body would fail (if body is `nil`).
|
496
524
|
|
497
|
-
**Feature**: session objects are now aware of the session id. The
|
525
|
+
**Feature**: session objects are now aware of the session id. The session id is available by calling `response.session.id`
|
498
526
|
|
499
|
-
**Fix** fixed an issue where
|
527
|
+
**Fix** fixed an issue where HTTP streaming wasn't chunk encoding after connection error handling update.
|
500
528
|
|
501
|
-
**Fix** fixed an issue where
|
529
|
+
**Fix** fixed an issue where HTTP streaming would disconnect while still processing. Streaming timeout now extended to 15 seconds between response writes.
|
502
530
|
|
503
531
|
***
|
504
532
|
|
@@ -514,7 +542,7 @@ Removed a deprecation notice for blocking API. Client API will remain blocking d
|
|
514
542
|
|
515
543
|
**Fix**: fixed an issue where WebsocketClient wouldn't mask outgoing data, causing some servers to respond badly.
|
516
544
|
|
517
|
-
**Performance**: minor performance improvements to the
|
545
|
+
**Performance**: minor performance improvements to the Websocket parser, for unmasking messages.
|
518
546
|
|
519
547
|
**Deprecation notice**:
|
520
548
|
|
@@ -524,7 +552,7 @@ Removed a deprecation notice for blocking API. Client API will remain blocking d
|
|
524
552
|
|
525
553
|
#### Change log v.0.1.5
|
526
554
|
|
527
|
-
**Feature**: The Response#body can now be set to a File object, allowing Iodine to preserve memory when serving large static files from disc. Limited Range requests are also supported - together, these changes allow Iodine to serve media files (such as movies) while suffering a smaller memory penalty and supporting a wider
|
555
|
+
**Feature**: The Response#body can now be set to a File object, allowing Iodine to preserve memory when serving large static files from disc. Limited Range requests are also supported - together, these changes allow Iodine to serve media files (such as movies) while suffering a smaller memory penalty and supporting a wider variety of players (Safari requires Range request support for it's media player).
|
528
556
|
|
529
557
|
**Fix**: Fixed an issue where Iodine might take a long time to shut down after a Fatal Error during the server initialization.
|
530
558
|
|
@@ -536,33 +564,33 @@ Removed a deprecation notice for blocking API. Client API will remain blocking d
|
|
536
564
|
|
537
565
|
**Fix**: fixed an issue where a protocol's #on_close callback wouldn't be called if the Iodine server receives a shutdown signal.
|
538
566
|
|
539
|
-
**Fix**: fixed an issue where
|
567
|
+
**Fix**: fixed an issue where HTTP2 header size limit condition was not recognized by the Ruby parser (a double space issue, might be an issue with the 2.2.3 Ruby parser).
|
540
568
|
|
541
569
|
***
|
542
570
|
|
543
571
|
#### Change log v.0.1.3
|
544
572
|
|
545
|
-
**Fix**: fixed an issue with the new form/multipart parser, where the '+' sign would be converted to spaces on form fields (not uploaded files), causing
|
573
|
+
**Fix**: fixed an issue with the new form/multipart parser, where the '+' sign would be converted to spaces on form fields (not uploaded files), causing in-advert potential change to the original POSTed data.
|
546
574
|
|
547
575
|
***
|
548
576
|
|
549
577
|
#### Change log v.0.1.2
|
550
578
|
|
551
|
-
**Fix**: fixed an issue where the default implementation of `ping`
|
579
|
+
**Fix**: fixed an issue where the default implementation of `ping` did not reset the timeout if the connection wasn't being closed (the default implementation checks if the Protocol is working on existing data and either resets the timer allowing the work to complete or closes the connection if no work is being done).
|
552
580
|
|
553
581
|
***
|
554
582
|
|
555
583
|
#### Change log v.0.1.1
|
556
584
|
|
557
|
-
**Fix**: Fixed an issue where slow processing of
|
585
|
+
**Fix**: Fixed an issue where slow processing of HTTP/1 requests could cause timeout disconnections to occur while the request is being processed.
|
558
586
|
|
559
587
|
**Change/Security**: Uploads now use temporary files. Aceessing the data for file uploads should be done throught the `:file` property of the params hash (i.e. `params[:upload_field_name][:file]`). Using the `:data` property (old API) would cause the whole file to be dumped to the memory and the file's content will be returned as a String.
|
560
588
|
|
561
|
-
**Change/Security**:
|
589
|
+
**Change/Security**: HTTP upload limits are now enforced. The current default limit is about ~0.5GB.
|
562
590
|
|
563
591
|
**Feature**: WebsocketClient now supports both an auto-connection-renewal and a polling machanism built in to the `WebsocketClient.connect` API. The polling feature is mostly a handy helper for testing, as it is assumed that connection renewal and pub/sub offer a better design than polling.
|
564
592
|
|
565
|
-
**Logging**: Better
|
593
|
+
**Logging**: Better HTTP error logging and recognition.
|
566
594
|
|
567
595
|
***
|
568
596
|
|
@@ -572,17 +600,17 @@ Removed a deprecation notice for blocking API. Client API will remain blocking d
|
|
572
600
|
|
573
601
|
We learn, we evolve, we change... but we remember our past and do our best to help with the transition and make it worth the toll it takes on our resources.
|
574
602
|
|
575
|
-
I took much of the code used for
|
603
|
+
I took much of the code used for GRHTTP and GReactor, changed it, morphed it and united it into the singular Iodine gem. This includes Major API changes, refactoring of code, bug fixes and changes to the core approach of how a task/io based application should behave or be constructed.
|
576
604
|
|
577
605
|
For example, Iodine kicks in automatically when the setup script is done, so that all code is run from within tasks and IO connections and no code is run in parallel to the Iodine engine.
|
578
606
|
|
579
607
|
Another example, Iodine now favors Object Oriented code, so that some actions - such as writing a network service - require classes of objects to be declared or inherited (i.e. the Protocol class).
|
580
608
|
|
581
|
-
This allows objects to manage their data as if they were in a single thread environment, unless the objects themselves are calling asynchronous code. For example, the Protocol class makes sure that the `on_open` and `on_message(data)` callbacks are
|
609
|
+
This allows objects to manage their data as if they were in a single thread environment, unless the objects themselves are calling asynchronous code. For example, the Protocol class makes sure that the `on_open` and `on_message(data)` callbacks are executed within a Mutex (`on_close` is an exception to the rule since it is assumed that objects should be prepared to loose network connection at any moment).
|
582
610
|
|
583
|
-
Another example is that real-life
|
611
|
+
Another example is that real-life deployment preferences were favored over adjustability or features. This means that some command-line arguments are automatically recognized (such as the `-p <port>` argument) and that Iodine assumes a single web service per script/process (whereas GReactor and GRHTTP allowed multiple listening sockets).
|
584
612
|
|
585
|
-
I tested this new gem during the 0.0.x version releases, and I feel that version 0.1.0 is stable enough to work with. For instance, I left the Iodine server running all night under stress (repeatedly benchmarking it)... millions of requests later, under
|
613
|
+
I tested this new gem during the 0.0.x version releases, and I feel that version 0.1.0 is stable enough to work with. For instance, I left the Iodine server running all night under stress (repeatedly benchmarking it)... millions of requests later, under heavy load, a restart wasn't required and memory consumption didn't show any increase after the warmup period.
|
586
614
|
|
587
615
|
|
588
616
|
|
data/README.md
CHANGED
@@ -71,8 +71,10 @@ Or by adding a single line to the application. i.e. (a `config.ru` example):
|
|
71
71
|
|
72
72
|
```ruby
|
73
73
|
require 'iodine'
|
74
|
+
# static file service
|
74
75
|
Iodine::Rack.public = '/my/public/folder'
|
75
|
-
|
76
|
+
# application
|
77
|
+
out = [404, {"Content-Length" => "10"}, ["Not Found."]].freeze
|
76
78
|
app = Proc.new { out }
|
77
79
|
run app
|
78
80
|
```
|
@@ -99,8 +101,8 @@ app = proc do |env|
|
|
99
101
|
elsif request.path_info == '/file'.freeze
|
100
102
|
[200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text.' }, File.open(__FILE__)]
|
101
103
|
else
|
102
|
-
[200, { 'Content-Type'
|
103
|
-
'Content-Length'
|
104
|
+
[200, { 'Content-Type' => 'text/html',
|
105
|
+
'Content-Length' => request.path_info.length.to_s },
|
104
106
|
[request.path_info]]
|
105
107
|
end
|
106
108
|
end
|
@@ -276,7 +278,7 @@ def run_server
|
|
276
278
|
end
|
277
279
|
```
|
278
280
|
|
279
|
-
In pure Ruby (without using C extensions or Java), it's possible to do the same by using `select`... and although `select` has some issues, it works well for
|
281
|
+
In pure Ruby (without using C extensions or Java), it's possible to do the same by using `select`... and although `select` has some issues, it works well for lighter loads.
|
280
282
|
|
281
283
|
The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything...
|
282
284
|
|
@@ -286,7 +288,7 @@ The thread pool is there to help slow user code.
|
|
286
288
|
|
287
289
|
It's very common that the application's code will run slower and require external resources (i.e., databases, a custom pub/sub service, etc'). This slow code could "starve" the server, which is patiently waiting to run it's tasks on the same thread.
|
288
290
|
|
289
|
-
The slower your application code, the more threads you will need to keep the server running
|
291
|
+
The slower your application code, the more threads you will need to keep the server running in a responsive manner (note that responsiveness and speed aren't always the same).
|
290
292
|
|
291
293
|
### How does it compare to other servers?
|
292
294
|
|
data/ext/iodine/base64.c
CHANGED
@@ -123,6 +123,10 @@ int bscrypt_base64_decode(char *target, char *encoded, int base64_len) {
|
|
123
123
|
base64_len--;
|
124
124
|
encoded++;
|
125
125
|
}
|
126
|
+
while (encoded[base64_len - 1] == '\r' || encoded[base64_len - 1] == '\n' ||
|
127
|
+
encoded[base64_len - 1] == ' ' || encoded[base64_len - 1] == 0) {
|
128
|
+
base64_len--;
|
129
|
+
}
|
126
130
|
while (base64_len >= 4) {
|
127
131
|
tmp1 = *(encoded++);
|
128
132
|
tmp2 = *(encoded++);
|
@@ -176,6 +180,14 @@ int bscrypt_base64_decode(char *target, char *encoded, int base64_len) {
|
|
176
180
|
written += 3;
|
177
181
|
break;
|
178
182
|
}
|
183
|
+
if (encoded[-1] == '=') {
|
184
|
+
target--;
|
185
|
+
written--;
|
186
|
+
if (encoded[-2] == '=') {
|
187
|
+
target--;
|
188
|
+
written--;
|
189
|
+
}
|
190
|
+
}
|
179
191
|
*target = 0;
|
180
192
|
return written;
|
181
193
|
}
|
data/ext/iodine/defer.c
CHANGED
@@ -19,12 +19,22 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
19
19
|
Compile time settings
|
20
20
|
***************************************************************************** */
|
21
21
|
|
22
|
-
#ifndef DEFER_QUEUE_BUFFER
|
23
|
-
#define DEFER_QUEUE_BUFFER 4096
|
24
|
-
#endif
|
25
22
|
#ifndef DEFER_THROTTLE
|
26
23
|
#define DEFER_THROTTLE 524287UL
|
27
24
|
#endif
|
25
|
+
#ifndef DEFER_THROTTLE_LIMIT
|
26
|
+
#define DEFER_THROTTLE_LIMIT 1572864UL
|
27
|
+
#endif
|
28
|
+
|
29
|
+
#ifndef DEFER_QUEUE_BLOCK_COUNT
|
30
|
+
#if UINTPTR_MAX <= 0xFFFFFFFF
|
31
|
+
/* Almost a page of memory on most 32 bit machines: ((4096/4)-4)/3 */
|
32
|
+
#define DEFER_QUEUE_BLOCK_COUNT 340
|
33
|
+
#else
|
34
|
+
/* Almost a page of memory on most 64 bit machines: ((4096/8)-4)/3 */
|
35
|
+
#define DEFER_QUEUE_BLOCK_COUNT 168
|
36
|
+
#endif
|
37
|
+
#endif
|
28
38
|
|
29
39
|
/* *****************************************************************************
|
30
40
|
Data Structures
|
@@ -37,32 +47,142 @@ typedef struct {
|
|
37
47
|
void *arg2;
|
38
48
|
} task_s;
|
39
49
|
|
40
|
-
/*
|
41
|
-
typedef struct
|
42
|
-
task_s
|
43
|
-
struct
|
44
|
-
|
50
|
+
/* task queue block */
|
51
|
+
typedef struct queue_block_s {
|
52
|
+
task_s tasks[DEFER_QUEUE_BLOCK_COUNT];
|
53
|
+
struct queue_block_s *next;
|
54
|
+
size_t write;
|
55
|
+
size_t read;
|
56
|
+
unsigned char state;
|
57
|
+
} queue_block_s;
|
45
58
|
|
46
|
-
|
47
|
-
static task_node_s tasks_buffer[DEFER_QUEUE_BUFFER];
|
59
|
+
static queue_block_s static_queue;
|
48
60
|
|
49
61
|
/* the state machine - this holds all the data about the task queue and pool */
|
50
62
|
static struct {
|
51
|
-
/* the next task to be performed */
|
52
|
-
task_node_s *first;
|
53
|
-
/* a pointer to the linked list's tail, where the next task will be stored */
|
54
|
-
task_node_s **last;
|
55
|
-
/* a linked list for task nodes (staticly allocated) */
|
56
|
-
task_node_s *pool;
|
57
63
|
/* a lock for the state machine, used for multi-threading support */
|
58
64
|
spn_lock_i lock;
|
59
|
-
/*
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
/* current active block to pop tasks */
|
66
|
+
queue_block_s *reader;
|
67
|
+
/* current active block to push tasks */
|
68
|
+
queue_block_s *writer;
|
69
|
+
} deferred = {.reader = &static_queue, .writer = &static_queue};
|
70
|
+
|
71
|
+
/* *****************************************************************************
|
72
|
+
Internal Data API
|
73
|
+
***************************************************************************** */
|
74
|
+
|
75
|
+
#if DEBUG
|
76
|
+
static size_t count_alloc, count_dealloc;
|
77
|
+
#define COUNT_ALLOC spn_add(&count_alloc, 1)
|
78
|
+
#define COUNT_DEALLOC spn_add(&count_dealloc, 1)
|
79
|
+
#define COUNT_RESET \
|
80
|
+
do { \
|
81
|
+
count_alloc = count_dealloc = 0; \
|
82
|
+
} while (0)
|
83
|
+
#else
|
84
|
+
#define COUNT_ALLOC
|
85
|
+
#define COUNT_DEALLOC
|
86
|
+
#define COUNT_RESET
|
87
|
+
#endif
|
88
|
+
|
89
|
+
static inline void push_task(task_s task) {
|
90
|
+
spn_lock(&deferred.lock);
|
91
|
+
|
92
|
+
/* test if full */
|
93
|
+
if (deferred.writer->state &&
|
94
|
+
deferred.writer->write == deferred.writer->read) {
|
95
|
+
/* return to static buffer or allocate new buffer */
|
96
|
+
if (static_queue.state == 2) {
|
97
|
+
deferred.writer->next = &static_queue;
|
98
|
+
} else {
|
99
|
+
deferred.writer->next = malloc(sizeof(*deferred.writer->next));
|
100
|
+
COUNT_ALLOC;
|
101
|
+
if (!deferred.writer->next)
|
102
|
+
goto critical_error;
|
103
|
+
}
|
104
|
+
deferred.writer = deferred.writer->next;
|
105
|
+
deferred.writer->write = 0;
|
106
|
+
deferred.writer->read = 0;
|
107
|
+
deferred.writer->state = 0;
|
108
|
+
deferred.writer->next = NULL;
|
109
|
+
}
|
110
|
+
|
111
|
+
/* place task and finish */
|
112
|
+
deferred.writer->tasks[deferred.writer->write++] = task;
|
113
|
+
/* cycle buffer */
|
114
|
+
if (deferred.writer->write == DEFER_QUEUE_BLOCK_COUNT) {
|
115
|
+
deferred.writer->write = 0;
|
116
|
+
deferred.writer->state = 1;
|
117
|
+
}
|
118
|
+
spn_unlock(&deferred.lock);
|
119
|
+
return;
|
120
|
+
|
121
|
+
critical_error:
|
122
|
+
spn_unlock(&deferred.lock);
|
123
|
+
perror("ERROR CRITICAL: defer can't allocate task");
|
124
|
+
kill(0, SIGINT);
|
125
|
+
exit(errno);
|
126
|
+
}
|
127
|
+
|
128
|
+
static inline task_s pop_task(void) {
|
129
|
+
task_s ret = (task_s){NULL};
|
130
|
+
queue_block_s *to_free = NULL;
|
131
|
+
/* lock the state machine, to grab/create a task and place it at the tail
|
132
|
+
*/ spn_lock(&deferred.lock);
|
133
|
+
|
134
|
+
/* empty? */
|
135
|
+
if (deferred.reader->write == deferred.reader->read &&
|
136
|
+
!deferred.reader->state)
|
137
|
+
goto finish;
|
138
|
+
/* collect task */
|
139
|
+
ret = deferred.reader->tasks[deferred.reader->read++];
|
140
|
+
/* cycle */
|
141
|
+
if (deferred.reader->read == DEFER_QUEUE_BLOCK_COUNT) {
|
142
|
+
deferred.reader->read = 0;
|
143
|
+
deferred.reader->state = 0;
|
144
|
+
}
|
145
|
+
/* did we finish the queue in the buffer? */
|
146
|
+
if (deferred.reader->write == deferred.reader->read) {
|
147
|
+
if (deferred.reader->next) {
|
148
|
+
to_free = deferred.reader;
|
149
|
+
deferred.reader = deferred.reader->next;
|
150
|
+
} else {
|
151
|
+
deferred.reader->write = deferred.reader->read = deferred.reader->state =
|
152
|
+
0;
|
153
|
+
}
|
154
|
+
goto finish;
|
155
|
+
}
|
156
|
+
|
157
|
+
finish:
|
158
|
+
if (to_free == &static_queue) {
|
159
|
+
static_queue.state = 2;
|
160
|
+
}
|
161
|
+
spn_unlock(&deferred.lock);
|
162
|
+
|
163
|
+
if (to_free && to_free != &static_queue) {
|
164
|
+
free(to_free);
|
165
|
+
COUNT_DEALLOC;
|
166
|
+
}
|
167
|
+
return ret;
|
168
|
+
}
|
169
|
+
|
170
|
+
static inline void clear_tasks(void) {
|
171
|
+
spn_lock(&deferred.lock);
|
172
|
+
while (deferred.reader) {
|
173
|
+
queue_block_s *tmp = deferred.reader;
|
174
|
+
deferred.reader = deferred.reader->next;
|
175
|
+
if (tmp != &static_queue) {
|
176
|
+
COUNT_DEALLOC;
|
177
|
+
free(tmp);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
static_queue = (queue_block_s){.next = NULL};
|
181
|
+
deferred.reader = deferred.writer = &static_queue;
|
182
|
+
spn_unlock(&deferred.lock);
|
183
|
+
}
|
184
|
+
|
185
|
+
#define push_task(...) push_task((task_s){__VA_ARGS__})
|
66
186
|
|
67
187
|
/* *****************************************************************************
|
68
188
|
API
|
@@ -73,79 +193,29 @@ int defer(void (*func)(void *, void *), void *arg1, void *arg2) {
|
|
73
193
|
/* must have a task to defer */
|
74
194
|
if (!func)
|
75
195
|
goto call_error;
|
76
|
-
|
77
|
-
/* lock the state machine, to grab/create a task and place it at the tail */
|
78
|
-
spn_lock(&deferred.lock);
|
79
|
-
if (deferred.pool) {
|
80
|
-
task = deferred.pool;
|
81
|
-
deferred.pool = deferred.pool->next;
|
82
|
-
} else if (deferred.initialized) {
|
83
|
-
task = malloc(sizeof(task_node_s));
|
84
|
-
if (!task)
|
85
|
-
goto error;
|
86
|
-
} else
|
87
|
-
goto initialize;
|
88
|
-
|
89
|
-
schedule:
|
90
|
-
*deferred.last = task;
|
91
|
-
deferred.last = &task->next;
|
92
|
-
task->task.func = func;
|
93
|
-
task->task.arg1 = arg1;
|
94
|
-
task->task.arg2 = arg2;
|
95
|
-
task->next = NULL;
|
96
|
-
spn_unlock(&deferred.lock);
|
196
|
+
push_task(.func = func, .arg1 = arg1, .arg2 = arg2);
|
97
197
|
return 0;
|
98
198
|
|
99
|
-
error:
|
100
|
-
spn_unlock(&deferred.lock);
|
101
|
-
perror("ERROR CRITICAL: defer can't allocate task");
|
102
|
-
kill(0, SIGINT), exit(errno);
|
103
|
-
|
104
199
|
call_error:
|
105
200
|
return -1;
|
106
|
-
|
107
|
-
initialize:
|
108
|
-
/* initialize the task pool using all the items in the static buffer */
|
109
|
-
/* also assign `task` one of the tasks from the pool and schedule the task */
|
110
|
-
deferred.initialized = 1;
|
111
|
-
task = tasks_buffer;
|
112
|
-
deferred.pool = tasks_buffer + 1;
|
113
|
-
for (size_t i = 1; i < (DEFER_QUEUE_BUFFER - 1); i++) {
|
114
|
-
tasks_buffer[i].next = &tasks_buffer[i + 1];
|
115
|
-
}
|
116
|
-
tasks_buffer[DEFER_QUEUE_BUFFER - 1].next = NULL;
|
117
|
-
goto schedule;
|
118
201
|
}
|
119
202
|
|
120
203
|
/** Performs all deferred functions until the queue had been depleted. */
|
121
204
|
void defer_perform(void) {
|
122
|
-
|
123
|
-
|
124
|
-
restart:
|
125
|
-
spn_lock(&deferred.lock); /* remember never to perform tasks within a lock! */
|
126
|
-
tmp = deferred.first;
|
127
|
-
if (tmp) {
|
128
|
-
deferred.first = tmp->next;
|
129
|
-
if (!deferred.first)
|
130
|
-
deferred.last = &deferred.first;
|
131
|
-
task = tmp->task;
|
132
|
-
if (tmp >= tasks_buffer && tmp < tasks_buffer + DEFER_QUEUE_BUFFER) {
|
133
|
-
tmp->next = deferred.pool;
|
134
|
-
deferred.pool = tmp;
|
135
|
-
} else {
|
136
|
-
free(tmp);
|
137
|
-
}
|
138
|
-
spn_unlock(&deferred.lock);
|
139
|
-
/* perform the task outside the lock. */
|
205
|
+
task_s task = pop_task();
|
206
|
+
while (task.func) {
|
140
207
|
task.func(task.arg1, task.arg2);
|
141
|
-
|
142
|
-
goto restart;
|
208
|
+
task = pop_task();
|
143
209
|
}
|
144
|
-
spn_unlock(&deferred.lock);
|
145
210
|
}
|
146
211
|
|
147
|
-
/**
|
148
|
-
int defer_has_queue(void) {
|
212
|
+
/** Returns true if there are deferred functions waiting for execution. */
|
213
|
+
int defer_has_queue(void) {
|
214
|
+
return deferred.reader->read != deferred.reader->write;
|
215
|
+
}
|
216
|
+
|
217
|
+
/** Clears the queue. */
|
218
|
+
void defer_clear_queue(void) { clear_tasks(); }
|
149
219
|
|
150
220
|
/* *****************************************************************************
|
151
221
|
Thread Pool Support
|
@@ -172,7 +242,7 @@ error:
|
|
172
242
|
int defer_join_thread(void *p_thr) {
|
173
243
|
if (!p_thr)
|
174
244
|
return -1;
|
175
|
-
pthread_join(*(pthread_t *)p_thr, NULL);
|
245
|
+
pthread_join(*((pthread_t *)p_thr), NULL);
|
176
246
|
free(p_thr);
|
177
247
|
return 0;
|
178
248
|
}
|
@@ -214,8 +284,8 @@ static void *defer_worker_thread(void *pool_) {
|
|
214
284
|
signal(SIGPIPE, SIG_IGN);
|
215
285
|
/* the throttle replaces conditional variables for better performance */
|
216
286
|
size_t throttle = (pool->count) * DEFER_THROTTLE;
|
217
|
-
if (!throttle || throttle >
|
218
|
-
throttle =
|
287
|
+
if (!throttle || throttle > DEFER_THROTTLE_LIMIT)
|
288
|
+
throttle = DEFER_THROTTLE_LIMIT;
|
219
289
|
/* perform any available tasks */
|
220
290
|
defer_perform();
|
221
291
|
/* as long as the flag is true, wait for and perform tasks. */
|
@@ -304,6 +374,7 @@ void reap_child_handler(int sig) {
|
|
304
374
|
errno = old_errno;
|
305
375
|
}
|
306
376
|
|
377
|
+
#if !defined(NO_CHILD_REAPER) || NO_CHILD_REAPER == 0
|
307
378
|
/* initializes zombie reaping for the process */
|
308
379
|
inline static void reap_children(void) {
|
309
380
|
struct sigaction sa;
|
@@ -315,6 +386,7 @@ inline static void reap_children(void) {
|
|
315
386
|
kill(0, SIGINT), exit(errno);
|
316
387
|
}
|
317
388
|
}
|
389
|
+
#endif
|
318
390
|
|
319
391
|
/* a global process identifier (0 == root) */
|
320
392
|
static int defer_fork_pid_id = 0;
|
@@ -443,6 +515,16 @@ static void sample_task(void *unused, void *unused2) {
|
|
443
515
|
spn_unlock(&i_lock);
|
444
516
|
}
|
445
517
|
|
518
|
+
static void single_counter_task(void *unused, void *unused2) {
|
519
|
+
(void)(unused);
|
520
|
+
(void)(unused2);
|
521
|
+
spn_lock(&i_lock);
|
522
|
+
i_count++;
|
523
|
+
spn_unlock(&i_lock);
|
524
|
+
if (i_count < (1024 * 1024))
|
525
|
+
defer(single_counter_task, NULL, NULL);
|
526
|
+
}
|
527
|
+
|
446
528
|
static void sched_sample_task(void *unused, void *unused2) {
|
447
529
|
(void)(unused);
|
448
530
|
(void)(unused2);
|
@@ -490,10 +572,27 @@ void defer_test(void) {
|
|
490
572
|
}
|
491
573
|
end = clock();
|
492
574
|
fprintf(stderr,
|
493
|
-
"Deferless (direct call) counter: %lu cycles with i_count = %lu
|
494
|
-
|
575
|
+
"Deferless (direct call) counter: %lu cycles with i_count = %lu, "
|
576
|
+
"%lu/%lu free/malloc\n",
|
577
|
+
end - start, i_count, count_dealloc, count_alloc);
|
495
578
|
|
496
579
|
spn_lock(&i_lock);
|
580
|
+
COUNT_RESET;
|
581
|
+
i_count = 0;
|
582
|
+
spn_unlock(&i_lock);
|
583
|
+
start = clock();
|
584
|
+
defer(single_counter_task, NULL, NULL);
|
585
|
+
defer(single_counter_task, NULL, NULL);
|
586
|
+
defer_perform();
|
587
|
+
end = clock();
|
588
|
+
fprintf(stderr,
|
589
|
+
"Defer single thread, two tasks: "
|
590
|
+
"%lu cycles with i_count = %lu, %lu/%lu "
|
591
|
+
"free/malloc\n",
|
592
|
+
end - start, i_count, count_dealloc, count_alloc);
|
593
|
+
|
594
|
+
spn_lock(&i_lock);
|
595
|
+
COUNT_RESET;
|
497
596
|
i_count = 0;
|
498
597
|
spn_unlock(&i_lock);
|
499
598
|
start = clock();
|
@@ -502,10 +601,13 @@ void defer_test(void) {
|
|
502
601
|
}
|
503
602
|
defer_perform();
|
504
603
|
end = clock();
|
505
|
-
fprintf(stderr,
|
506
|
-
|
604
|
+
fprintf(stderr,
|
605
|
+
"Defer single thread: %lu cycles with i_count = %lu, %lu/%lu "
|
606
|
+
"free/malloc\n",
|
607
|
+
end - start, i_count, count_dealloc, count_alloc);
|
507
608
|
|
508
609
|
spn_lock(&i_lock);
|
610
|
+
COUNT_RESET;
|
509
611
|
i_count = 0;
|
510
612
|
spn_unlock(&i_lock);
|
511
613
|
start = clock();
|
@@ -519,12 +621,15 @@ void defer_test(void) {
|
|
519
621
|
defer_pool_wait(pool);
|
520
622
|
end = clock();
|
521
623
|
fprintf(stderr,
|
522
|
-
"Defer multi-thread (%d threads): %lu cycles with i_count = %lu
|
523
|
-
|
624
|
+
"Defer multi-thread (%d threads): %lu cycles with i_count = %lu, "
|
625
|
+
"%lu/%lu free/malloc\n",
|
626
|
+
DEFER_TEST_THREAD_COUNT, end - start, i_count, count_dealloc,
|
627
|
+
count_alloc);
|
524
628
|
} else
|
525
629
|
fprintf(stderr, "Defer multi-thread: FAILED!\n");
|
526
630
|
|
527
631
|
spn_lock(&i_lock);
|
632
|
+
COUNT_RESET;
|
528
633
|
i_count = 0;
|
529
634
|
spn_unlock(&i_lock);
|
530
635
|
start = clock();
|
@@ -533,31 +638,29 @@ void defer_test(void) {
|
|
533
638
|
}
|
534
639
|
defer_perform();
|
535
640
|
end = clock();
|
536
|
-
fprintf(stderr,
|
537
|
-
|
641
|
+
fprintf(stderr,
|
642
|
+
"Defer single thread (2): %lu cycles with i_count = %lu, %lu/%lu "
|
643
|
+
"free/malloc\n",
|
644
|
+
end - start, i_count, count_dealloc, count_alloc);
|
538
645
|
|
539
646
|
fprintf(stderr, "calling defer_perform.\n");
|
540
647
|
defer(text_task, NULL, NULL);
|
541
648
|
defer_perform();
|
542
|
-
fprintf(stderr,
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
pool_count++;
|
547
|
-
pos = pos->next;
|
548
|
-
}
|
549
|
-
fprintf(stderr, "defer pool count %lu/%d (%s)\n", pool_count,
|
550
|
-
DEFER_QUEUE_BUFFER,
|
551
|
-
pool_count == DEFER_QUEUE_BUFFER ? "pass" : "FAILED");
|
649
|
+
fprintf(stderr,
|
650
|
+
"defer_perform returned. i_count = %lu, %lu/%lu free/malloc\n",
|
651
|
+
i_count, count_dealloc, count_alloc);
|
652
|
+
|
552
653
|
fprintf(stderr, "press ^C to finish PID test\n");
|
553
654
|
defer(pid_task, "pid test", NULL);
|
554
655
|
if (defer_perform_in_fork(4, 64) > 0) {
|
555
656
|
fprintf(stderr, "* %d finished\n", getpid());
|
556
657
|
exit(0);
|
557
658
|
};
|
558
|
-
fprintf(stderr,
|
559
|
-
|
560
|
-
|
659
|
+
fprintf(stderr, "* Defer queue %lu/%lu free/malloc\n", count_dealloc,
|
660
|
+
count_alloc);
|
661
|
+
defer_clear_queue();
|
662
|
+
fprintf(stderr, "* Defer queue %lu/%lu free/malloc\n", count_dealloc,
|
663
|
+
count_alloc);
|
561
664
|
}
|
562
665
|
|
563
666
|
#endif
|