await 0.1.0

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.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'eventmachine'
4
+
5
+ group :development do
6
+ gem 'bundler'
7
+ gem 'jeweler'
8
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Scott Tadman, The Working Group Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,107 @@
1
+ # await
2
+
3
+ This implements the await/defer pattern in Ruby using fibers within the
4
+ EventMachine environment.
5
+
6
+ In general terms, `await` is used to define a group of operations that must
7
+ be completed before processing can continue, and `defer` is used to define
8
+ these individual operations.
9
+
10
+ This can be used to simplify otherwise complicated callback structures where
11
+ a number of operations can be performed in parallel. Further complexity arises
12
+ when some of these operations have optional steps.
13
+
14
+ ## Examples
15
+
16
+ In order to execute properly, an `await` call must be created within a fiber
17
+ that can yield. Since the root fiber cannot yield, a secondary fiber must be
18
+ created.
19
+
20
+ A very simple example shows how a number of timers can be set to simulate
21
+ some long-running asynchronous operations:
22
+
23
+ ```ruby
24
+ require 'await'
25
+ require 'eventmachine'
26
+
27
+ EventMachine.run do
28
+ Fiber.new do
29
+ include Await
30
+
31
+ now = Time.now
32
+ await do
33
+ EventMachine::Timer.new(5, &defer)
34
+ EventMachine::Timer.new(2, &defer)
35
+ EventMachine::Timer.new(3, &defer)
36
+ end
37
+
38
+ puts "Took %.1fs to complete" % (Time.now - now).to_f
39
+
40
+ EventMachine.stop_event_loop
41
+ end.resume
42
+ end
43
+ ```
44
+
45
+ In the end you should see that it took only as long as the longest timer.
46
+
47
+ In its default state, `defer` is simply used as a trigger. It can also wrap
48
+ around a callback block to add additional functionality:
49
+
50
+ ```ruby
51
+ require 'await'
52
+ require 'eventmachine'
53
+
54
+ EventMachine.run do
55
+ Fiber.new do
56
+ include Await
57
+
58
+ now = Time.now
59
+ await do
60
+ trigger = defer do |x, y|
61
+ puts "Received arguments: #{[ x, y ].inspect}"
62
+ end
63
+
64
+ EventMachine::Timer.new(4) do
65
+ trigger.call(1, '2')
66
+ end
67
+
68
+ EventMachine::Timer.new(
69
+ 3,
70
+ &defer do
71
+ EventMachine::Timer.new(
72
+ 2,
73
+ &defer
74
+ )
75
+ end
76
+ )
77
+ end
78
+
79
+ puts "Took %.1fs to complete" % (Time.now - now).to_f
80
+
81
+ EventMachine.stop_event_loop
82
+ end.resume
83
+ end
84
+ ```
85
+
86
+ Since the `defer` method returns a standard Proc callback, it is possible to
87
+ pass it through as a callback method or to trigger it at an arbitrary time
88
+ with `call`.
89
+
90
+ More examples of callback structures are available in the various unit tests.
91
+
92
+ ## Troubleshooting
93
+
94
+ If the secondary fiber has not been created you will see errors like:
95
+
96
+ FiberError: can't yield from root fiber
97
+
98
+ Keep in mind that unless a `defer` call is made within a callback then the
99
+ operation is considered completed. If an additional asynchronous operation
100
+ must be performed before it is complete, be sure to wrap any and all of these
101
+ calls with `defer` as well to tag and track them properly.
102
+
103
+ ## Copyright
104
+
105
+ Copyright (c) 2012 Scott Tadman, The Working Group Inc.
106
+ See LICENSE.txt for further details.
107
+
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'rake'
15
+ require 'jeweler'
16
+
17
+ Jeweler::Tasks.new do |gem|
18
+ gem.name = "await"
19
+ gem.homepage = "http://github.com/twg/await"
20
+ gem.license = "MIT"
21
+ gem.summary = %Q{Asynchronous await/defer methods for organizing callbacks}
22
+ gem.description = %Q{Implements the await/defer pattern for event-driven or asynchronous Ruby}
23
+ gem.email = "scott@twg.ca"
24
+ gem.authors = [ "Scott Tadman" ]
25
+ end
26
+
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+
31
+ Rake::TestTask.new(:test) do |test|
32
+ test.libs << 'lib' << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "await"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Scott Tadman"]
12
+ s.date = "2012-11-13"
13
+ s.description = "Implements the await/defer pattern for event-driven or asynchronous Ruby"
14
+ s.email = "scott@twg.ca"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "LICENSE.txt",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "await.gemspec",
27
+ "lib/await.rb",
28
+ "test/helper.rb",
29
+ "test/test_await.rb"
30
+ ]
31
+ s.homepage = "http://github.com/twg/await"
32
+ s.licenses = ["MIT"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = "1.8.24"
35
+ s.summary = "Asynchronous await/defer methods for organizing callbacks"
36
+
37
+ if s.respond_to? :specification_version then
38
+ s.specification_version = 3
39
+
40
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
41
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
42
+ s.add_development_dependency(%q<bundler>, [">= 0"])
43
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
44
+ else
45
+ s.add_dependency(%q<eventmachine>, [">= 0"])
46
+ s.add_dependency(%q<bundler>, [">= 0"])
47
+ s.add_dependency(%q<jeweler>, [">= 0"])
48
+ end
49
+ else
50
+ s.add_dependency(%q<eventmachine>, [">= 0"])
51
+ s.add_dependency(%q<bundler>, [">= 0"])
52
+ s.add_dependency(%q<jeweler>, [">= 0"])
53
+ end
54
+ end
55
+
@@ -0,0 +1,94 @@
1
+ require 'thread'
2
+ require 'fiber'
3
+
4
+ module Await
5
+ module ThreadExtension
6
+ def await
7
+ # Capture the current context to properly chain this await in
8
+ parent = @deferred
9
+
10
+ deferred = @deferred = {
11
+ :fiber => Fiber.current
12
+ }
13
+
14
+ if (parent)
15
+ parent[deferred] = true
16
+ end
17
+
18
+ yield if (block_given?)
19
+
20
+ # So long as there is at least one outstanding defer block, this fiber
21
+ # must continue to yield.
22
+ while (deferred.size > 1)
23
+ fiber, trigger, block, args = Fiber.yield
24
+
25
+ deferred.delete(trigger)
26
+
27
+ if (block)
28
+ # Always introduce the correct context here by setting the
29
+ # thread-local @deferred instance variable.
30
+ @deferred = deferred
31
+ block.call(*args)
32
+ end
33
+ end
34
+
35
+ # If this was part of an existing await call then remove the current
36
+ # await operation from the list of pending entries.
37
+ if (parent)
38
+ if (deferred[:fiber] == Fiber.current)
39
+ parent.delete(deferred)
40
+ else
41
+ parent[:fiber].transfer([ Fiber.current, deferred ])
42
+ end
43
+ end
44
+
45
+ true
46
+ end
47
+
48
+ def defer(&block)
49
+ deferred = @deferred
50
+
51
+ trigger = lambda do |*args|
52
+ if (deferred[:fiber] == Fiber.current)
53
+ # Within the same fiber, remove this from the pending list
54
+ deferred.delete(trigger)
55
+
56
+ block.call(*args)
57
+ else
58
+ # If this is executing in a different fiber, transfer control back
59
+ # to the original fiber for reasons of continuity.
60
+ deferred[:fiber].transfer([ Fiber.current, trigger, block, args ])
61
+ end
62
+ end
63
+
64
+ deferred[trigger] = true
65
+
66
+ trigger
67
+ end
68
+ end
69
+
70
+ # Define a group of operations that need to be completed before execution
71
+ # will continue. The current fiber is suspended until all of the defer
72
+ # operations have completed.
73
+ def await(&block)
74
+ Thread.current.await(&block)
75
+ end
76
+
77
+ # Used to define a deferred operation. The return value is a Proc that can
78
+ # be passed through as a callback argument to any method. Once the callback
79
+ # has been executed it is considered complete unless within that callback
80
+ # other defer or await calls are made.
81
+ def defer(*args, &block)
82
+ Thread.current.defer(*args, &block)
83
+ end
84
+
85
+ # Import all mixin methods here as module methods so they can be used
86
+ # directly without requiring an import.
87
+ extend self
88
+ end
89
+
90
+ # == Hook Installation ======================================================
91
+
92
+ class Thread
93
+ include Await::ThreadExtension
94
+ end
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'test/unit'
13
+
14
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
16
+
17
+ require 'await'
18
+
19
+ require 'eventmachine'
20
+
21
+ class Test::Unit::TestCase
22
+ def em
23
+ EventMachine.run do
24
+ Fiber.new do
25
+ yield
26
+ end.resume
27
+
28
+ EventMachine.stop_event_loop
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,259 @@
1
+ require File.expand_path('helper', File.dirname(__FILE__))
2
+
3
+ class TestAwait < Test::Unit::TestCase
4
+ include Await
5
+
6
+ def test_module
7
+ assert_equal true, Await.respond_to?(:await)
8
+ assert_equal true, Await.respond_to?(:defer)
9
+ end
10
+
11
+ def test_await_default_state
12
+ triggered = false
13
+
14
+ em do
15
+ await do
16
+ triggered = true
17
+ end
18
+ end
19
+
20
+ assert_equal true, triggered
21
+ end
22
+
23
+ def test_await_block_object
24
+ trigger = nil
25
+ exited = false
26
+
27
+ em do
28
+ await do
29
+ trigger = defer
30
+ end
31
+
32
+ exited = true
33
+ end
34
+
35
+ assert_equal false, exited
36
+
37
+ trigger.call
38
+
39
+ assert_equal true, exited
40
+ end
41
+
42
+ def test_await_single_defer
43
+ triggered = false
44
+
45
+ em do
46
+ await do
47
+ defer do
48
+ triggered = true
49
+ end.call
50
+ end
51
+ end
52
+
53
+ assert_equal true, triggered
54
+ end
55
+
56
+ def test_await_nested
57
+ triggered = 0
58
+
59
+ em do
60
+ await do
61
+ defer do
62
+ await do
63
+ defer do
64
+ triggered += 1
65
+ end.call
66
+ end
67
+
68
+ assert_equal 1, triggered
69
+
70
+ await do
71
+ defer do
72
+ triggered += 2
73
+ end.call
74
+ end
75
+ end.call
76
+ end
77
+
78
+ assert_equal 3, triggered
79
+ end
80
+
81
+ assert_equal 3, triggered
82
+ end
83
+
84
+ def test_await_delayed_defer
85
+ triggered = false
86
+ defer_block = nil
87
+
88
+ em do
89
+ await do
90
+ defer_block = defer do
91
+ triggered = true
92
+ end
93
+ end
94
+ end
95
+
96
+ assert defer_block
97
+ assert_equal false, triggered
98
+
99
+ assert defer_block
100
+ defer_block.call
101
+
102
+ assert_equal true, triggered
103
+ end
104
+
105
+ def test_await_delayed_double_defer
106
+ outer_trigger = nil
107
+ inner_trigger = nil
108
+ triggered = false
109
+ continued = false
110
+
111
+ em do
112
+ await do
113
+ outer_trigger = defer do
114
+ inner_trigger = defer do
115
+ triggered = true
116
+ end
117
+ end
118
+ end
119
+
120
+ continued = true
121
+ end
122
+
123
+ assert_equal false, triggered
124
+ assert_equal false, continued
125
+
126
+ assert !inner_trigger
127
+
128
+ outer_trigger.call
129
+
130
+ assert_equal false, triggered
131
+ assert_equal false, continued
132
+
133
+ assert inner_trigger
134
+
135
+ inner_trigger.call
136
+
137
+ assert_equal true, triggered
138
+ assert_equal true, continued
139
+ end
140
+
141
+ def test_await_nested_delayed_trigger
142
+ outer_trigger = nil
143
+ inner_trigger = nil
144
+ triggered = false
145
+ continued = false
146
+
147
+ em do
148
+ await do
149
+ outer_trigger = defer do
150
+ await do
151
+ inner_trigger = defer do
152
+ triggered = true
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ continued = true
159
+ end
160
+
161
+ assert_equal false, triggered
162
+ assert_equal false, continued
163
+
164
+ assert !inner_trigger
165
+
166
+ outer_trigger.call
167
+
168
+ assert_equal false, triggered
169
+ assert_equal false, continued
170
+
171
+ assert inner_trigger
172
+
173
+ inner_trigger.call
174
+
175
+ assert_equal true, triggered
176
+ assert_equal true, continued
177
+ end
178
+
179
+ def test_await_delayed_defer_callback
180
+ triggered = false
181
+ trigger_callback = nil
182
+ results = nil
183
+
184
+ em do
185
+ await do
186
+ trigger_callback = defer do |*args|
187
+ results = args
188
+ end
189
+ end
190
+
191
+ triggered = true
192
+ end
193
+
194
+ assert_equal false, triggered
195
+
196
+ assert trigger_callback
197
+ trigger_callback.call(1, :two, '3')
198
+
199
+ assert_equal true, triggered
200
+ assert_equal [ 1, :two, '3' ], results
201
+ end
202
+
203
+ def test_await_multiple_defer
204
+ triggered = false
205
+ triggers = [ ]
206
+ count = 10
207
+
208
+ em do
209
+ await do
210
+ count.times do
211
+ triggers << defer
212
+ end
213
+ end
214
+
215
+ triggered = true
216
+ end
217
+
218
+ assert_equal false, triggered
219
+
220
+ while (triggers.length > 1)
221
+ triggers.pop.call
222
+ end
223
+
224
+ assert_equal false, triggered
225
+
226
+ triggers.pop.call
227
+
228
+ assert_equal true, triggered
229
+ end
230
+
231
+ def test_await_chained_defer
232
+ triggered = false
233
+ trigger_block_outer = nil
234
+ trigger_block_inner = nil
235
+
236
+ em do
237
+ await do
238
+ trigger_block_outer = defer do
239
+ trigger_block_inner = defer do
240
+ end
241
+ end
242
+ end
243
+
244
+ triggered = true
245
+ end
246
+
247
+ assert_equal false, triggered
248
+
249
+ assert trigger_block_outer
250
+ trigger_block_outer.call
251
+
252
+ assert_equal false, triggered
253
+
254
+ assert trigger_block_inner
255
+ trigger_block_inner.call
256
+
257
+ assert_equal true, triggered
258
+ end
259
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: await
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Scott Tadman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: jeweler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Implements the await/defer pattern for event-driven or asynchronous Ruby
63
+ email: scott@twg.ca
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files:
67
+ - LICENSE.txt
68
+ - README.md
69
+ files:
70
+ - .document
71
+ - Gemfile
72
+ - LICENSE.txt
73
+ - README.md
74
+ - Rakefile
75
+ - VERSION
76
+ - await.gemspec
77
+ - lib/await.rb
78
+ - test/helper.rb
79
+ - test/test_await.rb
80
+ homepage: http://github.com/twg/await
81
+ licenses:
82
+ - MIT
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.24
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Asynchronous await/defer methods for organizing callbacks
105
+ test_files: []