zk-eventmachine 0.2.0.beta.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.dev_extras/rvmrc +1 -1
- data/.gitignore +2 -1
- data/.yardopts +8 -0
- data/Gemfile +14 -2
- data/Rakefile +35 -0
- data/lib/z_k/z_k_event_machine/callback.rb +15 -5
- data/lib/z_k/z_k_event_machine/client.rb +100 -44
- data/lib/z_k/z_k_event_machine/iterator.rb +229 -0
- data/lib/z_k/z_k_event_machine/unixisms.rb +8 -3
- data/lib/z_k/z_k_event_machine/version.rb +1 -1
- data/lib/z_k/z_k_event_machine.rb +2 -3
- data/spec/spec_helper.rb +6 -4
- data/spec/support/logging.rb +23 -6
- data/spec/support/logging_progress_bar_formatter.rb +1 -1
- data/spec/support/wait_watchers.rb +34 -0
- data/spec/z_k/z_k_event_machine/client_spec.rb +181 -61
- data/spec/z_k/z_k_event_machine/event_handler_e_m_spec.rb +6 -5
- data/zk-eventmachine.gemspec +4 -10
- metadata +95 -111
- data/lib/z_k/z_k_event_machine/deferred.rb +0 -39
- data/lib/z_k/z_k_event_machine/synchrony_client.rb +0 -135
- data/spec/z_k/z_k_event_machine/synchrony_client_spec.rb +0 -342
data/.dev_extras/rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm 1.9.
|
1
|
+
rvm 1.9.3@zk-em
|
data/.gitignore
CHANGED
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -1,9 +1,21 @@
|
|
1
|
-
source '
|
1
|
+
source ENV['MBOX_BUNDLER_SOURCE'] if ENV['MBOX_BUNDLER_SOURCE']
|
2
2
|
source "http://rubygems.org"
|
3
3
|
|
4
|
+
group :test do
|
5
|
+
gem 'rspec', '~> 2.8.0'
|
6
|
+
gem 'yard', '~> 0.7.0'
|
7
|
+
gem 'autotest', '>= 4.4.0'
|
8
|
+
gem 'flexmock', '~> 0.8.10'
|
9
|
+
gem 'evented-spec','~> 0.9.0'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :development do
|
13
|
+
gem 'redcarpet', '~> 2.1.0'
|
14
|
+
gem 'rake'
|
15
|
+
gem 'pry'
|
16
|
+
end
|
4
17
|
|
5
18
|
# Specify your gem's dependencies in zk-em.gemspec
|
6
19
|
gemspec
|
7
20
|
|
8
|
-
|
9
21
|
# vim:ft=ruby
|
data/Rakefile
CHANGED
@@ -1,2 +1,37 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
task :yard do
|
5
|
+
Bundler.setup
|
6
|
+
require 'yard'
|
7
|
+
|
8
|
+
YARD::Rake::YardocTask.new(:run_yardoc) do |t|
|
9
|
+
t.files = ['lib/**/*.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
Rake::Task[:run_yardoc].invoke
|
13
|
+
end
|
14
|
+
|
15
|
+
%w[1.8.7 1.9.2 1.9.3 jruby].each do |rvm_ruby|
|
16
|
+
gemset_name = 'zk-em'
|
17
|
+
ruby_with_gemset = "#{rvm_ruby}@#{gemset_name}"
|
18
|
+
create_gemset_name = "mb:#{rvm_ruby}:create_gemset"
|
19
|
+
bundle_task_name = "mb:#{rvm_ruby}:bundle_install"
|
20
|
+
rspec_task_name = "mb:#{rvm_ruby}:run_rspec"
|
21
|
+
|
22
|
+
task create_gemset_name do
|
23
|
+
sh "rvm #{rvm_ruby} do rvm gemset create #{gemset_name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
task bundle_task_name => create_gemset_name do
|
27
|
+
rm_f 'Gemfile.lock'
|
28
|
+
sh "rvm #{ruby_with_gemset} do bundle install"
|
29
|
+
end
|
30
|
+
|
31
|
+
task rspec_task_name => bundle_task_name do
|
32
|
+
sh "rvm #{ruby_with_gemset} do bundle exec rspec spec --fail-fast"
|
33
|
+
end
|
34
|
+
|
35
|
+
task 'mb:test_all' => rspec_task_name
|
36
|
+
end
|
37
|
+
|
@@ -33,6 +33,12 @@ module ZK
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
# save a context object, used for associating delivered events with the request that created them
|
37
|
+
attr_accessor :context
|
38
|
+
|
39
|
+
# saves the request id of this call
|
40
|
+
attr_reader :req_id
|
41
|
+
|
36
42
|
def initialize(prok=nil, &block)
|
37
43
|
on_result(prok, &block)
|
38
44
|
end
|
@@ -49,7 +55,11 @@ module ZK
|
|
49
55
|
# Checks the return code from the async call. If the return code was not ZOK,
|
50
56
|
# then fire the errbacks and do the node-style error call
|
51
57
|
# otherwise, does nothing
|
58
|
+
#
|
59
|
+
# in this call we also stash the outgoing req_id so we can sync it up
|
52
60
|
def check_async_rc(hash)
|
61
|
+
@req_id = hash[:req_id]
|
62
|
+
logger.debug { "#{__method__}: got #{hash.inspect}" }
|
53
63
|
call(hash) unless success?(hash)
|
54
64
|
end
|
55
65
|
|
@@ -58,11 +68,11 @@ module ZK
|
|
58
68
|
# we take the appropriate actions
|
59
69
|
#
|
60
70
|
# delegates to #deferred_style_result and #node_style_result
|
61
|
-
def call(
|
62
|
-
|
71
|
+
def call(result)
|
72
|
+
logger.debug { "\n#{self.class.name}##{__method__}\n\treq_id: #{req_id.inspect}\n\tcontext: #{context.inspect}\n\tresult: #{result.inspect}" }
|
63
73
|
EM.schedule do
|
64
|
-
deferred_style_result(
|
65
|
-
node_style_result(
|
74
|
+
deferred_style_result(result)
|
75
|
+
node_style_result(result)
|
66
76
|
end
|
67
77
|
end
|
68
78
|
|
@@ -70,7 +80,7 @@ module ZK
|
|
70
80
|
#
|
71
81
|
# @param [Hash] hash the result of the async call
|
72
82
|
#
|
73
|
-
# @
|
83
|
+
# @return [true, false] for success, failure
|
74
84
|
def success?(hash)
|
75
85
|
hash[:rc] == Zookeeper::ZOK
|
76
86
|
end
|
@@ -1,26 +1,67 @@
|
|
1
1
|
module ZK
|
2
2
|
module ZKEventMachine
|
3
3
|
class Client < ZK::Client::Base
|
4
|
+
include Deferred::Accessors
|
4
5
|
include ZK::Logging
|
5
6
|
include Unixisms
|
6
7
|
|
7
8
|
DEFAULT_TIMEOUT = 10
|
8
9
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
10
|
+
# If we get a ZK::Exceptions::ConnectionLoss exeption back from any call,
|
11
|
+
# or a EXPIRED_SESSION_STATE event, we will call back any handlers registered
|
12
|
+
# here with the exception instance as the argument.
|
13
|
+
#
|
14
|
+
# once this deferred has been fired, it will be replaced with a new
|
15
|
+
# deferred, so callbacks must be re-registered, and *should* be
|
16
|
+
# re-registered *within* the callback to avoid missing events
|
17
|
+
#
|
18
|
+
# @method on_connection_lost
|
19
|
+
# @return [Deferred::Default]
|
20
|
+
deferred_event :connection_lost
|
21
|
+
|
22
|
+
# Registers a one-shot callback for the ZOO_CONNECTED_STATE event.
|
23
|
+
#
|
24
|
+
# @note this is experimental currently. This may or may not fire for the *initial* connection.
|
25
|
+
# it's purpose is to warn an already-existing client with watches that a connection has been
|
26
|
+
# re-established (with session information saved). From the ZooKeeper Programmers' Guide:
|
27
|
+
#
|
28
|
+
# If you are using watches, you must look for the connected watch event.
|
29
|
+
# When a ZooKeeper client disconnects from a server, you will not receive
|
30
|
+
# notification of changes until reconnected. If you are watching for a
|
31
|
+
# znode to come into existance, you will miss the event if the znode is
|
32
|
+
# created and deleted while you are disconnected.
|
33
|
+
#
|
34
|
+
# once this deferred has been fired, it will be replaced with a new
|
35
|
+
# deferred, so callbacks must be re-registered, and *should* be
|
36
|
+
# re-registered *within* the callback to avoid missing events
|
37
|
+
#
|
38
|
+
# @method on_connected
|
39
|
+
# @return [Deferred::Default]
|
40
|
+
deferred_event :connected
|
41
|
+
|
42
|
+
# Registers a one-shot callback for the ZOO_CONNECTING_STATE event
|
43
|
+
#
|
44
|
+
# This event is triggered when we have become disconnected from the
|
45
|
+
# cluster and are in the process of reconnecting.
|
46
|
+
deferred_event :connecting
|
47
|
+
|
48
|
+
# called back once the connection has been closed.
|
49
|
+
#
|
50
|
+
# @method on_close
|
51
|
+
# @return [Deferred::Default]
|
52
|
+
deferred_event :close
|
12
53
|
|
13
54
|
# Takes same options as ZK::Client::Base
|
14
55
|
def initialize(host, opts={})
|
15
56
|
@host = host
|
16
|
-
@
|
17
|
-
@
|
18
|
-
|
57
|
+
@event_handler = EventHandlerEM.new(self)
|
58
|
+
@closing = false
|
59
|
+
register_default_event_handlers!
|
19
60
|
end
|
20
61
|
|
21
|
-
|
22
|
-
|
23
|
-
|
62
|
+
# @private
|
63
|
+
def closing?
|
64
|
+
!!@closing
|
24
65
|
end
|
25
66
|
|
26
67
|
# open a ZK connection, attach it to the reactor.
|
@@ -28,37 +69,21 @@ module ZK
|
|
28
69
|
# ready for use
|
29
70
|
def connect(&blk)
|
30
71
|
# XXX: maybe move this into initialize, need to figure out how to schedule it properly
|
31
|
-
@cnx ||=
|
32
|
-
|
33
|
-
|
34
|
-
# end
|
72
|
+
@cnx ||= (
|
73
|
+
ZookeeperEM::Client.new(@host, DEFAULT_TIMEOUT, event_handler.get_default_watcher_block)
|
74
|
+
)
|
35
75
|
@cnx.on_attached(&blk)
|
36
76
|
end
|
37
77
|
|
38
78
|
# @private
|
39
|
-
# XXX: move this down into ZK::Client::Base
|
40
|
-
def connected?
|
41
|
-
@cnx and @cnx.connected?
|
42
|
-
end
|
43
|
-
|
44
|
-
# If we get a ZK::Exceptions::ConnectionLoss exeption back from any call,
|
45
|
-
# we will call back any handlers registered here with the exception
|
46
|
-
# instance as the argument
|
47
|
-
#
|
48
|
-
# once this deferred has been fired, it will be replaced with a new
|
49
|
-
# deferred, so callbacks must be re-registered
|
50
|
-
#
|
51
|
-
def on_connection_lost(&blk)
|
52
|
-
@connection_lost_deferred.callback(&blk) if blk
|
53
|
-
@connection_lost_deferred
|
54
|
-
end
|
55
|
-
|
56
79
|
def reopen(*a)
|
57
80
|
raise NotImplementedError, "reoopen is not implemented for the eventmachine version of the client"
|
58
81
|
end
|
59
82
|
|
60
83
|
def close!(&blk)
|
61
84
|
on_close(&blk)
|
85
|
+
return on_close if @closing
|
86
|
+
@closing = true
|
62
87
|
|
63
88
|
if @cnx
|
64
89
|
logger.debug { "#{self.class.name}: in close! clearing event_handler" }
|
@@ -66,11 +91,8 @@ module ZK
|
|
66
91
|
|
67
92
|
logger.debug { "#{self.class.name}: calling @cnx.close" }
|
68
93
|
@cnx.close do
|
69
|
-
|
70
|
-
|
71
|
-
logger.debug { "firing on_close handler" }
|
72
|
-
on_close.succeed
|
73
|
-
end
|
94
|
+
logger.debug { "firing on_close handler" }
|
95
|
+
on_close.succeed
|
74
96
|
@cnx = nil
|
75
97
|
end
|
76
98
|
else
|
@@ -83,12 +105,13 @@ module ZK
|
|
83
105
|
|
84
106
|
# get data at path, optionally enabling a watch on the node
|
85
107
|
#
|
86
|
-
# @
|
108
|
+
# @return [Callback] returns a Callback which is an EM::Deferred (so you
|
87
109
|
# can assign callbacks/errbacks) see Callback::Base for discussion
|
88
110
|
#
|
89
111
|
def get(path, opts={}, &block)
|
90
112
|
Callback.new_get_cb(block) do |cb|
|
91
113
|
cb.errback(&method(:connection_lost_hook))
|
114
|
+
cb.context = { :method => __method__, :path => path, :opts => opts }
|
92
115
|
super(path, opts.merge(:callback => cb))
|
93
116
|
end
|
94
117
|
end
|
@@ -96,6 +119,7 @@ module ZK
|
|
96
119
|
def create(path, data='', opts={}, &block)
|
97
120
|
Callback.new_create_cb(block) do |cb|
|
98
121
|
cb.errback(&method(:connection_lost_hook))
|
122
|
+
cb.context = { :method => __method__, :path => path, :data => data, :opts => opts }
|
99
123
|
super(path, data, opts.merge(:callback => cb))
|
100
124
|
end
|
101
125
|
end
|
@@ -103,6 +127,7 @@ module ZK
|
|
103
127
|
def set(path, data, opts={}, &block)
|
104
128
|
Callback.new_set_cb(block) do |cb|
|
105
129
|
cb.errback(&method(:connection_lost_hook))
|
130
|
+
cb.context = { :method => __method__, :path => path, :data => data, :opts => opts }
|
106
131
|
super(path, data, opts.merge(:callback => cb))
|
107
132
|
end
|
108
133
|
end
|
@@ -114,6 +139,7 @@ module ZK
|
|
114
139
|
|
115
140
|
Callback.__send__(meth, block) do |cb|
|
116
141
|
cb.errback(&method(:connection_lost_hook))
|
142
|
+
cb.context = { :method => __method__, :path => path, :meth => meth, :opts => opts }
|
117
143
|
super(path, opts.merge(:callback => cb))
|
118
144
|
end
|
119
145
|
end
|
@@ -125,6 +151,7 @@ module ZK
|
|
125
151
|
def delete(path, opts={}, &block)
|
126
152
|
Callback.new_delete_cb(block) do |cb|
|
127
153
|
cb.errback(&method(:connection_lost_hook))
|
154
|
+
cb.context = { :method => __method__, :path => path, :opts => opts }
|
128
155
|
super(path, opts.merge(:callback => cb))
|
129
156
|
end
|
130
157
|
end
|
@@ -132,6 +159,7 @@ module ZK
|
|
132
159
|
def children(path, opts={}, &block)
|
133
160
|
Callback.new_children_cb(block) do |cb|
|
134
161
|
cb.errback(&method(:connection_lost_hook))
|
162
|
+
cb.context = { :method => __method__, :path => path, :opts => opts }
|
135
163
|
super(path, opts.merge(:callback => cb))
|
136
164
|
end
|
137
165
|
end
|
@@ -139,6 +167,7 @@ module ZK
|
|
139
167
|
def get_acl(path, opts={}, &block)
|
140
168
|
Callback.new_get_acl_cb(block) do |cb|
|
141
169
|
cb.errback(&method(:connection_lost_hook))
|
170
|
+
cb.context = { :method => __method__, :path => path, :opts => opts }
|
142
171
|
super(path, opts.merge(:callback => cb))
|
143
172
|
end
|
144
173
|
end
|
@@ -146,25 +175,52 @@ module ZK
|
|
146
175
|
def set_acl(path, acls, opts={}, &block)
|
147
176
|
Callback.new_set_acl_cb(block) do |cb|
|
148
177
|
cb.errback(&method(:connection_lost_hook))
|
178
|
+
cb.context = { :method => __method__, :path => path, :acls => acls, :opts => opts }
|
149
179
|
super(path, acls, opts.merge(:callback => cb))
|
150
180
|
end
|
151
181
|
end
|
152
182
|
|
153
|
-
#
|
154
|
-
def
|
155
|
-
|
183
|
+
# @return [Fixnum] The underlying connection's session_id
|
184
|
+
def session_id
|
185
|
+
return nil unless @cnx
|
186
|
+
@cnx.session_id
|
156
187
|
end
|
157
188
|
|
158
|
-
#
|
159
|
-
def
|
160
|
-
|
189
|
+
# @return [String] The underlying connection's session passwd (an opaque value)
|
190
|
+
def session_passwd
|
191
|
+
return nil unless @cnx
|
192
|
+
@cnx.session_passwd
|
161
193
|
end
|
162
194
|
|
163
195
|
protected
|
196
|
+
# @private
|
197
|
+
def register_default_event_handlers!
|
198
|
+
@event_handler.register_state_handler(Zookeeper::ZOO_EXPIRED_SESSION_STATE, &method(:handle_expired_session_state_event!))
|
199
|
+
@event_handler.register_state_handler(Zookeeper::ZOO_CONNECTED_STATE, &method(:handle_connected_state_event!))
|
200
|
+
@event_handler.register_state_handler(Zookeeper::ZOO_CONNECTING_STATE, &method(:handle_connecting_state_event!))
|
201
|
+
end
|
202
|
+
|
203
|
+
# @private
|
204
|
+
def handle_connected_state_event!(event)
|
205
|
+
EM.schedule { reset_connected_event.succeed(event) }
|
206
|
+
end
|
207
|
+
|
208
|
+
# @private
|
209
|
+
def handle_connecting_state_event!(event)
|
210
|
+
EM.schedule { reset_connecting_event.succeed(event) }
|
211
|
+
end
|
212
|
+
|
213
|
+
# @private
|
214
|
+
def handle_expired_session_state_event!(event)
|
215
|
+
exc = ZK::Exceptions::ConnectionLoss.new("Received EXPIRED_SESSION_STATE event: #{event.inspect}")
|
216
|
+
exc.set_backtrace(caller)
|
217
|
+
connection_lost_hook(exc)
|
218
|
+
end
|
219
|
+
|
220
|
+
# @private
|
164
221
|
def connection_lost_hook(exc)
|
165
222
|
if exc and exc.kind_of?(ZK::Exceptions::ConnectionLoss)
|
166
|
-
|
167
|
-
dfr.succeed(exc)
|
223
|
+
EM.schedule { reset_connection_lost_event.succeed(exc) }
|
168
224
|
end
|
169
225
|
end
|
170
226
|
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# Taken from EventMachine release candidate
|
2
|
+
module ZK
|
3
|
+
module ZKEventMachine
|
4
|
+
# A simple iterator for concurrent asynchronous work.
|
5
|
+
#
|
6
|
+
# Unlike ruby's built-in iterators, the end of the current iteration cycle is signaled manually,
|
7
|
+
# instead of happening automatically after the yielded block finishes executing. For example:
|
8
|
+
#
|
9
|
+
# (0..10).each{ |num| }
|
10
|
+
#
|
11
|
+
# becomes:
|
12
|
+
#
|
13
|
+
# EM::Iterator.new(0..10).each{ |num,iter| iter.next }
|
14
|
+
#
|
15
|
+
# This is especially useful when doing asynchronous work via reactor libraries and
|
16
|
+
# functions. For example, given a sync and async http api:
|
17
|
+
#
|
18
|
+
# response = sync_http_get(url); ...
|
19
|
+
# async_http_get(url){ |response| ... }
|
20
|
+
#
|
21
|
+
# a synchronous iterator such as:
|
22
|
+
#
|
23
|
+
# responses = urls.map{ |url| sync_http_get(url) }
|
24
|
+
# ...
|
25
|
+
# puts 'all done!'
|
26
|
+
#
|
27
|
+
# could be written as:
|
28
|
+
#
|
29
|
+
# EM::Iterator.new(urls).map(proc{ |url,iter|
|
30
|
+
# async_http_get(url){ |res|
|
31
|
+
# iter.return(res)
|
32
|
+
# }
|
33
|
+
# }, proc{ |responses|
|
34
|
+
# ...
|
35
|
+
# puts 'all done!'
|
36
|
+
# })
|
37
|
+
#
|
38
|
+
# Now, you can take advantage of the asynchronous api to issue requests in parallel. For example,
|
39
|
+
# to fetch 10 urls at a time, simply pass in a concurrency of 10:
|
40
|
+
#
|
41
|
+
# EM::Iterator.new(urls, 10).each do |url,iter|
|
42
|
+
# async_http_get(url){ iter.next }
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
class Iterator
|
46
|
+
# Create a new parallel async iterator with specified concurrency.
|
47
|
+
#
|
48
|
+
# i = EM::Iterator.new(1..100, 10)
|
49
|
+
#
|
50
|
+
# will create an iterator over the range that processes 10 items at a time. Iteration
|
51
|
+
# is started via #each, #map or #inject
|
52
|
+
#
|
53
|
+
def initialize(list, concurrency = 1)
|
54
|
+
raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a)
|
55
|
+
@list = list.to_a.dup
|
56
|
+
@concurrency = concurrency
|
57
|
+
|
58
|
+
@started = false
|
59
|
+
@ended = false
|
60
|
+
end
|
61
|
+
|
62
|
+
# Change the concurrency of this iterator. Workers will automatically be spawned or destroyed
|
63
|
+
# to accomodate the new concurrency level.
|
64
|
+
#
|
65
|
+
def concurrency=(val)
|
66
|
+
old = @concurrency
|
67
|
+
@concurrency = val
|
68
|
+
|
69
|
+
spawn_workers if val > old and @started and !@ended
|
70
|
+
end
|
71
|
+
attr_reader :concurrency
|
72
|
+
|
73
|
+
# Iterate over a set of items using the specified block or proc.
|
74
|
+
#
|
75
|
+
# EM::Iterator.new(1..100).each do |num, iter|
|
76
|
+
# puts num
|
77
|
+
# iter.next
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# An optional second proc is invoked after the iteration is complete.
|
81
|
+
#
|
82
|
+
# EM::Iterator.new(1..100).each(
|
83
|
+
# proc{ |num,iter| iter.next },
|
84
|
+
# proc{ puts 'all done' }
|
85
|
+
# )
|
86
|
+
#
|
87
|
+
def each(foreach=nil, after=nil, &blk)
|
88
|
+
raise ArgumentError, 'proc or block required for iteration' unless foreach ||= blk
|
89
|
+
raise RuntimeError, 'cannot iterate over an iterator more than once' if @started or @ended
|
90
|
+
|
91
|
+
@started = true
|
92
|
+
@pending = 0
|
93
|
+
@workers = 0
|
94
|
+
|
95
|
+
all_done = proc{
|
96
|
+
after.call if after and @ended and @pending == 0
|
97
|
+
}
|
98
|
+
|
99
|
+
@process_next = proc{
|
100
|
+
# p [:process_next, :pending=, @pending, :workers=, @workers, :ended=, @ended, :concurrency=, @concurrency, :list=, @list]
|
101
|
+
unless @ended or @workers > @concurrency
|
102
|
+
if @list.empty?
|
103
|
+
@ended = true
|
104
|
+
@workers -= 1
|
105
|
+
all_done.call
|
106
|
+
else
|
107
|
+
item = @list.shift
|
108
|
+
@pending += 1
|
109
|
+
|
110
|
+
is_done = false
|
111
|
+
on_done = proc{
|
112
|
+
raise RuntimeError, 'already completed this iteration' if is_done
|
113
|
+
is_done = true
|
114
|
+
|
115
|
+
@pending -= 1
|
116
|
+
|
117
|
+
if @ended
|
118
|
+
all_done.call
|
119
|
+
else
|
120
|
+
EM.next_tick(@process_next)
|
121
|
+
end
|
122
|
+
}
|
123
|
+
class << on_done
|
124
|
+
alias :next :call
|
125
|
+
end
|
126
|
+
|
127
|
+
foreach.call(item, on_done)
|
128
|
+
end
|
129
|
+
else
|
130
|
+
@workers -= 1
|
131
|
+
end
|
132
|
+
}
|
133
|
+
|
134
|
+
spawn_workers
|
135
|
+
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# Collect the results of an asynchronous iteration into an array.
|
140
|
+
#
|
141
|
+
# EM::Iterator.new(%w[ pwd uptime uname date ], 2).map(proc{ |cmd,iter|
|
142
|
+
# EM.system(cmd){ |output,status|
|
143
|
+
# iter.return(output)
|
144
|
+
# }
|
145
|
+
# }, proc{ |results|
|
146
|
+
# p results
|
147
|
+
# })
|
148
|
+
#
|
149
|
+
def map(foreach, after)
|
150
|
+
index = 0
|
151
|
+
|
152
|
+
inject([], proc{ |results,item,iter|
|
153
|
+
i = index
|
154
|
+
index += 1
|
155
|
+
|
156
|
+
is_done = false
|
157
|
+
on_done = proc{ |res|
|
158
|
+
raise RuntimeError, 'already returned a value for this iteration' if is_done
|
159
|
+
is_done = true
|
160
|
+
|
161
|
+
results[i] = res
|
162
|
+
iter.return(results)
|
163
|
+
}
|
164
|
+
class << on_done
|
165
|
+
alias :return :call
|
166
|
+
def next
|
167
|
+
raise NoMethodError, 'must call #return on a map iterator'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
foreach.call(item, on_done)
|
172
|
+
}, proc{ |results|
|
173
|
+
after.call(results)
|
174
|
+
})
|
175
|
+
end
|
176
|
+
|
177
|
+
# Inject the results of an asynchronous iteration onto a given object.
|
178
|
+
#
|
179
|
+
# EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter|
|
180
|
+
# EM.system(cmd){ |output,status|
|
181
|
+
# hash[cmd] = status.exitstatus == 0 ? output.strip : nil
|
182
|
+
# iter.return(hash)
|
183
|
+
# }
|
184
|
+
# }, proc{ |results|
|
185
|
+
# p results
|
186
|
+
# })
|
187
|
+
#
|
188
|
+
def inject(obj, foreach, after)
|
189
|
+
each(proc{ |item,iter|
|
190
|
+
is_done = false
|
191
|
+
on_done = proc{ |res|
|
192
|
+
raise RuntimeError, 'already returned a value for this iteration' if is_done
|
193
|
+
is_done = true
|
194
|
+
|
195
|
+
obj = res
|
196
|
+
iter.next
|
197
|
+
}
|
198
|
+
class << on_done
|
199
|
+
alias :return :call
|
200
|
+
def next
|
201
|
+
raise NoMethodError, 'must call #return on an inject iterator'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
foreach.call(obj, item, on_done)
|
206
|
+
}, proc{
|
207
|
+
after.call(obj)
|
208
|
+
})
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Spawn workers to consume items from the iterator's enumerator based on the current concurrency level.
|
214
|
+
#
|
215
|
+
def spawn_workers
|
216
|
+
EM.next_tick(start_worker = proc{
|
217
|
+
if @workers < @concurrency and !@ended
|
218
|
+
# p [:spawning_worker, :workers=, @workers, :concurrency=, @concurrency, :ended=, @ended]
|
219
|
+
@workers += 1
|
220
|
+
@process_next.call
|
221
|
+
EM.next_tick(start_worker)
|
222
|
+
end
|
223
|
+
})
|
224
|
+
nil
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
@@ -3,7 +3,7 @@ module ZK
|
|
3
3
|
module Unixisms
|
4
4
|
def mkdir_p(paths, &block)
|
5
5
|
dfr = Deferred::Default.new.tap do |my_dfr|
|
6
|
-
|
6
|
+
Iterator.new(Array(paths).flatten.compact, 1).map(
|
7
7
|
lambda { |path,iter| # foreach
|
8
8
|
d = _mkdir_p_dfr(path)
|
9
9
|
d.callback { |p| iter.return(p) }
|
@@ -18,7 +18,7 @@ module ZK
|
|
18
18
|
|
19
19
|
def rm_rf(paths, &blk)
|
20
20
|
dfr = Deferred::Default.new.tap do |my_dfr|
|
21
|
-
|
21
|
+
Iterator.new(Array(paths).flatten.compact, 1).each(
|
22
22
|
lambda { |path,iter| # foreach
|
23
23
|
d = _rm_rf_dfr(path)
|
24
24
|
d.callback { iter.next }
|
@@ -31,15 +31,18 @@ module ZK
|
|
31
31
|
_handle_calling_convention(dfr, &blk)
|
32
32
|
end
|
33
33
|
|
34
|
+
# @private
|
34
35
|
def find(*paths, &block)
|
35
36
|
raise NotImplementedError, "Coming soon"
|
36
37
|
end
|
37
38
|
|
39
|
+
# @private
|
38
40
|
def block_until_node_deleted(abs_node_path)
|
39
41
|
raise NotImplementedError, "blocking does not make sense in EventMachine-land"
|
40
42
|
end
|
41
43
|
|
42
44
|
protected
|
45
|
+
# @private
|
43
46
|
def _handle_calling_convention(dfr, &blk)
|
44
47
|
return dfr unless blk
|
45
48
|
dfr.callback { |*a| blk.call(nil, *a) }
|
@@ -47,6 +50,7 @@ module ZK
|
|
47
50
|
dfr
|
48
51
|
end
|
49
52
|
|
53
|
+
# @private
|
50
54
|
def _rm_rf_dfr(path)
|
51
55
|
Deferred::Default.new.tap do |my_dfr|
|
52
56
|
delete(path) do |exc|
|
@@ -60,7 +64,7 @@ module ZK
|
|
60
64
|
my_dfr.succeed
|
61
65
|
when nil
|
62
66
|
abspaths = chldrn.map { |n| [path, n].join('/') }
|
63
|
-
|
67
|
+
Iterator.new(abspaths).each(
|
64
68
|
lambda { |absp,iter|
|
65
69
|
d = _rm_rf_dfr(absp)
|
66
70
|
d.callback { |*|
|
@@ -85,6 +89,7 @@ module ZK
|
|
85
89
|
end
|
86
90
|
end
|
87
91
|
|
92
|
+
# @private
|
88
93
|
def _mkdir_p_dfr(path)
|
89
94
|
Deferred::Default.new.tap do |my_dfr|
|
90
95
|
d = create(path, '')
|