zookeeper 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,4 +1,13 @@
1
- v1.1.2 backport change from 1.2.1: make assert_open more sane
1
+ v1.2.0 Stop the World, I Wanna fork()
2
+
3
+ * changed pause/resume methods to pause_before_fork_in_parent
4
+ and resume_after_fork_in_parent to match their ZK counterparts
5
+
6
+ * replaced the Queue in QueueWithPipe (really have to change that name)
7
+ with an Array and Mutex/ConditionVariable pair. This allows us to
8
+ have better control over the shutdown signaling, and lets us resume
9
+ operations after a pause.
10
+
2
11
 
3
12
  v1.1.1 Cleanup after code review (h/t @eric)
4
13
 
data/Guardfile CHANGED
@@ -1,6 +1,8 @@
1
- guard 'rspec' do
1
+
2
+ guard 'rspec', :version => 2, :cli => '-c -f progress --fail-fast' do
2
3
  watch(%r{^spec/.+_spec.rb$})
3
4
  watch(%r{^lib/(.+)\.rb$}) { |m| %w[spec/zookeeper_spec.rb spec/chrooted_connection_spec.rb] }
4
5
  watch(%r{^ext/zookeeper_c.bundle}) { %w[spec/c_zookeeper_spec.rb] }
6
+ watch(%r{^ext/zookeeper_base.rb}) { "spec" }
5
7
  end
6
8
 
data/ext/c_zookeeper.rb CHANGED
@@ -139,13 +139,13 @@ class CZookeeper
139
139
  #
140
140
  # requests may still be added during this time, but they will not be
141
141
  # processed until you call resume
142
- def pause
142
+ def pause_before_fork_in_parent
143
143
  logger.debug { "#{self.class}##{__method__}" }
144
144
  @mutex.synchronize { stop_event_thread }
145
145
  end
146
146
 
147
147
  # call this if 'pause' was previously called to start the event loop again
148
- def resume
148
+ def resume_after_fork_in_parent
149
149
  logger.debug { "#{self.class}##{__method__}" }
150
150
 
151
151
  @mutex.synchronize do
@@ -184,7 +184,7 @@ class CZookeeper
184
184
  @reg.lock
185
185
  begin
186
186
  if meth == :state
187
- @reg.pending.unshift(cnt)
187
+ @reg.state_check << cnt
188
188
  else
189
189
  @reg.pending << cnt
190
190
  end
@@ -228,7 +228,7 @@ class CZookeeper
228
228
  @running_cond.wait(timeout)
229
229
  !!@_running
230
230
  ensure
231
- @mutex.unlock
231
+ @mutex.unlock rescue nil
232
232
  end
233
233
  end
234
234
 
@@ -314,7 +314,7 @@ class CZookeeper
314
314
 
315
315
  raise ShuttingDownException if @_shutting_down
316
316
  ensure
317
- @mutex.unlock
317
+ @mutex.unlock rescue nil
318
318
  end
319
319
  end
320
320
 
@@ -334,7 +334,7 @@ class CZookeeper
334
334
  @_running = true
335
335
  @running_cond.broadcast
336
336
  ensure
337
- @mutex.unlock
337
+ @mutex.unlock rescue nil
338
338
  end
339
339
  end
340
340
 
@@ -343,7 +343,7 @@ class CZookeeper
343
343
  begin
344
344
  @connected_cond.broadcast
345
345
  ensure
346
- @mutex.unlock
346
+ @mutex.unlock rescue nil
347
347
  end
348
348
  end
349
349
  end
@@ -32,7 +32,7 @@ class ZookeeperBase
32
32
 
33
33
 
34
34
  def_delegators :@czk, :get_children, :exists, :delete, :get, :set,
35
- :set_acl, :get_acl, :client_id, :sync, :wait_until_connected, :pause, :resume
35
+ :set_acl, :get_acl, :client_id, :sync, :wait_until_connected
36
36
 
