zk-eventmachine 0.2.0.beta.3 → 0.9.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.
- 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, '')
|