async 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0c677beaeb3fb10276210be29052382b077ac6ce
4
+ data.tar.gz: bc0719a32fbffef3861f111d7a245c45b6e24786
5
+ SHA512:
6
+ metadata.gz: ad2df57e9e6ee7a4280a1b64c6429f6e8c69c2de40dcdd5cb9091c0e8f8b7debdd9f88d4ff3e049900aa16f97c48a3735630725ff4459db1e240360731844712
7
+ data.tar.gz: 1773d262ce191fcf566a385620ae7fd7f880f1e886f99e0f8bc4fd1829662b83e824cc3d694d6d4e37b25622608eff1a3452eb82b95e4f297f3aa9c41553d7d4
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .tags*
19
+ documentation/run/*
20
+ documentation/public/code/*
21
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format documentation
3
+ --warnings
4
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ sudo: false
3
+ dist: trusty
4
+ cache: bundler
5
+ rvm:
6
+ - 2.2.6
7
+ - 2.3.3
8
+ - 2.4.0
9
+ - jruby-head
10
+ - ruby-head
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: ruby-head
14
+ - rvm: jruby-head
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in utopia.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'pry'
8
+ gem 'guard-rspec'
9
+
10
+ gem 'yard'
11
+ end
12
+
13
+ group :test do
14
+ gem 'benchmark-ips'
15
+ gem 'ruby-prof', platforms: :mri
16
+
17
+ gem 'simplecov'
18
+ gem 'coveralls', require: false
19
+ end
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ directories %w(lib spec)
4
+ clearing :on
5
+
6
+ guard :rspec, cmd: "bundle exec rspec" do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
9
+ watch("spec/spec_helper.rb") { "spec" }
10
+ end
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Async
2
+
3
+ Asynchronous I/O framework for Ruby based on [nio4r] and [timers].
4
+
5
+ [timers]: https://github.com/socketry/timers
6
+ [nio4r]: https://github.com/socketry/nio4r
7
+
8
+ [![Build Status](https://secure.travis-ci.org/socketry/async.svg)](http://travis-ci.org/socketry/async)
9
+ [![Code Climate](https://codeclimate.com/github/socketry/async.svg)](https://codeclimate.com/github/socketry/async)
10
+ [![Coverage Status](https://coveralls.io/repos/socketry/async/badge.svg)](https://coveralls.io/r/socketry/async)
11
+
12
+ ## Motivation
13
+
14
+ Several years ago, I was hosting websites on a server in my garage. Back then, my ADSL modem was very basic, and I wanted to have a DNS server which would resolve to an internal IP address when the domain itself resolved to my public IP. Thus was born [RubyDNS]. This project [was originally built on](https://github.com/ioquatix/rubydns/tree/v0.8.5) top of [EventMachine], but a lack of support for [IPv6 at the time](https://github.com/ioquatix/rubydns/issues/45) and [other problems](https://github.com/ioquatix/rubydns/issues/14), meant that I started looking for other options. Around that time [Celluloid] was picking up steam. I had not encountered actors before and I wanted to learn more about it. So, [I reimplemented RubyDNS on top of Celluloid](https://github.com/ioquatix/rubydns/tree/v0.9.0) and this eventually became the first stable release.
15
+
16
+ Moving forward, I refactored the internals of RubyDNS into [Celluloid::DNS]. This rewrite helped solidify the design of RubyDNS and to a certain extent it works. However, [unfixed bugs and design problems](https://github.com/celluloid/celluloid/pull/710) in Celluloid meant that RubyDNS 2.0 was delayed by almost 2 years. I wasn't happy releasing things with known bugs and problems. Anyway, a lot of discussion and thinking, I decided to build a small event reactor using [nio4r] and [timers], the core parts of [Celluloid::IO] which made it work so well.
17
+
18
+ [Celluloid]: https://github.com/celluloid/celluloid
19
+ [Celluloid::IO]: https://github.com/celluloid/celluloid-io
20
+ [Celluloid::DNS]: https://github.com/celluloid/celluloid-dns
21
+ [EventMachine]: https://github.com/eventmachine/eventmachine
22
+ [RubyDNS]: https://github.com/ioquatix/rubydns
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ gem "async"
30
+ ```
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install async
39
+
40
+ ## Supported Ruby Versions
41
+
42
+ This library aims to support and is [tested against][travis] the following Ruby
43
+ versions:
44
+
45
+ * Ruby 2.2.6+
46
+ * Ruby 2.3
47
+ * Ruby 2.4
48
+ * JRuby 9.1.6.0+
49
+
50
+ If something doesn't work on one of these versions, it's a bug.
51
+
52
+ This library may inadvertently work (or seem to work) on other Ruby versions,
53
+ however support will only be provided for the versions listed above.
54
+
55
+ If you would like this library to support another Ruby version or
56
+ implementation, you may volunteer to be a maintainer. Being a maintainer
57
+ entails making sure all tests run and pass on that implementation. When
58
+ something breaks on your implementation, you will be responsible for providing
59
+ patches in a timely fashion. If critical issues for a particular implementation
60
+ exist at the time of a major release, support for that Ruby version may be
61
+ dropped.
62
+
63
+ [travis]: http://travis-ci.org/socketry/async
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create new Pull Request
72
+
73
+ ## See Also
74
+
75
+ - [async-dns](https://github.com/socketry/async-dns) — Asynchronous DNS resolver and server.
76
+ - [rubydns](https://github.com/socketry/rubydns) — A easy to use Ruby DNS server.
77
+
78
+ ## License
79
+
80
+ Released under the MIT license.
81
+
82
+ Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
83
+
84
+ Permission is hereby granted, free of charge, to any person obtaining a copy
85
+ of this software and associated documentation files (the "Software"), to deal
86
+ in the Software without restriction, including without limitation the rights
87
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
88
+ copies of the Software, and to permit persons to whom the Software is
89
+ furnished to do so, subject to the following conditions:
90
+
91
+ The above copyright notice and this permission notice shall be included in
92
+ all copies or substantial portions of the Software.
93
+
94
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
95
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
96
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
97
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
98
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
99
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
100
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:test)
5
+
6
+ task :default => :test
data/async.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require_relative 'lib/async/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "async"
6
+ spec.version = Async::VERSION
7
+ spec.authors = ["Samuel Williams"]
8
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
9
+ spec.description = <<-EOF
10
+ Async provides a modern asynchronous I/O framework for Ruby, based
11
+ on nio4r. It implements the reactor pattern, providing both IO and timer
12
+ based events.
13
+ EOF
14
+ spec.summary = "Async is an asynchronous I/O framework based on nio4r."
15
+ spec.homepage = "https://github.com/socketry/async"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+ spec.has_rdoc = "yard"
23
+
24
+ spec.required_ruby_version = ">= 2.2.6"
25
+
26
+ spec.add_runtime_dependency "nio4r", "~> 2"
27
+ spec.add_runtime_dependency "timers", "~> 4.1"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.3"
30
+ spec.add_development_dependency "process-daemon", "~> 1.0.0"
31
+ spec.add_development_dependency "rspec", "~> 3.4.0"
32
+ spec.add_development_dependency "rake"
33
+ end
data/examples/aio.rb ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'async'
4
+
5
+ reactor = Async::Reactor.new
6
+
7
+ puts "Creating server"
8
+ server = TCPServer.new("localhost", 6777)
9
+
10
+ REPEATS = 10
11
+
12
+ timer = reactor.after(1) do
13
+ puts "Reactor timed out!"
14
+ reactor.stop
15
+ end
16
+
17
+ reactor.async(server) do |server, task|
18
+ REPEATS.times do |i|
19
+ puts "Accepting peer on server #{server}"
20
+ task.with(server.accept) do |peer|
21
+ puts "Sending data to peer"
22
+ peer << "data #{i}"
23
+ peer.shutdown
24
+ end
25
+ end
26
+
27
+ puts "Server finished, canceling timer"
28
+ timer.cancel
29
+ end
30
+
31
+ REPEATS.times do |i|
32
+ # This aspect of the connection is synchronous.
33
+ puts "Creating client #{i}"
34
+ client = TCPSocket.new("localhost", 6777)
35
+
36
+ reactor.async(client) do |client|
37
+ puts "Reading data on client #{i}"
38
+ puts client.read(1024)
39
+ end
40
+ end
41
+
42
+ reactor.run
data/lib/async.rb ADDED
@@ -0,0 +1,23 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative "async/version"
22
+ require_relative "async/logger"
23
+ require_relative "async/reactor"
data/lib/async/io.rb ADDED
@@ -0,0 +1,93 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'wrapper'
22
+
23
+ require 'forwardable'
24
+
25
+ module Async
26
+ # Represents an asynchronous IO within a reactor.
27
+ class IO < Wrapper
28
+ extend Forwardable
29
+
30
+ WRAPPERS = {}
31
+
32
+ def self.[] instance
33
+ WRAPPERS[instance.class]
34
+ end
35
+
36
+ class << self
37
+ if RUBY_VERSION >= "2.3"
38
+ def wrap_blocking_method(new_name, method_name)
39
+ # puts "#{self}\##{$1} -> #{method_name}"
40
+ define_method(new_name) do |*args|
41
+ async do
42
+ @io.__send__(method_name, *args, exception: false)
43
+ end
44
+ end
45
+ end
46
+ else
47
+ def wrap_blocking_method(new_name, method_name)
48
+ # puts "#{self}\##{$1} -> #{method_name}"
49
+ define_method(new_name) do |*args|
50
+ async do
51
+ @io.__send__(method_name, *args)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def wraps(klass, *additional_methods)
58
+ WRAPPERS[klass] = self
59
+
60
+ klass.instance_methods(false).grep(/(.*)_nonblock/) do |method_name|
61
+ wrap_blocking_method($1, method_name)
62
+ end
63
+
64
+ def_delegators :@io, *(additional_methods - instance_methods(false))
65
+ end
66
+ end
67
+
68
+ wraps ::IO
69
+
70
+ protected
71
+
72
+ def async
73
+ while true
74
+ begin
75
+ result = yield
76
+
77
+ case result
78
+ when :wait_readable
79
+ wait_readable
80
+ when :wait_writable
81
+ wait_writable
82
+ else
83
+ return result
84
+ end
85
+ rescue ::IO::WaitReadable
86
+ wait_readable
87
+ rescue ::IO::WaitWritable
88
+ wait_writable
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'logger'
22
+
23
+ module Async
24
+ class << self
25
+ attr :logger
26
+
27
+ def default_log_level
28
+ if $DEBUG
29
+ Logger::DEBUG
30
+ elsif $VERBOSE
31
+ Logger::INFO
32
+ else
33
+ Logger::WARN
34
+ end
35
+ end
36
+ end
37
+
38
+ @logger = Logger.new($stderr, level: default_log_level)
39
+ end
@@ -0,0 +1,166 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'logger'
22
+ require_relative 'task'
23
+ require_relative 'wrapper'
24
+
25
+ require 'nio'
26
+ require 'timers'
27
+ require 'forwardable'
28
+
29
+ module Async
30
+ class TimeoutError < RuntimeError
31
+ end
32
+
33
+ class Reactor
34
+ extend Forwardable
35
+
36
+ def self.run(*args, &block)
37
+ reactor = self.new
38
+
39
+ reactor.async(*args, &block)
40
+ end
41
+
42
+ def initialize(wrappers: IO)
43
+ @wrappers = wrappers
44
+
45
+ @selector = NIO::Selector.new
46
+ @timers = Timers::Group.new
47
+
48
+ @fibers = []
49
+
50
+ @stopped = true
51
+ end
52
+
53
+ attr :wrappers
54
+ attr :stopped
55
+
56
+ def_delegators :@timers, :every, :after
57
+
58
+ def wrap(io, task)
59
+ @wrappers[io].new(io, task)
60
+ end
61
+
62
+ def with(io, &block)
63
+ async do |task|
64
+ task.with(io, &block)
65
+ end
66
+ end
67
+
68
+ def async(*ios, &block)
69
+ task = Task.new(ios, self, &block)
70
+
71
+ # I want to take a moment to explain the logic of this.
72
+ # When calling an async block, we deterministically execute it until the
73
+ # first blocking operation. We don't *have* to do this - we could schedule
74
+ # it for later execution, but it's useful to:
75
+ # - Fail at the point of call where possible.
76
+ # - Execute determinstically where possible.
77
+ # - Avoid overhead if no blocking operation is performed.
78
+ fiber = task.run
79
+
80
+ # We only start tracking this if the fiber is still alive:
81
+ @fibers << fiber if fiber.alive?
82
+
83
+ # Async.logger.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
84
+ return task
85
+ end
86
+
87
+ def register(*args)
88
+ @selector.register(*args)
89
+ end
90
+
91
+ def stop
92
+ @stopped = true
93
+ end
94
+
95
+ def run(*args, &block)
96
+ @stopped = false
97
+
98
+ # Allow the user to kick of the initial async tasks.
99
+ async(*args, &block) if block_given?
100
+
101
+ @timers.wait do |interval|
102
+ # - nil: no timers
103
+ # - -ve: timers expired already
104
+ # - 0: timers ready to fire
105
+ # - +ve: timers waiting to fire
106
+ interval = 0 if interval && interval < 0
107
+
108
+ # Async.logger.debug "[#{self} Pre] Updating #{@fibers.count} fibers..."
109
+ # Async.logger.debug @fibers.collect{|fiber| [fiber, fiber.alive?]}.inspect
110
+ # As timeouts may have been updated, and caused fibers to complete, we should check this.
111
+ @fibers.delete_if{|fiber| !fiber.alive?}
112
+
113
+ # If there is nothing to do, then finish:
114
+ # Async.logger.debug "[#{self}] @fibers.empty? = #{@fibers.empty?} && interval #{interval.inspect}"
115
+ return if @fibers.empty? && interval.nil?
116
+
117
+ # Async.logger.debug "Selecting with #{@fibers.count} fibers interval = #{interval}..."
118
+ if monitors = @selector.select(interval)
119
+ monitors.each do |monitor|
120
+ if task = monitor.value
121
+ # Async.logger.debug "Resuming task #{task} due to IO..."
122
+ task.resume
123
+ end
124
+ end
125
+ end
126
+ end until @stopped
127
+ ensure
128
+ # Async.logger.debug "[#{self} Ensure] Exiting run-loop (stopped: #{@stopped} exception: #{$!})..."
129
+ # Async.logger.debug @fibers.collect{|fiber| [fiber, fiber.alive?]}.inspect
130
+ @stopped = true
131
+ end
132
+
133
+ def sleep(duration)
134
+ task = Fiber.current
135
+
136
+ timer = self.after(duration) do
137
+ if task.alive?
138
+ task.resume
139
+ end
140
+ end
141
+
142
+ result = Fiber.yield
143
+
144
+ raise result if result.is_a? Exception
145
+ ensure
146
+ timer.cancel if timer
147
+ end
148
+
149
+ def timeout(duration)
150
+ backtrace = caller
151
+ task = Fiber.current
152
+
153
+ timer = self.after(duration) do
154
+ if task.alive?
155
+ error = TimeoutError.new("execution expired")
156
+ error.set_backtrace backtrace
157
+ task.resume error
158
+ end
159
+ end
160
+
161
+ yield
162
+ ensure
163
+ timer.cancel if timer
164
+ end
165
+ end
166
+ end