37
37
  # some state methods need to be more paranoid about locking to ensure the correct
38
38
  # state is returned
@@ -117,7 +117,8 @@ class ZookeeperBase
117
117
  # if either of these happen, the user will need to renegotiate a connection via reopen
118
118
  def assert_open
119
119
  @mutex.synchronize do
120
- raise Exceptions::NotConnected if closed?
120
+ raise Exceptions::SessionExpired if state == ZOO_EXPIRED_SESSION_STATE
121
+ raise Exceptions::NotConnected unless connected?
121
122
  if forked?
122
123
  raise InheritedConnectionError, <<-EOS.gsub(/(?:^|\n)\s*/, ' ').strip
123
124
  You tried to use a connection inherited from another process
@@ -193,6 +194,29 @@ class ZookeeperBase
193
194
  @mutex.synchronize { !@czk or @czk.closed? }
194
195
  end
195
196
 
197
+ def pause_before_fork_in_parent
198
+ @mutex.synchronize do
199
+ logger.debug { "ZookeeperBase#pause_before_fork_in_parent" }
200
+
201
+ # XXX: add anal-retentive state checking
202
+ raise "EXPLODERATE! @czk was nil!" unless @czk
203
+
204
+ @czk.pause_before_fork_in_parent
205
+ stop_dispatch_thread!
206
+ end
207
+ end
208
+
209
+ def resume_after_fork_in_parent
210
+ @mutex.synchronize do
211
+ logger.debug { "ZookeeperBase#resume_after_fork_in_parent" }
212
+
213
+ raise "EXPLODERATE! @czk was nil!" unless @czk
214
+
215
+ event_queue.open
216
+ setup_dispatch_thread!
217
+ @czk.resume_after_fork_in_parent
218
+ end
219
+ end
196
220
 
197
221
  protected
198
222
  # this is a hack: to provide consistency between the C and Java drivers when
data/java/java_base.rb CHANGED
@@ -435,6 +435,14 @@ class JavaBase
435
435
  @connected_latch.release
436
436
  end
437
437
 
438
+ def pause_before_fork_in_parent
439
+ # this is a no-op in java-land
440
+ end
441
+
442
+ def resume_after_fork_in_parent
443
+ # this is a no-op in java-land
444
+ end
445
+
438
446
  protected
439
447
  def jzk
440
448
  @mutex.synchronize { @jzk }
@@ -192,6 +192,16 @@ module ClientMethods
192
192
  super
193
193
  end
194
194
 
195
+ # stop all underlying threads in preparation for a fork()
196
+ def pause_before_fork_in_parent
197
+ super
198
+ end
199
+
200
+ # re-start all underlying threads after performing a fork()
201
+ def resume_after_fork_in_parent
202
+ super
203
+ end
204
+
195
205
  protected
196
206
  # used during shutdown, awaken the event delivery thread if it's blocked
197
207
  # waiting for the next event
@@ -94,7 +94,7 @@ protected
94
94
  #
95
95
  # @dispatcher will be nil when this method exits
96
96
  #
97
- def stop_dispatch_thread!
97
+ def stop_dispatch_thread!(timeout=2)
98
98
  logger.debug { "#{self.class}##{__method__}" }
99
99
 
100
100
  if @dispatcher
@@ -111,8 +111,8 @@ protected
111
111
  #
112
112
  @dispatch_shutdown_cond.wait
113
113
 
114
- # wait for another 2 sec for the thread to join
115
- until @dispatcher.join(2)
114
+ # wait for another timeout sec for the thread to join
115
+ until @dispatcher.join(timeout)
116
116
  logger.error { "Dispatch thread did not join cleanly, waiting" }
117
117
  end
118
118
  @dispatcher = nil
@@ -5,8 +5,6 @@ module Common
5
5
  extend Forwardable
6
6
  include Logger
7
7
 
8
- def_delegators :@queue, :clear
9
-
10
8
  # raised when close has been called, and pop() is performed
