Please notice that this change log contains changes for upcoming releases as well. Please refer to the current gem version to review the current release.
Update: Now using facil.io
edge (stripped down v.0.5.3).
Fix: (websocket
) fix #21, where a client's first message could have been lost due to long on_open
processing times. This was fixed by fragmenting the upgrade
event into two events, adding the facil_attach_locked
feature and attaching the new protocol before sending the response. Credit to @madsheep and @nilclass for exposing the issue and tracking it down to the on_open
callbacks.
Fix: (sock
) sockets created using the TCP/IP sock
library now use TCP_NODELAY
as the new default. This shouldn't be considered a breaking change as much as it should be considered a fix.
Fix: (http1
) HTTP/1.x now correctly initializes the udata
pointer to NULL fore each new request.
Fix: (defer
) a shutdown issue in defer_perform_in_fork
was detected by @cdkrot and his fix was implemented.
Update: Now using facil.io
v.0.5.2.
Fix: (from facil.io
) fix SIGTERM
handling, make sure sibling processes exit when a sibling dies.
Fix: fix static file service for X-Sendfile
as well as static error file fallback pages (404.html etc').
Fix: fixed an issue related to Ruby 2.3 optimizations of String management (an issue that didn't seem to effect Ruby 2.4). This fix disables the recyclable buffer implemented for the on_message
Websocket callback. The callback will now receive a copy of the buffer (not the buffer itself), so there is no risk of collisions between the network buffer (managed in C) and the on_message(data)
String (managed by Ruby).
Fix: fixed a possible issue in fragmented pipelined Websocket messages.
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.
Fix: fixed a possible issue with highjacking which might cause the server to hang.
Fix: postpone warmup in fear of abuse and collisions when using fork
. i.e., during warmup, an application might perform actions that conflict with fork
and worker initialization, such as creating a database connection pool during warmup, or maybe spawning a thread. Now warmup
is postponed until after worker processes are up and running, resulting in a per-process warmup rather than a per-cluster warmup.
Fix move the rake-compiler
dependency to "development" instead of "runtime". Credit to Luis Lavena (@luislavena) for exposing the issue (#19).
Braking change: Some of the API was changed / updated, hopefully simplified.
DEPRECTAION / Braking change: The websocket#write_each
function is gone. Future (planned) support for a Pub/Sub API would have caused confusion and since it's barely used (probably only there as a benchmarking proof of concept) it was removed.
Update: Now using facil.io
v.0.5.0 The extended features include the following listed features.
Fixes: This was such a big rewrite, I suspect half the fixes I want to list are things I broke during the rewrite... but there were plenty of fixes.
Feature: Iodine now support native Websocket Pub/Sub, with an example in the examples
folder. i.e.:
# Within a Websocket connection:
subscribe "chat"
publish "chat", "Iodine is here!"
Feature: Iodine's Pub/Sub API supports both direct client messages and server filtered messages. i.e.
# Within a Websocket connection:
subscribe("chat-server") {|msg| write "Notice: #{msg}" }
# v.s
subscribe("chat-client")
publish "chat-server", "Iodine is here!"
Feature: Iodine's Pub/Sub API includes support for home made Pub/Sub Engines connecting Iodine to your Pub/Sub service of choice.
Feature: Iodine's Pub/Sub support includes a Process Cluster engine (pub/sub to all Websockets sharing the process cluster) as well as a Single Process engine (pub/sub to all websockets supporting a single process).
Feature: Iodine's Pub/Sub support includes a native Redis Pub/Sub engine. The parser is written from the ground up in C to keep the Iodine licensing as MIT. It's young, so keep your eyes pealed and submit any issues you encounter.
Feature + Breaking Change: Iodine now support multiple HTTP servers at once. i.e.:
# `Iodine::HTTP.listen` initializes an HTTP service in the C and system levels, so it can't be changed once initialized.
Iodine::HTTP.listen port: 3000, app: my_app1
Iodine::HTTP.listen port: 3000, app: my_app2, public: "./www"
Iodine.start
Update: Now using facil.io
v.0.4.3. This fixes some delays in websocket packet flushing (latency), as well as other internal polishes. It also promises some possible future feature extensions that could add a major performance boost.
Fix: (sock
) Fixed an issue with the sendfile
implementation on macOS and BSD, where medium to large files wouldn't be sent correctly.
Minor changes: This release incorporates some more minor changes from the facil.io
0.4.2 update.
Fix: (sock
, facil
, bscrypt) Add missing static
keywords to prevent state collisions with other libraries.
Update: Now using facil.io
v.0.4.1
Fix: (from facil.io
) fixed the default response Date
(should have been "now", but wasn't initialized).
Compatibility: (from facil.io
) Now checks for HTTP/1.0 clients to determine connection persistence.
Compatibility: (from facil.io
) Added spaces after header names (:
=> :
), since some parsers don't seem to read the RFC.
Fix: (from facil.io
) fixed thread throttling for better energy conservation.
Fix: (from facil.io
) fixed stream response logging.
Update: Follow facil.io
's update for healthier thread throttling and energy consumption.
Fix: Fixed a minor issue with the logging of responses where the size of the response is unknown (streamed).
Gem Specification update: We updated the gem specification to allow for Rack 1.x users and to update the gem description.
facil.io
C Core Update: The C library core that drives Iodine facil.io
was updated to version 0.4.0 and Iodine follows closely on the heels of this update. The transition was easy enough and the API remains unchanged... but because the performance gain was so big and because it's a new code base, we opted to bump the minor release version.
Performance: Enhanced Performance for single threaded / blocking applications by adding a dedicated IO thread. This is related to issue #14.
Update: iodine can now run as a basic HTTP static file server without a Ruby application (no config.ru
) when the -www
option is used from the command line.
Fix: Fixed typo in logging and code comments, credit to @jmoriau in PR #13.
Fix: fixed the experimental each_write
. An issue was found where passing a block might crash Iodine, since the block will be freed by the GC before Iodine was done with it. Now the block is correctly added to the object Registry, preventing premature memory deallocation.
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.
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. This deprecation is now enforced.
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
layer. It should be noted that Chrome is the only browser where this issue could be invoked for testing.
Credit: credit to Elia Schito (@elia) and Augusts Bautra (@Epigene) for fixing parts of the documentation (PR #11 , #12).
Fix: removed mempool
after it failed some stress and concurrency tests.
Fix: C layer memory pool had a race-condition that could have caused, in some unlikely events, memory allocation failure for Websocket protocol handlers. This had now been addressed and fixed.
Experimental feature: added an each_write
feature to allow direct write
operations that write data to all open Websocket connections sharing the same process (worker). When this method is called without the optional block, the data will be sent without the need to acquire the Ruby GIL.
Update: lessons learned from facil.io
have been implemented for better compatibility of Iodine's core C layer.
Update: added documentation and an extra helper method to set a connection's timeout when using custom protocols (Iodine as an EventMachine alternative).
C Layer Update updated the facil.io
library used, to incorporate the following fixes / update:
-
Better cross platform compilation by avoiding some name-space clashes. i.e, fixes a name clash with the
__used
directive / macro, where some OSs (i.e. CentOS) used a similar directive with different semantics. -
Reviewed and fixed some signed vs. unsigned integer comparisons.
-
Smoother event scheduling by increasing the event-node's pool size.
-
Smoother thread concurrency growth by managing thread
nanosleep
times as thread count dependent. -
Cleared out "unused variable" warnings.
-
Streamlined the
accept
process to remove a double socket's data clean-up. -
SERVER_DELAY_IO
is now implemented as an event instead of a stack frame. -
Fixed a possible Linux
sendfile
implementation issue where sometimes errors wouldn't be caught orsendfile
would be called past a file's limit (edge case handling). -
bscrypt
random generator (wheredev/random
is unavailable) should now provide more entropy.
Fix: fixed a gcc-4.8 compatibility issue that prevented iodine 0.2.8 from compiling on Heroku's cedar-14 stack. This was related to missing system include files in gcc-4.8. It should be noted that Heroku's stack and compiler (which utilizes Ubuntu 14) has known issues and / or limited support for some of it's published features... but I should have remembered that before releasing version 0.2.8... sorry about that.
Memory Performance: The Websocket connection Protocol now utilizes both a C level memory pool and a local thread storage for temporary data. This helps mitigate possible memory fragmentation issues related to long running processes and long-lived objects. In addition, the socket read
buffer was moved from the protocol object to a local thread storage (assumes pthreads and not green threads). This minimizes the memory footprint for each connection (at the expense of memory locality) and should allow Iodine to support more concurrent connections using less system resources. Last, but not least, the default message buffer per connection starts at 4Kb instead of 16Kb (grows as needed, up to Iodine::Rack.max_msg_size
), assuming smaller messages are the norm.
Housekeeping: Cleaned up some code, removed old files, copied over the latest facil.io
library. There's probably some more housekeeping left to perform, especially anywhere where documentation is concerned. I welcome help with documentation.
Minor Fix: fixed an issue where a negative number of processes or threads would initiate a very large number of forks, promoting a system resource choke. Limited the number of threads (1023) and processes (127).
Update: Automated the number of processes (forks) and threads used when these are not explicitly specified. These follow the number of cores / 2.
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).
Fix:: fix for issue #9 (credit to Jack Christensen for exposing the issue) caused by an unlocked critical section's "window of opportunity" that allowed asynchronous Websocket each
blocks to run during the tail of the Websocket handshake (while the on_open
callback was running in parallel).
Minor Fix: Fix Iodine::Rack's startup message's fprintf
call to fit correct argument sizes (Linux warnings).
Minor Fix: Patched Iodine against Apple's broken getrlimit
on macOS. This allows correct auto-setting of open file limits for the socket layer.
Minor Fix: Fixed the processor under-utilization warning, where "0" might be shown for the number processes instead of "1".
Update: Added support for the env
keys HTTP_VERSION
and SERVER_PROTOCOL
to indicate the HTTP protocol version. Iodine implements an HTTP/1.1 server, so versions aren't expected to be higher than 1.x.
Update: Iodine::Rack startup messages now include details regarding open file limits imposed by the OS (open file limits control the maximum allowed concurrent connections and other resource limits).
Update: The write
system call is now deferred when resources allow, meaning that (as long as the write
buffer isn't full) write
is not only non-blocking, but it's performed as a separate event, outside of the Ruby GIL.
Update: The global socket write
buffer was increased to ~16Mb (from ~4Mb), allowing for more concurrent write
operations. However, the write
buffer is still limited and write
might block while the buffer is full. Blocking and "hanging" the server until there's enough room in the buffer for the requested write
will slow the server down while keeping it healthy and more secure. IMHO, it is the lesser of two evils.
Update The static file service now supports ETag
caching, sending a 304 (not changed) response for valid ETags.
Update: A performance warning now shows if the CPUs are significantly under-utilized (less than half are used) of if too many are utilized (more than double the amount of CPUs), warning against under-utilization or excessive context switching (respectively).
Notice: The Rack Websocket Draft does not support the each
and defer
methods. Although I tried to maintain these as part of the draft, the community preferred to leave the implementation of these to the client (rather then the server). If collisions occur, these methods might be removed in the future.
Update: Websockets now support the has_pending?
method and on_ready
callback, as suggested by the Rack Websocket Draft.
Update: deprecated the websocket method uuid
in favor of conn_id
, as suggested by the Rack Websocket Draft.
Fix: fixed an issue were the server would crash when attempting to send a long enough websocket message.
This version is a total rewrite. The API is totally changed, nothing stayed.
Iodine is now written in C, as a C extension for Ruby. The little, if any, ruby code written is just the fluff and feathers.
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
).
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.
Logs: Log the number of threads utilized when starting up the server.
Update/Fix: Updated the x-forwarded-for
header recognition, to accommodate an Array formatting sometimes used (["ip1", "ip2", ...]
).
Update: native support for the Forwarded
header Http.
API Changes: Iodine::Http.max_http_buffer
was replaced with Iodine::Http.max_body_size
, for a better understanding of the method's result.
Update: added the go_away
method to the Http/1 peorotocol, for seamless connection closeing across Http/2, Http/1 and Websockets.
Update: The request now has the shortcut method Request#host_name
for accessing the host's name (without the port part of the string).
Credit: thanks you @frozenfoxx for going through the readme and fixing my broken grammer.
Fix: fixed an issue where multiple Pings might get sent when pinging takes time. Now pings are exclusive (run within their own Mutex).
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.
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.
- Remember that sessions are never really safe, no matter how much we guard them. Session hijacking is far too easy. This is why Iodine stores the session data locally and not within the session cookie. This is also why you should review any authentication before performing sensitive tasks based on session stored authentication data.
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.
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).
Update: Websockets now support the on_ping
callback, which will be called whenever a ping was sent without error.
Update: the Response now supports redirect_to
for both permanent and temporary redirection, with an optional flash
cookie setup.
Performance: the Protocol class now recycles the data string as a thread global socket buffer (different threads have different buffer strings), preventing excessive malloc
calls by the Ruby interpreter. To keep the data
(in on_message(data)
) past the on_message
method's scope, be sure to duplicate it using data.dup
, or the string's buffer will be recycled.
Change: Session cookie lifetime is now limited to the browser's session. The local data will still persist until the tmp-folder is cleared (when using session file storage).
Fix: renamed the SSL session token so that the SSL session id isn't lost when a non-secure session is used.
Fix: The flash
cookie-jar will now actively prevent Symbol and String keys from overlapping.
Compatibility: minor fixes and changes in preperation for Ruby 2.3.0. These may affect performance due to slower String initialization times.
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}
.
Security: set the HttpOnly flag for session id cookies.
Fix: fixed the Rack server Handler, which was broken in version 0.1.10.
Fix: make sure the WebsocketClient doesn't automatically renew the connection when the connection was manually closed by the client.
Performance: faster TimedEvent clearing when manually stopped. Minor improvements to direct big-file sending (recycle buffer to avoid malloc).
Fix: WebsocketClient connection renewal will now keep the same WebsocketClient instance object.
Update Creating a TimedEvent before Iodine starts running will automatically 'nudge' Iodine into "Task polling" mode, cycling until the user signals a stop.
Update: repeatedly calling Iodine.force_start!
will now be ignored, as might have been expected. Once Iodine had started, force_start!
cannot be called until Iodine had finished (and even than, Iodine might never be as fresh nor as young as it was).
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).
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.
Fix fixed an issue where sometimes extractin the Http response's body would fail (if body is nil
).
Feature: session objects are now aware of the session id. The seesion id is available by calling response.session.id
Fix fixed an issue where Http streaming wasn't chunk encoding after connection error handling update.
Fix fixed an issue where Http streaming would disconnect while still processing. Streaming timeout now extended to 15 seconds between response writes.
Removed a deprecation notice for blocking API. Client API will remain blocking due to use-case requirements.
Fix: fixed an issue where a session key-value pair might not get deleted when using session.delete key
and the key
is not a String object. Also, now setting a key's value to nil
should delete the key-value pair.
Fix: fixed an issue where WebsocketClient wouldn't mask outgoing data, causing some servers to respond badly.
Performance: minor performance improvements to the websocket parser, for unmasking messages.
Deprecation notice:
(removed after reviewing use-cases).
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).
Fix: Fixed an issue where Iodine might take a long time to shut down after a Fatal Error during the server initialization.
Fix: fixed an issue with where the WebsocketClient#on_close wouldn't be called for a renewable Websocket connection during shutdown.
Fix: fixed an issue where a protocol's #on_close callback wouldn't be called if the Iodine server receives a shutdown signal.
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).
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.
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).
Fix: Fixed an issue where slow processing of Http/1 requests could cause timeout disconnections to occur while the request is being processed.
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.
Change/Security: Http upload limits are now enforced. The current default limit is about ~0.5GB.
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.
Logging: Better Http error logging and recognition.
First actual release:
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.
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.
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.
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).
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).
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).
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.
The gem is available as open source under the terms of the MIT License.