zk 0.6.5 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/z_k/election.rb CHANGED
@@ -347,6 +347,8 @@ module ZK
347
347
  @observing = true
348
348
 
349
349
  @leader_ack_sub ||= @zk.watcher.register(leader_ack_path) do |event|
350
+ logger.debug { "leader_ack_callback, event.node_deleted? #{event.node_deleted?}, event.node_created? #{event.node_created?}" }
351
+
350
352
  if event.node_deleted?
351
353
  the_king_is_dead
352
354
  elsif event.node_created?
@@ -354,6 +356,7 @@ module ZK
354
356
  else
355
357
  acked = leader_acked?(true)
356
358
 
359
+
357
360
  # If the current state of the system is not what we think it should be
358
361
  # a transition has occurred and we should fire our callbacks
359
362
  if (acked and !@leader_alive)
@@ -43,7 +43,7 @@ module ZK
43
43
  # @see ZooKeeper::WatcherEvent
44
44
  # @see ZooKeeper::EventHandlerSubscription
45
45
  def register(path, &block)
46
- logger.debug { "EventHandler#register path=#{path.inspect}" }
46
+ # logger.debug { "EventHandler#register path=#{path.inspect}" }
47
47
  EventHandlerSubscription.new(self, path, block).tap do |subscription|
48
48
  synchronize { @callbacks[path] << subscription }
49
49
  end
@@ -94,7 +94,7 @@ module ZK
94
94
 
95
95
  # called from the client-registered callback when an event fires
96
96
  def process(event) #:nodoc:
97
- logger.debug { "EventHandler#process dispatching event: #{event.inspect}" } unless event.type == -1
97
+ # logger.debug { "EventHandler#process dispatching event: #{event.inspect}" }# unless event.type == -1
98
98
  event.zk = @zk
99
99
 
100
100
  cb_key =
@@ -106,10 +106,12 @@ module ZK
106
106
  raise ZKError, "don't know how to process event: #{event.inspect}"
107
107
  end
108
108
 
109
+ # logger.debug { "EventHandler#process: cb_key: #{cb_key}" }
110
+
109
111
  cb_ary = synchronize do
110
112
  if event.node_event?
111
113
  if watch_type = ZOOKEEPER_WATCH_TYPE_MAP[event.type]
112
- logger.debug { "re-allowing #{watch_type.inspect} watches on path #{event.path.inspect}" }
114
+ # logger.debug { "re-allowing #{watch_type.inspect} watches on path #{event.path.inspect}" }
113
115
 
114
116
  # we recieved a watch event for this path, now we allow code to set new watchers
115
117
  @outstanding_watches[watch_type].delete(event.path)
@@ -161,7 +163,7 @@ module ZK
161
163
  opts[:watcher] = watcher_callback
162
164
  else
163
165
  # outstanding watch for path and data pair already exists, so ignore
164
- logger.debug { "outstanding watch request for path #{path.inspect} and watcher type #{watch_type.inspect}, not re-registering" }
166
+ # logger.debug { "outstanding watch request for path #{path.inspect} and watcher type #{watch_type.inspect}, not re-registering" }
165
167
  end
166
168
  end
167
169
  end
@@ -188,7 +190,7 @@ module ZK
188
190
  end
189
191
 
190
192
  def safe_call(callbacks, *args)
191
- callbacks.each do |cb|
193
+ while cb = callbacks.shift
192
194
  begin
193
195
  cb.call(*args) if cb.respond_to?(:call)
194
196
  rescue Exception => e
data/lib/z_k/mongoid.rb CHANGED
@@ -5,7 +5,7 @@ module ZK
5
5
  #
6
6
  # Before use (in one of your Rails initializers, for example) you should
7
7
  # assign either a ZK::Client or ZK::Pool subclass to
8
- # ZooKeeperLockMixin.zk_lock_pool.
8
+ # ZK::Mongoid::Locking.zk_lock_pool.
9
9
  #
10
10
  # this class assumes the availability of a 'logger' method in the mixee
11
11
  #