11
9
  #
12
10
  class ShutdownException < StandardError; end
@@ -15,57 +13,98 @@ module Common
15
13
  KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
16
14
 
17
15
  def initialize
18
- @queue = Queue.new
16
+ @array = []
19
17
 
20
- @mutex = Mutex.new
21
- @closed = false
18
+ @mutex = Mutex.new
19
+ @cond = ConditionVariable.new
20
+ @closed = false
22
21
  @graceful = false
23
22
  end
24
23
 
24
+ def clear
25
+ @mutex.lock
26
+ begin
27
+ @array.clear
28
+ ensure
29
+ @mutex.unlock rescue nil
30
+ end
31
+ end
32
+
25
33
  def push(obj)
26
- logger.debug { "#{self.class}##{__method__} obj: #{obj.inspect}, kill_token? #{obj == KILL_TOKEN}" }
27
- @queue.push(obj)
34
+ @mutex.lock
35
+ begin
36
+ # raise ShutdownException if (@closed or @graceful)
37
+ @array << obj
38
+ @cond.signal
39
+ ensure
40
+ @mutex.unlock rescue nil
41
+ end
28
42
  end
29
43
 
30
44
  def pop(non_blocking=false)
31
- raise ShutdownException if closed? # this may get us in trouble
45
+ rval = nil
32
46
 
33
- rv = @queue.pop(non_blocking)
47
+ @mutex.lock
48
+ begin
34
49
 
35
- if rv == KILL_TOKEN
36
- close
37
- raise ShutdownException
38
- end
50
+ begin
51
+ raise ShutdownException if @closed # this may get us in trouble
52
+
53
+ rval = @array.shift
54
+
55
+ unless rval
56
+ raise ThreadError if non_blocking # sigh, ruby's stupid behavior
57
+ raise ShutdownException if @graceful # we've processed all the remaining mesages
58
+
59
+ @cond.wait(@mutex) until (@closed or @graceful or (@array.length > 0))
60
+ end
61
+ end until rval
39
62
 
40
- rv
63
+ return rval
64
+
65
+ ensure
66
+ @mutex.unlock rescue nil
67
+ end
41
68
  end
42
69
 
43
70
  # close the queue and causes ShutdownException to be raised on waiting threads
44
71
  def graceful_close!
45
- @mutex.synchronize do
72
+ @mutex.lock
73
+ begin
46
74
  return if @graceful or @closed
47
75
  logger.debug { "#{self.class}##{__method__} gracefully closing" }
48
76
  @graceful = true
49
- push(KILL_TOKEN)
77
+ @cond.broadcast
78
+ ensure
79
+ @mutex.unlock rescue nil
50
80
  end
51
81
  nil
52
82
  end
53
83
 
84
+ def open
85
+ @mutex.lock
86
+ begin
87
+ @closed = @graceful = false
88
+ @cond.broadcast
89
+ ensure
90
+ @mutex.unlock rescue nil
91
+ end
92
+ end
93
+
54
94
  def close
55
- @mutex.synchronize do
95
+ @mutex.lock
96
+ begin
56
97
  return if @closed
57
98
  @closed = true
99
+ @cond.broadcast
100
+ ensure
101
+ @mutex.unlock rescue nil
58
102
  end
59
103
  end
60
104
 
61
105
  def closed?
62
106
  @mutex.synchronize { !!@closed }
63
107
  end
64
-
65
- private
66
- def clear_reads_on_pop?
67
- @clear_reads_on_pop
68
- end
69
108
  end
70
109
  end
71
110
  end
@@ -12,13 +12,13 @@ module Zookeeper
12
12
  # `state_check` are high-priority checks that query the connection about
13
13
  # its current state, they always run before other continuations
14
14
  #
15
- class Registry < Struct.new(:pending, :in_flight)
15
+ class Registry < Struct.new(:pending, :state_check, :in_flight)
16
16
  extend Forwardable
17
17
 
