goliath 0.9.2 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of goliath might be problematic. Click here for more details.
- data/Gemfile +1 -1
- data/{HISTORY → HISTORY.md} +26 -12
- data/README.md +17 -10
- data/examples/api_proxy.rb +28 -0
- data/examples/async_aroundware_demo.rb +14 -10
- data/examples/auth_and_rate_limit.rb +160 -38
- data/examples/config/auth_and_rate_limit.rb +8 -5
- data/examples/config/content_stream.rb +5 -9
- data/examples/early_abort.rb +37 -0
- data/examples/env_use_statements.rb +3 -0
- data/examples/favicon.rb +40 -0
- data/examples/http_log.rb +2 -1
- data/examples/public/favicon.ico +0 -0
- data/examples/rack_routes.rb +19 -0
- data/examples/rasterize/rasterize.rb +2 -1
- data/examples/rasterize/rasterize_and_shorten.rb +10 -5
- data/goliath.gemspec +7 -9
- data/lib/goliath/api.rb +16 -4
- data/lib/goliath/connection.rb +8 -7
- data/lib/goliath/deprecated/async_aroundware.rb +133 -0
- data/lib/goliath/{synchrony → deprecated}/mongo_receiver.rb +28 -8
- data/lib/goliath/deprecated/response_receiver.rb +97 -0
- data/lib/goliath/env.rb +5 -0
- data/lib/goliath/rack.rb +6 -1
- data/lib/goliath/rack/async_middleware.rb +34 -12
- data/lib/goliath/rack/barrier_aroundware.rb +228 -0
- data/lib/goliath/rack/barrier_aroundware_factory.rb +60 -0
- data/lib/goliath/rack/builder.rb +22 -6
- data/lib/goliath/rack/heartbeat.rb +8 -5
- data/lib/goliath/rack/simple_aroundware.rb +114 -0
- data/lib/goliath/rack/simple_aroundware_factory.rb +121 -0
- data/lib/goliath/rack/validation/required_param.rb +9 -2
- data/lib/goliath/request.rb +7 -0
- data/lib/goliath/runner.rb +17 -5
- data/lib/goliath/server.rb +11 -3
- data/lib/goliath/test_helper.rb +14 -14
- data/lib/goliath/version.rb +1 -1
- data/spec/integration/early_abort_spec.rb +50 -0
- data/spec/integration/keepalive_spec.rb +2 -2
- data/spec/integration/pipelining_spec.rb +2 -2
- data/spec/integration/rack_routes_spec.rb +25 -0
- data/spec/integration/template_spec.rb +2 -0
- data/spec/unit/rack/heartbeat_spec.rb +11 -1
- data/spec/unit/rack/validation/required_param_spec.rb +10 -0
- data/spec/unit/runner_spec.rb +13 -0
- data/spec/unit/server_spec.rb +4 -0
- metadata +218 -265
- data/lib/goliath/rack/async_aroundware.rb +0 -56
- data/lib/goliath/synchrony/response_receiver.rb +0 -64
@@ -1,7 +1,16 @@
|
|
1
|
-
require 'goliath/
|
1
|
+
require 'goliath/deprecated/response_receiver'
|
2
|
+
require 'em-synchrony/em-mongo'
|
2
3
|
|
3
4
|
module Goliath
|
4
5
|
module Synchrony
|
6
|
+
#
|
7
|
+
# Note: This class is deprecated. Please instead use BarrierAroundware
|
8
|
+
# (orchestrates multiple concurrent requests) or SimpleAroundware (like
|
9
|
+
# AsyncMiddleware, but with a simpler interface).
|
10
|
+
#
|
11
|
+
# There are more notes on the lib/goliath/deprecated/async_aroundware docs.
|
12
|
+
#
|
13
|
+
# ___________________________________________________________________________
|
5
14
|
#
|
6
15
|
# Currently, you must provide in the env a method 'mongo' that returns a mongo
|
7
16
|
# collection or collection proxy (probably by setting it up in the config).
|
@@ -31,18 +40,29 @@ module Goliath
|
|
31
40
|
# ... requests aren't deferrables so they're tracked in @pending_queries
|
32
41
|
end
|
33
42
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
43
|
+
if defined?(EM::Mongo::Cursor)
|
44
|
+
def find(collection, selector={}, opts={}, &block)
|
45
|
+
@pending_queries += 1
|
46
|
+
db.collection(collection).afind(selector, opts).to_a.callback do |result|
|
47
|
+
yield result
|
48
|
+
@pending_queries -= 1
|
49
|
+
self.succeed if finished?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
def find(collection, selector={}, opts={}, &block)
|
54
|
+
@pending_queries += 1
|
55
|
+
db.collection(collection).afind(selector, opts) do |result|
|
56
|
+
yield result
|
57
|
+
@pending_queries -= 1
|
58
|
+
self.succeed if finished?
|
59
|
+
end
|
40
60
|
end
|
41
61
|
end
|
42
62
|
|
43
63
|
def first(collection, selector={}, opts={}, &block)
|
44
64
|
opts[:limit] = 1
|
45
|
-
find(collection, selector, opts) do |result|
|
65
|
+
self.find(collection, selector, opts) do |result|
|
46
66
|
yield result.first
|
47
67
|
end
|
48
68
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Synchrony
|
3
|
+
|
4
|
+
#
|
5
|
+
# Note: This class is deprecated. Please instead use BarrierAroundware
|
6
|
+
# (orchestrates multiple concurrent requests) or SimpleAroundware (like
|
7
|
+
# AsyncMiddleware, but with a simpler interface).
|
8
|
+
#
|
9
|
+
# There are more notes on the lib/goliath/deprecated/async_aroundware docs.
|
10
|
+
#
|
11
|
+
module ResponseReceiver
|
12
|
+
# The request environment, set in the initializer
|
13
|
+
attr_reader :env
|
14
|
+
# The response, set by the ResponseReceiver's downstream
|
15
|
+
attr_accessor :status, :headers, :body
|
16
|
+
|
17
|
+
# Override this method in your middleware to perform any preprocessing
|
18
|
+
# (launching a deferred request, perhaps).
|
19
|
+
#
|
20
|
+
# @return [Array] array contains [status, headers, body]
|
21
|
+
def pre_process
|
22
|
+
Goliath::Connection::AsyncResponse
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override this method in your middleware to perform any postprocessing.
|
26
|
+
# This will only be invoked when all deferred requests (including the
|
27
|
+
# response) have completed.
|
28
|
+
#
|
29
|
+
# @return [Array] array contains [status, headers, body]
|
30
|
+
def post_process
|
31
|
+
[status, headers, body]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Virtual setter for the downstream middleware/endpoint response
|
35
|
+
def downstream_resp=(status_headers_body)
|
36
|
+
@status, @headers, @body = status_headers_body
|
37
|
+
end
|
38
|
+
|
39
|
+
# Invoked by the async_callback chain. Stores the [status, headers, body]
|
40
|
+
# for post_process'ing
|
41
|
+
def call resp
|
42
|
+
return resp if resp.first == Goliath::Connection::AsyncResponse.first
|
43
|
+
self.downstream_resp = resp
|
44
|
+
check_progress(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Have we received a response?
|
48
|
+
def response_received?
|
49
|
+
!! @status
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def check_progress(fiber)
|
55
|
+
if finished?
|
56
|
+
succeed
|
57
|
+
# continue processing
|
58
|
+
fiber.resume(self) if fiber && fiber.alive? && fiber != Fiber.current
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Note: This class is deprecated. Please instead use BarrierAroundware
|
65
|
+
# (orchestrates multiple concurrent requests) or SimpleAroundware (like
|
66
|
+
# AsyncMiddleware, but with a simpler interface).
|
67
|
+
#
|
68
|
+
# There are more notes on the lib/goliath/deprecated/async_aroundware docs.
|
69
|
+
#
|
70
|
+
class MultiReceiver < EM::Synchrony::Multi
|
71
|
+
include ResponseReceiver
|
72
|
+
|
73
|
+
# Create a new MultiReceiver
|
74
|
+
# @param env [Goliath::Env] the current environment
|
75
|
+
def initialize env
|
76
|
+
@env = env
|
77
|
+
super()
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :enqueue, :add
|
81
|
+
|
82
|
+
def successes
|
83
|
+
responses[:callback]
|
84
|
+
end
|
85
|
+
|
86
|
+
def failures
|
87
|
+
responses[:errback]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Finished if we received a response and the multi request is finished
|
91
|
+
def finished?
|
92
|
+
super && response_received?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
data/lib/goliath/env.rb
CHANGED
@@ -6,6 +6,7 @@ module Goliath
|
|
6
6
|
# Goliath::Env also provides access to the logger, configuration information
|
7
7
|
# and anything else set into the config data during initialization.
|
8
8
|
class Env < Hash
|
9
|
+
attr_accessor :event_handler
|
9
10
|
include Constants
|
10
11
|
|
11
12
|
# Create a new Goliath::Env object
|
@@ -121,6 +122,10 @@ module Goliath
|
|
121
122
|
super
|
122
123
|
end
|
123
124
|
|
125
|
+
def defer_stack
|
126
|
+
@defer_stack ||= []
|
127
|
+
end
|
128
|
+
|
124
129
|
# The Goliath::Env will provide any of it's keys as a method. It will also provide
|
125
130
|
# any of the keys in the config object as methods. The methods will return
|
126
131
|
# the value of the key. If the key doesn't exist in either hash this will
|
data/lib/goliath/rack.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Goliath
|
2
2
|
module Rack
|
3
|
-
autoload :AsyncAroundware, 'goliath/rack/async_aroundware'
|
4
3
|
autoload :AsyncMiddleware, 'goliath/rack/async_middleware'
|
4
|
+
autoload :BarrierAroundware, 'goliath/rack/barrier_aroundware'
|
5
|
+
autoload :BarrierAroundwareFactory, 'goliath/rack/barrier_aroundware_factory'
|
5
6
|
autoload :Builder, 'goliath/rack/builder'
|
6
7
|
autoload :DefaultMimeType, 'goliath/rack/default_mime_type'
|
7
8
|
autoload :DefaultResponseFormat, 'goliath/rack/default_response_format'
|
@@ -10,9 +11,13 @@ module Goliath
|
|
10
11
|
autoload :JSONP, 'goliath/rack/jsonp'
|
11
12
|
autoload :Params, 'goliath/rack/params'
|
12
13
|
autoload :Render, 'goliath/rack/render'
|
14
|
+
autoload :SimpleAroundware, 'goliath/rack/simple_aroundware'
|
15
|
+
autoload :SimpleAroundwareFactory, 'goliath/rack/simple_aroundware_factory'
|
13
16
|
autoload :Templates, 'goliath/rack/templates'
|
14
17
|
autoload :Tracer, 'goliath/rack/tracer'
|
15
18
|
autoload :Validator, 'goliath/rack/validator'
|
16
19
|
autoload :Validation, 'goliath/rack/validation'
|
20
|
+
#
|
21
|
+
autoload :AsyncAroundware, 'goliath/deprecated/async_aroundware'
|
17
22
|
end
|
18
23
|
end
|
@@ -21,13 +21,13 @@ module Goliath
|
|
21
21
|
# include Goliath::Rack::AsyncMiddleware
|
22
22
|
#
|
23
23
|
# def call(env)
|
24
|
-
#
|
24
|
+
# awesomeness_quotient = 3
|
25
25
|
# # the extra args sent to super are passed along to post_process
|
26
|
-
# super(env,
|
26
|
+
# super(env, awesomeness_quotient)
|
27
27
|
# end
|
28
28
|
#
|
29
|
-
# def post_process(env, status, headers, body,
|
30
|
-
# new_body = make_totally_awesome(body,
|
29
|
+
# def post_process(env, status, headers, body, awesomeness_quotient)
|
30
|
+
# new_body = make_totally_awesome(body, awesomeness_quotient)
|
31
31
|
# [status, headers, new_body]
|
32
32
|
# end
|
33
33
|
# end
|
@@ -41,6 +41,8 @@ module Goliath
|
|
41
41
|
# next request. Everything that you need to store needs to be stored in
|
42
42
|
# local variables.
|
43
43
|
module AsyncMiddleware
|
44
|
+
include Goliath::Rack::Validator
|
45
|
+
|
44
46
|
# Called by the framework to create the middleware.
|
45
47
|
#
|
46
48
|
# @param app [Proc] The application
|
@@ -65,19 +67,38 @@ module Goliath
|
|
65
67
|
# @param env [Goliath::Env] The goliath environment
|
66
68
|
# @return [Array] The [status_code, headers, body] tuple
|
67
69
|
def call(env, *args)
|
68
|
-
async_cb = env['async.callback']
|
69
70
|
|
70
|
-
env
|
71
|
-
async_cb.call(post_process(env, status, headers, body, *args))
|
72
|
-
end
|
71
|
+
hook_into_callback_chain(env, *args)
|
73
72
|
|
74
|
-
|
73
|
+
downstream_resp = @app.call(env)
|
75
74
|
|
76
|
-
if
|
77
|
-
|
78
|
-
else
|
75
|
+
if final_response?(downstream_resp)
|
76
|
+
status, headers, body = downstream_resp
|
79
77
|
post_process(env, status, headers, body, *args)
|
78
|
+
else
|
79
|
+
return Goliath::Connection::AsyncResponse
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Put a callback block in the middle of the async_callback chain:
|
84
|
+
# * save the old callback chain;
|
85
|
+
# * have the downstream callback send results to our proc...
|
86
|
+
# * which fires old callback chain when it completes
|
87
|
+
def hook_into_callback_chain(env, *args)
|
88
|
+
async_callback = env['async.callback']
|
89
|
+
|
90
|
+
# The response from the downstream app is sent to post_process
|
91
|
+
# and then directly up the callback chain
|
92
|
+
downstream_callback = Proc.new do |status, headers, body|
|
93
|
+
new_resp = safely(env){ post_process(env, status, headers, body, *args) }
|
94
|
+
async_callback.call(new_resp)
|
80
95
|
end
|
96
|
+
|
97
|
+
env['async.callback'] = downstream_callback
|
98
|
+
end
|
99
|
+
|
100
|
+
def final_response?(resp)
|
101
|
+
resp != Goliath::Connection::AsyncResponse
|
81
102
|
end
|
82
103
|
|
83
104
|
# Override this method in your middleware to perform any
|
@@ -88,6 +109,7 @@ module Goliath
|
|
88
109
|
def post_process(env, status, headers, body)
|
89
110
|
[status, headers, body]
|
90
111
|
end
|
112
|
+
|
91
113
|
end
|
92
114
|
end
|
93
115
|
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
|
4
|
+
#
|
5
|
+
# This module gives you ergonomics similar to traditional Rack middleware:
|
6
|
+
#
|
7
|
+
# * Use instance variables! Each SimpleAroundware is unique to its request.
|
8
|
+
# * You have accessors for env and (once in post_process) status, headers,
|
9
|
+
# body -- no more shipping them around to every method.
|
10
|
+
#
|
11
|
+
# ...along with a new superpower: you can #enqueue requests in #pre_process,
|
12
|
+
# and the barrier will hold off on executing #post_process until both the
|
13
|
+
# downstream and your enqueued requests have completed.
|
14
|
+
#
|
15
|
+
# If in your traditional middleware you'd (with poor concurrency) do this:
|
16
|
+
#
|
17
|
+
# class MyRackMiddleware
|
18
|
+
# def call(env)
|
19
|
+
# user_info = get_user_from_db
|
20
|
+
# status, headers, body = @app.call(env)
|
21
|
+
# new_body = put_username_into_sidebar_text(body, user_info)
|
22
|
+
# [status, headers, new_body]
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# You can now do this:
|
27
|
+
#
|
28
|
+
# class MyAwesomeAroundware
|
29
|
+
# include Goliath::Rack::BarrierAroundware
|
30
|
+
# attr_accessor :user_info
|
31
|
+
# def pre_process
|
32
|
+
# enqueue :user_info, async_get_user_from_db
|
33
|
+
# end
|
34
|
+
# # !concurrency!
|
35
|
+
# def post_process
|
36
|
+
# new_body = put_username_into_sidebar_text(body, user_info)
|
37
|
+
# [status, headers, new_body]
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Which you'd include in your endpoint like this:
|
42
|
+
#
|
43
|
+
# class AwesomeApi < Goliath::API
|
44
|
+
# use Goliath::Rack::BarrierAroundwareFactory, MyAwesomeAroundware
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# The user record was retrieved from the db while other processing happened;
|
48
|
+
# once the async request named :user_info returned, goliath noticed that you
|
49
|
+
# had a #user_info= setter and so it set the variable appropriately. (It's
|
50
|
+
# also put in the #successes (or #failures) hash).
|
51
|
+
#
|
52
|
+
# You can also enqueue a non-EM::Deferrable request. #enqueue_acceptor gives
|
53
|
+
# you a dummy deferrable; send the response to its succeed method:
|
54
|
+
#
|
55
|
+
# # a database lookup that takes a block
|
56
|
+
# enqueue_acceptor(:bob) do |acc|
|
57
|
+
# db.collection(:users).afind(:username => :bob) do |resp|
|
58
|
+
# acc.succeed(resp.first)
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# You're free to invoke the barrier whenever you like. Consider a bouncer
|
63
|
+
# who is polite to townies (he lets them order from the bar while he checks
|
64
|
+
# their ID) but a jerk to college kids (who have to wait in line before they
|
65
|
+
# can order):
|
66
|
+
#
|
67
|
+
# class AuthAroundware
|
68
|
+
# include Goliath::Rack::BarrierAroundware
|
69
|
+
# attr_accessor :user_info
|
70
|
+
# def pre_process
|
71
|
+
# enqueue :user_info, async_get_user_from_db
|
72
|
+
# unless lazy_authorization?
|
73
|
+
# perform # yield execution until user_info has arrived
|
74
|
+
# check_authorization! # then check the info *before* continuing
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# #
|
78
|
+
# def post_process
|
79
|
+
# check_authorization! if lazy_authorization?
|
80
|
+
# [status, headers, new_body]
|
81
|
+
# end
|
82
|
+
# def lazy_authorization?
|
83
|
+
# (env['REQUEST_METHOD'] == 'GET') || (env['REQUEST_METHOD'] == 'HEAD')
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
# class AwesomeApi < Goliath::API
|
87
|
+
# use Goliath::Rack::BarrierAroundwareFactory, AuthAroundware
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# The `perform` statement puts up a barrier until all pending requests (in
|
91
|
+
# this case, :user_info) complete. The downstream request isn't enqueued
|
92
|
+
# until pre_process completes, so in the non-`GET` branch the AuthAroundware
|
93
|
+
# is able to verify the user *before* allowing execution to proceed. If the
|
94
|
+
# request is a harmless `GET`, though, both the user_info and downstream
|
95
|
+
# requests can proceed concurrently, and we instead `check_authorization!`
|
96
|
+
# in the post_process block.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# class ShortenUrl
|
100
|
+
# attr_accessor :shortened_url
|
101
|
+
# include Goliath::Rack::BarrierAroundware
|
102
|
+
#
|
103
|
+
# def pre_process
|
104
|
+
# target_url = PostRank::URI.clean(env.params['url'])
|
105
|
+
# shortener_request = EM::HttpRequest.new('http://is.gd/create.php').aget(:query => { :format => 'simple', :url => target_url })
|
106
|
+
# enqueue :shortened_url, shortener_request
|
107
|
+
# Goliath::Connection::AsyncResponse
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# # by the time you get here, the AroundwareFactory will have populated
|
111
|
+
# # the [status, headers, body] and the shortener_request will have
|
112
|
+
# # populated the shortened_url attribute.
|
113
|
+
# def post_process
|
114
|
+
# if succeeded?(:shortened_url)
|
115
|
+
# headers['X-Shortened-URI'] = shortened_url
|
116
|
+
# end
|
117
|
+
# [status, headers, body]
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# class AwesomeApiWithShortening < Goliath::API
|
122
|
+
# use Goliath::Rack::Params
|
123
|
+
# use Goliath::Rack::BarrierAroundwareFactory, ShortenUrl
|
124
|
+
# def response(env)
|
125
|
+
# # ... do something awesome
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
module BarrierAroundware
|
130
|
+
include EventMachine::Deferrable
|
131
|
+
include Goliath::Rack::SimpleAroundware
|
132
|
+
|
133
|
+
# Pool with handles of pending requests
|
134
|
+
attr_reader :pending_requests
|
135
|
+
# Pool with handles of sucessful requests
|
136
|
+
attr_reader :successes
|
137
|
+
# Pool with handles of failed requests
|
138
|
+
attr_reader :failures
|
139
|
+
|
140
|
+
# @param env [Goliath::Env] The request environment
|
141
|
+
# @return [Goliath::Rack::BarrierAroundware]
|
142
|
+
def initialize(env)
|
143
|
+
@env = env
|
144
|
+
@pending_requests = Set.new
|
145
|
+
@successes = {}
|
146
|
+
@failures = {}
|
147
|
+
end
|
148
|
+
|
149
|
+
# On receipt of an async result,
|
150
|
+
# * remove the tracking handle from pending_requests
|
151
|
+
# * and file the response in either successes or failures as appropriate
|
152
|
+
# * call the setter for that handle if any (on receipt of :shortened_url,
|
153
|
+
# calls self.shortened_url = resp)
|
154
|
+
# * check progress -- succeeds (transferring controll) if nothing is pending.
|
155
|
+
def accept_response(handle, resp_succ, resp, req=nil, fiber=nil)
|
156
|
+
raise "received response for a non-pending request!" if not pending_requests.include?(handle)
|
157
|
+
pending_requests.delete(handle)
|
158
|
+
resp_succ ? (successes[handle] = [req, resp]) : (failures[handle] = [req, resp])
|
159
|
+
self.send("#{handle}=", resp) if self.respond_to?("#{handle}=")
|
160
|
+
check_progress(fiber)
|
161
|
+
resp
|
162
|
+
end
|
163
|
+
|
164
|
+
# Add a deferred request to the pending pool, and set a callback to
|
165
|
+
# #accept_response when the request completes
|
166
|
+
def enqueue(handle, deferred_req)
|
167
|
+
fiber = Fiber.current
|
168
|
+
add_to_pending(handle)
|
169
|
+
deferred_req.callback{|resp| safely(env){ accept_response(handle, true, resp, deferred_req, fiber) } }
|
170
|
+
deferred_req.errback{|resp| safely(env){ accept_response(handle, false, resp, deferred_req, fiber) } }
|
171
|
+
end
|
172
|
+
|
173
|
+
# Do you have a method that uses a block, not a deferrable? This method
|
174
|
+
# gives you a deferrable 'acceptor' and enqueues it -- simply call
|
175
|
+
# #succeed (or #fail) on the acceptor from within the block, passing it
|
176
|
+
# your desired response.
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# # sleep for 1.0 seconds and then complete
|
180
|
+
# enqueue_acceptor(:sleepy)do |acc|
|
181
|
+
# EM.add_timer(1.0){ acc.succeed }
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# # a database lookup that takes a block
|
186
|
+
# enqueue_acceptor(:bob) do |acc|
|
187
|
+
# db.collection(:users).afind(:username => :bob) do |resp|
|
188
|
+
# acc.succeed(resp.first)
|
189
|
+
# end
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
def enqueue_acceptor(handle)
|
193
|
+
acceptor = EM::DefaultDeferrable.new
|
194
|
+
yield(acceptor)
|
195
|
+
enqueue handle, acceptor
|
196
|
+
end
|
197
|
+
|
198
|
+
# Register a pending request. If you call this from outside #enqueue, you
|
199
|
+
# must construct callbacks that eventually invoke accept_response
|
200
|
+
def add_to_pending(handle)
|
201
|
+
set_deferred_status(nil) # we're not done yet, even if we were
|
202
|
+
@pending_requests << handle
|
203
|
+
end
|
204
|
+
|
205
|
+
def finished?
|
206
|
+
pending_requests.empty?
|
207
|
+
end
|
208
|
+
|
209
|
+
# Perform will yield (allowing other processes to continue) until all
|
210
|
+
# pending responses complete. You're free to enqueue responses, call
|
211
|
+
# perform,
|
212
|
+
def perform
|
213
|
+
Fiber.yield unless finished?
|
214
|
+
end
|
215
|
+
|
216
|
+
protected
|
217
|
+
|
218
|
+
def check_progress(fiber)
|
219
|
+
if finished?
|
220
|
+
succeed
|
221
|
+
# continue processing
|
222
|
+
fiber.resume(self) if fiber && fiber.alive? && fiber != Fiber.current
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|