data/lib/z_k/pool.rb CHANGED
@@ -6,9 +6,15 @@ module ZK
6
6
  def initialize
7
7
  @state = :init
8
8
 
9
- @connections = []
10
- @connections.extend(MonitorMixin)
11
- @checkin_cond = @connections.new_cond
9
+ @mutex = Monitor.new
10
+ @checkin_cond = @mutex.new_cond
11
+
12
+ @connections = [] # all connections we control
13
+ @pool = [] # currently available connections
14
+
15
+ # this is required for 1.8.7 compatibility
16
+ @on_connected_subs = {}
17
+ @on_connected_subs.extend(MonitorMixin)
12
18
  end
13
19
 
14
20
  # has close_all! been called on this ConnectionPool ?
@@ -34,7 +40,7 @@ module ZK
34
40
  # close all the connections on the pool
35
41
  # @param optional Boolean graceful allow the checked out connections to come back first?
36
42
  def close_all!
37
- synchronize do
43
+ @mutex.synchronize do
38
44
  return unless open?
39
45
  @state = :closing
40
46
 
@@ -47,7 +53,7 @@ module ZK
47
53
  # calls close! on all connection objects, whether or not they're back in the pool
48
54
  # this is DANGEROUS!
49
55
  def force_close! #:nodoc:
50
- synchronize do
56
+ @mutex.synchronize do
51
57
  return if (closed? or forced?)
52
58
  @state = :forced
53
59
 
@@ -99,7 +105,7 @@ module ZK
99
105
  end
100
106
 
101
107
  def size #:nodoc:
102
- @pool.size
108
+ @connection.synchronize { @pool.size }
103
109
  end
104
110
 
105
111
  def pool_state #:nodoc:
@@ -108,7 +114,7 @@ module ZK
108
114
 
109
115
  protected
110
116
  def synchronize
111
- @connections.synchronize { yield }
117
+ @mutex.synchronize { yield }
112
118
  end
113
119
 
114
120
  def assert_open!
@@ -140,10 +146,9 @@ module ZK
140
146
  @max_clients = Integer(opts.delete(:max_clients))
141
147
  @connection_timeout = opts.delete(:timeout)
142
148
 
143
- # for compatibility w/ ClientPool we'll use @connections for synchronization
144
- @pool = [] # currently available connections
149
+ @count_waiters = 0
145
150
 
146
- synchronize do
151
+ @mutex.synchronize do
147
152
  populate_pool!(@min_clients)
148
153
  @state = :open
149
154
  end
@@ -152,41 +157,48 @@ module ZK
152
157
  # returns the current number of allocated clients in the pool (not
153
158
  # available clients)
154
159
  def size
155
- @connections.length
160
+ @mutex.synchronize { @connections.length }
156
161
  end
157
162
 
158
163
  # clients available for checkout (at time of call)
159
164
  def available_size
160
- @pool.length
165
+ @mutex.synchronize { @pool.length }
161
166
  end
162
167
 
163
168
  def checkin(connection)
164
- synchronize do
165
- return if @pool.include?(connection)
169
+ @mutex.synchronize do
170
+ if @pool.include?(connection)
171
+ logger.debug { "Pool already contains connection: #{connection.object_id}, @connections.include? #{@connections.include?(connection).inspect}" }
172
+ return
173
+ end
174
+
175
+ @pool << connection
166
176
 
167
- @pool.unshift(connection)
168
177
  @checkin_cond.signal
169
178
  end
170
179
  end
171
180
 
172
181
  # number of threads waiting for connections
173
182
  def count_waiters #:nodoc:
174
- @checkin_cond.count_waiters
183
+ @mutex.synchronize { @count_waiters }
175
184
  end
176
185
 
177
186
  def checkout(blocking=true)
178
187
  raise ArgumentError, "checkout does not take a block, use .with_connection" if block_given?
179
- synchronize do
188
+ @mutex.synchronize do
180
189
  while true
181
190
  assert_open!
182
191
 
183
192
  if @pool.length > 0
184
193
  cnx = @pool.shift
185
194
 