18
18
  def_delegators :@mutex, :lock, :unlock
19
19
 
20
20
  def initialize
21
- super([], {})
21
+ super([], [], {})
22
22
  @mutex = Mutex.new
23
23
  end
24
24
 
@@ -33,7 +33,7 @@ module Zookeeper
33
33
 
34
34
  # does not lock the mutex, returns true if there are pending jobs
35
35
  def anything_to_do?
36
- !pending.empty?
36
+ (pending.length + state_check.length) > 0
37
37
  end
38
38
 
39
39
  # returns the pending continuations, resetting the list
@@ -41,7 +41,7 @@ module Zookeeper
41
41
  def next_batch()
42
42
  @mutex.lock
43
43
  begin
44
- pending.slice!(0,pending.length)
44
+ state_check.slice!(0, state_check.length) + pending.slice!(0,pending.length)
45
45
  ensure
46
46
  @mutex.unlock rescue nil
47
47
  end
@@ -82,9 +82,6 @@ module Zookeeper
82
82
  @mutex = Mutex.new
83
83
  @cond = ConditionVariable.new
84
84
  @rval = nil
85
-
86
- # make this error reporting more robust if necessary, right now, just set to state
87
- @error = nil
88
85
 
89
86
  # set to true when an event occurs that would cause the caller to
90
87
  # otherwise block forever
@@ -93,17 +90,9 @@ module Zookeeper
93
90
 
94
91
  # the caller calls this method and receives the response from the async loop
95
92
  def value
96
- @mutex.synchronize do
97
- @cond.wait(@mutex) until @rval or @error
98
-
99
- case @error
100
- when nil
101
- # ok, nothing to see here, carry on
102
- when ZOO_EXPIRED_SESSION_STATE
103
- raise Exceptions::SessionExpired, "connection has expired"
104
- else
105
- raise Exceptions::NotConnected, "connection state is #{STATE_NAMES[@error]}"
106
- end
93
+ @mutex.lock
94
+ begin
95
+ @cond.wait(@mutex) until @rval
107
96
 
108
97
  case @rval.length
109
98
  when 1
@@ -111,6 +100,8 @@ module Zookeeper
111
100
  else
112
101
  return @rval
113
102
  end
103
+ ensure
104
+ @mutex.unlock rescue nil
114
105
  end
115
106
  end
116
107
 
@@ -133,24 +124,13 @@ module Zookeeper
133
124
  # implementation, but it's more important to get *something* working and
134
125
  # passing specs, then refactor to make everything sane
135
126
  #
136
- #
137
127
  def submit(czk)
138
- state = czk.zkrb_state # check the state of the connection
139
-
140
- if @meth == :state # if the method is a state call
141
- @rval = [state] # we're done, no error
142
- return deliver!
143
-
144
- elsif state != ZOO_CONNECTED_STATE # otherwise, we must be connected
145
- @error = state # so set the error
146
- return deliver! # and we're out
147
- end
148
-
149
128
  rc, *_ = czk.__send__(:"zkrb_#{@meth}", *async_args)
150
129
 
151
- if user_callback? or (rc != ZOK) # async call, or we failed to submit it
152
- @rval = [rc] # create the repsonse
153
- deliver! # wake the caller and we're out
130
+ # if this is an state call, async call, or we failed to submit it
131
+ if (@meth == :state) or user_callback? or (rc != ZOK)
132
+ @rval = [rc] # create the repsonse
133
+ deliver! # wake the caller and we're out
154
134
  end
155
135
  end
156
136
 
@@ -172,6 +152,9 @@ module Zookeeper
172
152
 
173
153
  logger.debug { "async_args, meth: #{meth} ary: #{ary.inspect}, #{callback_arg_idx}" }
174
154
 
155
+ # this is not already an async call
156
+ # so we replace the req_id with the ZKRB_ASYNC_CONTN_ID so the
157
+ # event thread knows to dispatch it itself
175
158
  ary[callback_arg_idx] ||= self
