Skip to content

Commit

Permalink
Initial prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Apr 6, 2017
0 parents commit a93f251
Show file tree
Hide file tree
Showing 25 changed files with 1,027 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .gitignore
@@ -0,0 +1,21 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
.tags*
documentation/run/*
documentation/public/code/*
.rspec_status
5 changes: 5 additions & 0 deletions .rspec
@@ -0,0 +1,5 @@
--color
--format documentation
--backtrace
--require spec_helper
--warnings
14 changes: 14 additions & 0 deletions .travis.yml
@@ -0,0 +1,14 @@
language: ruby
sudo: false
dist: trusty
cache: bundler
rvm:
- 2.2.6
- 2.3.3
- 2.4.0
- jruby-head
- ruby-head
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
19 changes: 19 additions & 0 deletions Gemfile
@@ -0,0 +1,19 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in utopia.gemspec
gemspec

group :development do
gem 'pry'
gem 'guard-rspec'

gem 'yard'
end

group :test do
gem 'benchmark-ips'
gem 'ruby-prof', platforms: :mri

gem 'simplecov'
gem 'coveralls', require: false
end
10 changes: 10 additions & 0 deletions Guardfile
@@ -0,0 +1,10 @@
# frozen_string_literal: true

directories %w(lib spec)
clearing :on

guard :rspec, cmd: "bundle exec rspec" do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch("spec/spec_helper.rb") { "spec" }
end
79 changes: 79 additions & 0 deletions README.md
@@ -0,0 +1,79 @@
# Async

Asynchronous I/O framework for Ruby based on [nio4r] and [timers].

[timers]: https://github.com/socketry/timers
[nio4r]: https://github.com/socketry/nio4r

## Installation

Add this line to your application's Gemfile:

```ruby
gem "async"
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install async

## Supported Ruby Versions

This library aims to support and is [tested against][travis] the following Ruby
versions:

* Ruby 2.2.6+
* Ruby 2.3
* Ruby 2.4
* JRuby 9.1.6.0+

If something doesn't work on one of these versions, it's a bug.

This library may inadvertently work (or seem to work) on other Ruby versions,
however support will only be provided for the versions listed above.

If you would like this library to support another Ruby version or
implementation, you may volunteer to be a maintainer. Being a maintainer
entails making sure all tests run and pass on that implementation. When
something breaks on your implementation, you will be responsible for providing
patches in a timely fashion. If critical issues for a particular implementation
exist at the time of a major release, support for that Ruby version may be
dropped.

[travis]: http://travis-ci.org/socketry/async

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request

## License

Released under the MIT license.

Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
6 changes: 6 additions & 0 deletions Rakefile
@@ -0,0 +1,6 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:test)

task :default => :test
33 changes: 33 additions & 0 deletions async.gemspec
@@ -0,0 +1,33 @@
# -*- encoding: utf-8 -*-
require_relative 'lib/async/version'

Gem::Specification.new do |spec|
spec.name = "async"
spec.version = Async::VERSION
spec.authors = ["Samuel Williams"]
spec.email = ["samuel.williams@oriontransfer.co.nz"]
spec.description = <<-EOF
Async provides a modern asynchronous I/O framework for Ruby, based
on nio4r. It implements the reactor pattern, providing both IO and timer
based events.
EOF
spec.summary = "Async is an asynchronous I/O framework based on nio4r."
spec.homepage = "https://github.com/socketry/async"
spec.license = "MIT"

spec.files = `git ls-files`.split($/)
spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.has_rdoc = "yard"

spec.required_ruby_version = ">= 2.2.6"

spec.add_runtime_dependency "nio4r", "~> 2"
spec.add_runtime_dependency "timers", "~> 4.1"

spec.add_development_dependency "bundler", "~> 1.3"
spec.add_development_dependency "process-daemon", "~> 1.0.0"
spec.add_development_dependency "rspec", "~> 3.4.0"
spec.add_development_dependency "rake"
end
41 changes: 41 additions & 0 deletions examples/aio.rb
@@ -0,0 +1,41 @@
#!/usr/bin/env ruby

require 'async'

reactor = Async::Reactor.new

puts "Creating server"
server = TCPServer.new("localhost", 6777)

REPEATS = 10

reactor.async(server) do |server|
REPEATS.times do |i|
puts "Accepting peer on server #{server}"
peer = server.accept

puts "Sending data to peer"
peer << "data #{i}"
peer.shutdown
end

puts "Server finished"
end

REPEATS.times do |i|
# This aspect of the connection is synchronous.
puts "Creating client #{i}"
client = TCPSocket.new("localhost", 6777)

reactor.async(client) do |client|
puts "Reading data on client #{i}"
puts client.read(1024)
end
end

reactor.timers.after(1) do
puts "Reactor timed out!"
reactor.stop
end

reactor.run_forever
24 changes: 24 additions & 0 deletions lib/async.rb
@@ -0,0 +1,24 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative "async/version"
require_relative "async/logger"
require_relative "async/reactor"
require_relative "async/wrap"
105 changes: 105 additions & 0 deletions lib/async/context.rb
@@ -0,0 +1,105 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative 'wrap'

require 'fiber'

module Async
class Interrupt < Exception
end

class Context
extend Forwardable

def initialize(ios, reactor, &block)
@ios = ios.collect{|io| Async::Wrap[io, self]}
@reactor = reactor

@fiber = Fiber.new do
set!

begin
yield(*@ios, self)
rescue Interrupt
Async.logger.debug("Context #{self} interrupted: #{$!}")
ensure
close
end
end
end

def_delegators :@reactor, :timeout, :sleep

def run
@fiber.resume

return @fiber
end

def stop!
if @fiber.alive?
exception = Interrupt.new("Stop right now!")
@fiber.resume(exception)
end
end

attr :ios
attr :reactor

def with(io)
wrapper = Async::Wrap[io, self]

yield wrapper
ensure
wrapper.close
io.close
end

def register(io, interests)
@reactor.register(io, interests)
end

def resolve(name)
warn "Name resolution is not implemented for #{name}"

return name
end

def self.get!
Thread.current[:async_context] or raise RuntimeError, "No async context available!"
end

def self.reactor!
get!.reactor
end

private

def close
@ios.each(&:close)
end

def set!
# This is actually fiber-local:
Thread.current[:async_context] = self
end
end
end
29 changes: 29 additions & 0 deletions lib/async/logger.rb
@@ -0,0 +1,29 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require 'logger'

module Async
class << self
attr :logger
end

@logger = Logger.new($stderr, level: Logger::DEBUG)
end

0 comments on commit a93f251

Please sign in to comment.