186
- # if the cnx isn't connected? then remove it from the pool and go
187
- # through the loop again. when the cnx's on_connected event fires, it
188
- # will add the connection back into the pool
189
- next unless cnx.connected?
195
+ # if the connection isn't connected, then set up an on_connection
196
+ # handler and try the next one in the pool
197
+ unless cnx.connected?
198
+ logger.debug { "connection #{cnx.object_id} is not connected" }
199
+ handle_checkin_on_connection(cnx)
200
+ next
201
+ end
190
202
 
191
203
  # otherwise we return the cnx
192
204
  return cnx
@@ -199,26 +211,57 @@ module ZK
199
211
  else
200
212
  return false
201
213
  end
202
- end
214
+ end # while
203
215
  end
204
216
  end
205
217
 
218
+ # @private
219
+ def can_grow_pool?
220
+ @mutex.synchronize { @connections.size < @max_clients }
221
+ end
222
+
206
223
  protected
207
224
  def populate_pool!(num_cnx)
208
225
  num_cnx.times { add_connection! }
209
226
  end
210
227
 
211
228
  def add_connection!
212
- synchronize do
229
+ @mutex.synchronize do
213
230
  cnx = create_connection
214
231
  @connections << cnx
215
232
 
216
- cnx.on_connected { checkin(cnx) }
217
- end
233
+ handle_checkin_on_connection(cnx)
234
+ end # synchronize
218
235
  end
219
236
 
220
- def can_grow_pool?
221
- synchronize { @connections.size < @max_clients }
237
+ def handle_checkin_on_connection(cnx)
238
+ @mutex.synchronize do
239
+ do_checkin = lambda do
240
+ checkin(cnx)
241
+ end
242
+
243
+ if cnx.connected?
244
+ do_checkin.call
245
+ return
246
+ else
247
+ @on_connected_subs.synchronize do
248
+
249
+ sub = cnx.on_connected do
250
+ # this synchronization is to prevent a race between setting up the subscription
251
+ # and assigning it to the @on_connected_subs hash. It's possible that the callback
252
+ # would fire before we had a chance to add the sub to the hash.
253
+ @on_connected_subs.synchronize do
254
+ if sub = @on_connected_subs.delete(cnx)
255
+ sub.unsubscribe
256
+ do_checkin.call
257
+ end
258
+ end
259
+ end
260
+
261
+ @on_connected_subs[cnx] = sub
262
+ end
263
+ end
264
+ end
222
265
  end
223
266
 
224
267
  def create_connection
@@ -72,7 +72,9 @@ module ZK
72
72
  @threadqueue.clear
73
73
  @size.times { @threadqueue << KILL_TOKEN }
74
74
 
75
- while th = @threadpool.shift
75
+ threads, @threadpool = @threadpool, []
76
+
77
+ while th = threads.shift
76
78
  begin
77
79
  th.join(timeout)
78
80
  rescue Exception => e
@@ -80,6 +82,8 @@ module ZK
80
82
  logger.error { e.to_std_format }
81
83
  end
82
84
  end
85
+
86
+ @threadqueue = ::Queue.new
83
87
  end
84
88
 
85
89
  nil
@@ -87,11 +91,12 @@ module ZK
87
91
 
88
92
  private
89
93
  def spawn_threadpool #:nodoc:
90
- until @threadpool.size == @size.to_i
94
+ until @threadpool.size >= @size.to_i
91
95
  thread = Thread.new do
92
96
  while @running
93
97
  begin
94
98
  op = @threadqueue.pop
99
+ # $stderr.puts "thread #{Thread.current.inspect} got #{op.inspect}"
95
100
  break if op == KILL_TOKEN
96
101
  op.call
97
102
  rescue Exception => e
data/lib/z_k/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ZK
2
- VERSION = "0.6.5"
2
+ VERSION = "0.7.1"
3
3
  end
data/lib/z_k.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'rubygems'
2
- require 'bundler/setup'
3
2
 
4
3
  require 'logger'
5
4
  require 'zookeeper'
@@ -26,7 +25,7 @@ require 'z_k/find'
26
25
  module ZK
27
26
  ZK_ROOT = File.expand_path('../..', __FILE__)