176
159
 
177
160
  ary
@@ -182,8 +165,11 @@ module Zookeeper
182
165
  end
183
166
 
184
167
  def deliver!
185
- @mutex.synchronize do
168
+ @mutex.lock
169
+ begin
186
170
  @cond.signal
171
+ ensure
172
+ @mutex.unlock rescue nil
187
173
  end
188
174
  end
189
175
  end # Base
@@ -1,4 +1,4 @@
1
1
  module Zookeeper
2
- VERSION = '1.1.3'
2
+ VERSION = '1.2.0'
3
3
  DRIVER_VERSION = '3.3.5'
4
4
  end
@@ -14,8 +14,15 @@ unless defined?(::JRUBY_VERSION)
14
14
  false
15
15
  end
16
16
 
17
- before do
17
+ LBORDER = ('-' * 35) << '< '
18
+ RBORDER = ' >' << ('-' * 35)
19
+
20
+ def mark(thing)
21
+ logger << "\n#{LBORDER}#{thing}#{RBORDER}\n\n"
22
+ end
18
23
 
24
+ before do
25
+ mark "BEFORE: START"
19
26
  if defined?(::Rubinius)
20
27
  pending("this test is currently broken in rbx")
21
28
  # elsif ENV['TRAVIS']
@@ -24,11 +31,12 @@ unless defined?(::JRUBY_VERSION)
24
31
  @zk = Zookeeper.new(connection_string)
25
32
  rm_rf(@zk, path)
26
33
  end
27
- logger.debug { "----------------< BEFORE: END >-------------------" }
34
+ mark "BEFORE: END"
28
35
  end
29
36
 
30
37
  after do
31
- logger.debug { "----------------< AFTER: BEGIN >-------------------" }
38
+ mark "AFTER: START"
39
+
32
40
  if @pid and process_alive?(@pid)
33
41
  begin
34
42
  Process.kill('KILL', @pid)
@@ -39,6 +47,8 @@ unless defined?(::JRUBY_VERSION)
39
47
 
40
48
  @zk.close if @zk and !@zk.closed?
41
49
  with_open_zk(connection_string) { |z| rm_rf(z, path) }
50
+
51
+ mark "AFTER: END"
42
52
  end
43
53
 
44
54
  def wait_for_child_safely(pid, timeout=5)
@@ -56,7 +66,8 @@ unless defined?(::JRUBY_VERSION)
56
66
  end
57
67
 
58
68
  it %[should do the right thing and not fail] do
59
- logger.debug { "----------------< TEST: BEGIN >-------------------" }
69
+ mark "TEST: START"
70
+
60
71
  @zk.wait_until_connected
61
72
 
62
73
  mkdir_p(@zk, pids_root)
@@ -75,9 +86,9 @@ unless defined?(::JRUBY_VERSION)
75
86
 
76
87
  @zk.stat(:path => "#{pids_root}/child", :watcher => cb)
77
88
 
78
- logger.debug { "-------------------> FORK <---------------------------" }
89
+ @zk.pause_before_fork_in_parent
79
90
 
80
- @zk.pause
91
+ mark "FORK"
81
92
 
82
93
  @pid = fork do
83
94
  logger.debug { "reopening connection in child: #{$$}" }
@@ -91,7 +102,7 @@ unless defined?(::JRUBY_VERSION)
91
102
  exit!(0)
92
103
  end
93
104
 
94
- @zk.resume
105
+ @zk.resume_after_fork_in_parent
95
106
 
96
107
  event_waiter_th = Thread.new do
97
108
  @latch.await(5) unless @event
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zookeeper
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
- - 1
9
- - 3
10
- version: 1.1.3
8
+ - 2
9
+ - 0
10
+ version: 1.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Phillip Pearson
@@ -20,7 +20,7 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2012-05-21 00:00:00 Z
23
+ date: 2012-05-18 00:00:00 Z
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: backports