zookeeper 1.1.3 → 1.2.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/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