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.

Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -33
  3. data/README.md +7 -5
  4. data/ext/iodine/base64.c +12 -0
  5. data/ext/iodine/defer.c +211 -108
  6. data/ext/iodine/defer.h +7 -0
  7. data/ext/iodine/facil.c +5 -1
  8. data/ext/iodine/facil.h +1 -1
  9. data/ext/iodine/fio2resp.c +19 -30
  10. data/ext/iodine/fio2resp.h +2 -1
  11. data/ext/iodine/fio_cli_helper.c +2 -2
  12. data/ext/iodine/fiobj.h +11 -624
  13. data/ext/iodine/fiobj_ary.c +65 -26
  14. data/ext/iodine/fiobj_ary.h +106 -0
  15. data/ext/iodine/fiobj_hash.c +175 -115
  16. data/ext/iodine/fiobj_hash.h +128 -0
  17. data/ext/iodine/fiobj_internal.c +189 -0
  18. data/ext/iodine/{fiobj_types.h → fiobj_internal.h} +126 -136
  19. data/ext/iodine/fiobj_json.c +161 -207
  20. data/ext/iodine/fiobj_json.h +43 -0
  21. data/ext/iodine/fiobj_numbers.c +53 -35
  22. data/ext/iodine/fiobj_numbers.h +49 -0
  23. data/ext/iodine/fiobj_primitives.c +103 -70
  24. data/ext/iodine/fiobj_primitives.h +55 -0
  25. data/ext/iodine/fiobj_str.c +171 -59
  26. data/ext/iodine/fiobj_str.h +113 -0
  27. data/ext/iodine/fiobj_sym.c +46 -124
  28. data/ext/iodine/fiobj_sym.h +60 -0
  29. data/ext/iodine/fiobject.c +589 -0
  30. data/ext/iodine/fiobject.h +276 -0
  31. data/ext/iodine/pubsub.h +1 -1
  32. data/ext/iodine/resp.c +2 -2
  33. data/ext/iodine/siphash.c +11 -0
  34. data/ext/iodine/spnlock.inc +7 -5
  35. data/ext/iodine/websocket_parser.h +44 -7
  36. data/lib/iodine/version.rb +1 -1
  37. metadata +13 -8
  38. data/ext/iodine/fiobj_alloc.c +0 -81
  39. data/ext/iodine/fiobj_generic.c +0 -260
  40. data/ext/iodine/fiobj_io.c +0 -58
  41. data/ext/iodine/fiobj_misc.c +0 -213
  42. data/ext/iodine/fiobj_tests.c +0 -474
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8110abd50ee8b728afb0b0bba32010636acae150
4
- data.tar.gz: ccc3e4a8e25014e15bae41aadc343aa04c596586
3
+ metadata.gz: 8fae6d0a20c82608a04c0a8845ccc4992810ac6c
4
+ data.tar.gz: eec0b8600e70903a2433022c7d236c8a315f08d9
5
5
  SHA512:
6
- metadata.gz: 73e41e508c1b66928402ff515890394ddf36b62bbc11c309d924306310ed72ff67d3c0e0f0562f97ee22b235a71a91d32ff2a46e2c7e839ec4f60b2f2a1431b4
7
- data.tar.gz: 34415df07c8f045a4f287334e012d9678bb1ad44e8355872183ee06df01e0fcbd7a5c17bba5f8648fbf0bf86762f2c6cc67cd7b3fbe060a4318d0679f6f87a30
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
- #### Next
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 highjacking which might cause the server to hang.
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 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.
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 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.
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 websocket method `uuid` in favor of `conn_id`, as suggested by the [Rack Websocket Draft](https://github.com/rack/rack/pull/1107).
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**: 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.
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 Http.
419
+ **Update**: native support for the `Forwarded` header HTTP.
392
420
 
393
- **API Changes**: `Iodine::Http.max_http_buffer` was replaced with `Iodine::Http.max_body_size`, for a better understanding of the method's result.
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 Http/1 peorotocol, for seamless connection closeing across Http/2, Http/1 and Websockets.
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 grammer.
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**: 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.
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**: 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 (hopefuly) prevents excess `malloc` calls by the interperter.
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 websocket availability for broadcasting (delay from connection to availability might occure until IO is registered).
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 preperation for Ruby 2.3.0. These may affect performance due to slower String initialization times.
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 HttpOnly flag for session id cookies.
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 behald of any given Protocol object ("yes" to concurrency between objects but "no" to concurrency within objects).
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 extractin the Http response's body would fail (if body is `nil`).
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 seesion id is available by calling `response.session.id`
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 Http streaming wasn't chunk encoding after connection error handling update.
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 Http streaming would disconnect while still processing. Streaming timeout now extended to 15 seconds between response writes.
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 websocket parser, for unmasking messages.
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 variaty of players (Safari requires Range request support for it's media player).
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 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).
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 inadvert potential change to the original POSTed data.
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` didn 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).
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 Http/1 requests could cause timeout disconnections to occur while the request is being processed.
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**: Http upload limits are now enforced. The current default limit is about ~0.5GB.
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 Http error logging and recognition.
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 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.
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 excecuted 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).
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 deployemnt preferences were favored over adjustability or features. This means that some command-line arguments are automatically recognized (such as the `-p <port>` argument) and thet Iodine assumes a single web service per script/process (whereas GReactor and GRHttp allowed multiple listening sockets).
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 heavey load, a restart wasn't required and memory consumption didn't show any increase after the warmup period.
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
- out = [404, {"Content-Length" => "10".freeze}.freeze, ["Not Found.".freeze].freeze].freeze
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'.freeze => 'text/html'.freeze,
103
- 'Content-Length'.freeze => request.path_info.length.to_s },
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 smaller concurrency levels.
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 smoothly.
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
- /* a single linked list for tasks */
41
- typedef struct task_node_s {
42
- task_s task;
43
- struct task_node_s *next;
44
- } task_node_s;
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
- /* static memory allocation for a task node buffer */
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
- /* a flag indicating whether the task pool was initialized */
60
- unsigned char initialized;
61
- } deferred = {.first = NULL,
62
- .last = &deferred.first,
63
- .pool = NULL,
64
- .lock = 0,
65
- .initialized = 0};
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
- task_node_s *task;
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
- task_node_s *tmp;
123
- task_s task;
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
- /* I used `goto` to optimize assembly instruction flow. maybe it helps. */
142
- goto restart;
208
+ task = pop_task();
143
209
  }
144
- spn_unlock(&deferred.lock);
145
210
  }
146
211
 
147
- /** returns true if there are deferred functions waiting for execution. */
148
- int defer_has_queue(void) { return deferred.first != NULL; }
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 > 1572864UL)
218
- throttle = 1572864UL;
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\n",
494
- end - start, i_count);
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, "Defer single thread: %lu cycles with i_count = %lu\n",
506
- end - start, i_count);
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\n",
523
- DEFER_TEST_THREAD_COUNT, end - start, i_count);
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, "Defer single thread (2): %lu cycles with i_count = %lu\n",
537
- end - start, i_count);
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, "defer_perform returned. i_count = %lu\n", i_count);
543
- size_t pool_count = 0;
544
- task_node_s *pos = deferred.pool;
545
- while (pos) {
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
- " === Defer pool memory footprint %lu X %d = %lu bytes ===\n",
560
- sizeof(task_node_s), DEFER_QUEUE_BUFFER, sizeof(tasks_buffer));
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