28
27
 
29
- KILL_TOKEN = :__kill_token__ #:nodoc:
28
+ KILL_TOKEN = :__ZK_kILL_tOkEn__ #:nodoc:
30
29
 
31
30
 
32
31
  # The logger used by the ZK library. uses a Logger to +/dev/null+ by default
data/spec/spec_helper.rb CHANGED
@@ -89,6 +89,7 @@ class ::Thread
89
89
  end
90
90
 
91
91
  def report_realtime(what)
92
+ return yield
92
93
  t = Benchmark.realtime { yield }
93
94
  $stderr.puts "#{what}: %0.3f" % [t.to_f]
94
95
  end
@@ -0,0 +1,14 @@
1
+ require 'rspec/core/formatters/progress_formatter'
2
+
3
+ module Motionbox
4
+ # essentially a monkey-patch to the ProgressBarFormatter, outputs
5
+ # '== #{example_proxy.description} ==' in the logs before each test. makes it
6
+ # easier to match up tests with the SQL they produce
7
+ class LoggingProgressBarFormatter < RSpec::Core::Formatters::ProgressFormatter
8
+ def example_started(example)
9
+ ZK.logger.info(yellow("\n=====<([ #{example.full_description} ])>=====\n"))
10
+ super
11
+ end
12
+ end
13
+ end
14
+
data/spec/watch_spec.rb CHANGED
@@ -100,18 +100,36 @@ describe ZK do
100
100
  end
101
101
 
102
102
  describe 'state watcher' do
103
- before do
104
- @event = nil
105
- @cnx_str = "localhost:#{ZK_TEST_PORT}"
103
+ describe 'live-fire test' do
104
+ before do
105
+ @event = nil
106
+ @cnx_str = "localhost:#{ZK_TEST_PORT}"
107
+
108
+ @zk = ZK.new(@cnx_str) do |zk|
109
+ @cnx_reg = zk.on_connected { |event| @event = event }
110
+ end
111
+ end
106
112
 
107
- @zk = ZK.new(@cnx_str) do |zk|
108
- @cnx_reg = zk.on_connected { |event| @event = event }
113
+ it %[should fire the registered callback] do
114
+ wait_while { @event.nil? }
115
+ @event.should_not be_nil
109
116
  end
110
117
  end
111
118
 
112
- it %[should fire the registered callback] do
113
- wait_while { @event.nil? }
114
- @event.should_not be_nil
119
+ describe 'registered listeners' do
120
+ before do
121
+ @event = flexmock(:event) do |m|
122
+ m.should_receive(:type).and_return(-1)
123
+ m.should_receive(:zk=).with(any())
124
+ m.should_receive(:node_event?).and_return(false)
125
+ m.should_receive(:state_event?).and_return(true)
126
+ m.should_receive(:state).and_return(ZookeeperConstants::ZOO_CONNECTED_STATE)
127
+ end
128
+ end
129
+
130
+ it %[should only fire the callback once] do
131
+ pending "not sure if this is the behavior we want"
132
+ end
115
133
  end
116
134
  end
117
135
  end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), %w[spec_helper])
1
+ require 'spec_helper'
2
2
 
3
3
  describe ZK::Client do
4
4
  before do
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), %w[spec_helper])
1
+ require 'spec_helper'
2
2
 
3
3
  describe ZK::Election do
4
4
  before do
@@ -262,10 +262,9 @@ describe ZK::Election do
262
262
  describe 'leadership transition' do
263
263
  before do
264
264
  @obama.vote!
265
- @palin.vote!
266
-
267
265
  wait_until { @obama.leader? }
268
266
 
267
+ @palin.vote!
269
268
  @palin.should_not be_leader
270
269
 
271
270
  @got_life_event = @got_death_event = false
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), %w[spec_helper])
1
+ require 'spec_helper'
2
2
 
3
3
  # this is a remnant of the old Locker class, but a good test of what's expected
4
4
  # from ZK::Client#locker
@@ -1,4 +1,4 @@
1
- require File.expand_path('../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  require 'tracer'
4
4