Ione v1.2, TLS support, and more
Ione v1.2 was released a little over a month ago and I forgot to announce it here. It now supports making TLS connections and starting TLS servers.
There’s also some additions to the futures API and the timer scheduler is no longer unusable.
TLS
Starting a TLS server or opening a TLS connection is now almost as easy as starting any server or opening any connection. The only thing you need to do extra is to create an OpenSSL::SSL::SSLContext:
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
ssl_context.cert_store = OpenSSL::X509::Store.new
ssl_context.cert_store.set_default_paths
f = reactor.connect('google.com', 443, ssl: ssl_context)
f.value # => an open TLS connection
Futures
Futures got some optimizations and new features. You can now compose operations with #then, which works like a mix between #map and #flat_map. It can be very convenient in situations where you may want to perform an additional asynchronous operation depending on the result of the previous operation. Before you had to use #flat_map and return a pre-resolved future in the case where you did not.
For example, maybe you’re loading something from a database, and when it’s not what you wanted, for example an empty result, you load something else, as a fallback. It might look like this:
f = load_the_thing
ff = f.flat_map do |result|
if result.empty?
load_fallback_thing
else
Future.resolved(result)
end
end
You need to use #flat_map because otherwise there’s no way to chain another asynchronous call – but it also means you must return a future even when you don’t want to chain. With #then you’re not required to:
f = load_the_thing
ff = f.then do |result|
if result.empty?
load_fallback_thing
else
result
end
end
The futures API also got two other additions: Future.traverse and Future.reduce. The former is a combination of Enumerable#map and Future.all, and encapsulates a pattern I use all of the time: mapping an array of queries or IDs to an array of futures that loads something from a databased for each query or ID, and then using Future.all to create a single future of all of the result – Future.traverse does that elegantly in one go:
f = Future.traverse(ids) do |id|
load_thing(id)
end
f.value # => [thing, thing, thing]
Finally, Future.reduce takes a list of futures and as their values become available it reduces them down to a single value, like Enumerable#reduce.
Timers
In previous versions of Ione the timer scheduler was extemely naïve. It was basically just an array of timers, in the order that they were scheduled. For each tick the reactor would scan through the array to find any expired timers and trigger their callbacks. It would also not remove the timers, just mark them as triggered, so the next tick would have to scan through the same array again. When a new timer was scheduled the array was compacted and all triggered timers were removed.
There were two reasons for this implementation: the first was that I wanted to avoid locking in the reactor thread, which was possible using this metod, and the second was that I just didn’t consider timers a very important feature and didn’t have time to make something better.
Not surprisingly timers were basically useless, and when people started using timeouts in cql-rb and the DataStax driver it got ugly. I had not planned to do anything more than TLS in v1.2, but when I realized how bad the situation was I read up on priority queues, implemented a heap data structure and replaced the scheduler. This delayed v1.2, but it was really worth it.
