README.md in async-0.13.0 vs README.md in async-0.14.0

- old
+ new

@@ -1,8 +1,8 @@ # Async -Asynchronous I/O framework for Ruby based on [nio4r] and [timers]. +Async is a composable asynchronous I/O framework for Ruby based on [nio4r] and [timers]. [timers]: https://github.com/socketry/timers [nio4r]: https://github.com/socketry/nio4r [![Build Status](https://secure.travis-ci.org/socketry/async.svg)](http://travis-ci.org/socketry/async) @@ -37,57 +37,73 @@ $ gem install async ## Usage -Implementing an asynchronous client/server is easy: +`Async::Reactor` is the top level IO reactor, and runs `Async::Task`s asynchronously. The reactor itself is not thread-safe, so you'd typically have one reactor per thread. -```ruby -#!/usr/bin/env ruby +An `Async::Task` runs using a `Fiber` and blocking operations e.g. `sleep`, `read`, `write` yield control until the operation can succeed. -require 'async' -require 'async/tcp_socket' +The design of this core library is deliberately simple in scope. Additional libraries provide asynchronous networking, process management, etc. It's likely you will prefer to depend on `async-io` for actual wrappers around `IO` and `Socket`. -def echo_server - Async::Reactor.run do |task| - # This is a synchronous block within the current task: - task.with(TCPServer.new('localhost', 9000)) do |server| - - # This is an asynchronous block within the current reactor: - task.reactor.with(server.accept) do |client| - data = client.read(512) - - task.sleep(rand) - - client.write(data) - end while true - end - end +### Main Entry Points + +#### `Async::Reactor.run` + +The highest level entry point is `Async::Reactor.run`. It's useful if you are building a library and you want well defined asynchronous semantics. + +```ruby +def run_server + Async::Reactor.run do |task| + # ... acccept connections + end end +``` -def echo_client(data) - Async::Reactor.run do |task| - Async::TCPServer.connect('localhost', 9000) do |socket| - socket.write(data) - puts "echo_client: #{socket.read(512)}" - end - end +If `Async::Reactor.run(&block)` happens within an existing reactor, it will schedule an asynchronous task and return. If `Async::Reactor.run(&block)` happens outside of an existing reactor, it will create a reactor, schedule the asynchronous task, and block until it completes. The task is scheduled by calling `Async::Reactor.async(&block)`. + +This puts the power into the hands of the client, who can either have blocking or non-blocking behaviour by explicitly wrapping the call in a reactor (or not). The cost of using `Async::Reactor.run` is minimal for initialization/server setup, but is not ideal for per-connection tasks. + +#### `Async::Task#async` + +If you can guarantee you are running within a task, and have access to it (e.g. via an argument), you can efficiently schedule new tasks using the `Async::Task#async(&block)` method. + +```ruby +def do_request(task: Task.current) + task.async do + # ... do some actual work + end end +``` +This method effectively creates a child task. It's the most efficient way to schedule a task. The task is executed until the first blocking operation, at which point it will yield control and `#async` will return. The result of this method is the task itself. + +### Reactor Tree + +`Async::Reactor` and `Async::Task` form nodes in a tree. Reactors and tasks can spawn children tasks. When you invoke `Async::Reactor#async`, the parent task is determined by calling `Async::Task.current?` which uses fiber local storage. A slightly more efficient method is to use `Async::Task#async`, which uses `self` as the parent task. + +When invoking `Async::Reactor#stop`, you will stop *all* children tasks of that reactor. Tasks will raise `Async::Interrupt` if they are in a blocking operation. In addition, it's possible to only stop a sub-tree by issuing `Async::Task#stop`, which will stop that task and all it's children (recursively). When you design a server, you should return the task back to the caller. They can use this task to stop the server if needed, independently of any other unrelated tasks within the reactor, and it will correctly clean up all related tasks. + +### Resource Management + +In order to ensure your resources are cleaned up correctly, make sure you wrap resources appropriately, e.g.: + +```ruby Async::Reactor.run do - # Start the echo server: - server = echo_server - - 5.times.collect do |i| - echo_client("Hello World #{i}") - end.each(&:wait) # Wait until all clients are finished. - - # Terminate the server and all tasks created within it's async scope: - server.stop + begin + socket = connect(remote_address) # May raise Async::Interrupt so socket could be nil + + socket.write(...) # May raise Async::Interrupt + socket.read(...) # May raise Async::Interrupt + ensure + socket.close if socket + end end ``` +As tasks run synchronously until they yield back to the reactor, you can guarantee this model works correctly. While in theory `IO#autoclose` allows you to automatically close file descriptors when they go out of scope via the GC, it may produce unpredictable behavour (exhaustion of file descriptors, flushing data at odd times), so it's not recommended. + ## Supported Ruby Versions This library aims to support and is [tested against][travis] the following Ruby versions: @@ -117,10 +133,12 @@ 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## See Also +- [async-io](https://github.com/socketry/async-io) — Asynchronous networking and sockets. - [async-dns](https://github.com/socketry/async-dns) — Asynchronous DNS resolver and server. +- [async-rspec](https://github.com/socketry/async-rspec) — Shared contexts for running async specs. - [rubydns](https://github.com/ioquatix/rubydns) — A easy to use Ruby DNS server. ## License Released under the MIT license.