zk 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/z_k.rb CHANGED
@@ -27,10 +27,19 @@ module ZK
27
27
 
28
28
  KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
29
29
 
30
- # The logger used by the ZK library. uses a Logger to +/dev/null+ by default
30
+ unless @logger
31
+ @logger = Logger.new($stderr).tap { |n| n.level = Logger::ERROR }
32
+ end
33
+
34
+ # The logger used by the ZK library. uses a Logger stderr with Logger::ERROR
35
+ # level. The only thing that should ever be logged are exceptions that are
36
+ # swallowed by background threads.
37
+ #
38
+ # You can change this logger by setting ZK#logger= to an object that
39
+ # implements the stdllb Logger API.
31
40
  #
32
41
  def self.logger
33
- @logger ||= Logger.new('/dev/null')
42
+ @logger
34
43
  end
35
44
 
36
45
  # Assign the Logger instance to be used by ZK
@@ -97,6 +97,15 @@ module ZK
97
97
  end
98
98
 
99
99
  # close the underlying connection and clear all pending events.
100
+ #
101
+ # @note it is VERY IMPORTANT that when using the threaded client that you
102
+ # __NOT CALL THIS ON THE EVENT DISPATCH THREAD__. If you do call it when
103
+ # `event_dispatch_thread?` is true, you will get a big fat Kernel.warn
104
+ # and nothing will happen. There is currently no safe way to have the event
105
+ # thread cause a shutdown in the main thread (with good reason). There is also
106
+ # no way to get an exception from the event thread (they're currently
107
+ # just logged and swallowed), so this is the least horrible remedy available.
108
+ #
100
109
  def close!
101
110
  event_handler.clear!
102
111
  wrap_state_closed_error { cnx.close unless cnx.closed? }
@@ -317,9 +326,9 @@ module ZK
317
326
  def get(path, opts={})
318
327
  h = { :path => path }.merge(opts)
319
328
 
320
- setup_watcher!(:data, h)
321
-
322
- rv = check_rc(cnx.get(h), h)
329
+ rv = setup_watcher!(:data, h) do
330
+ check_rc(cnx.get(h), h)
331
+ end
323
332
 
324
333
  opts[:callback] ? rv : rv.values_at(:data, :stat)
325
334
  end
@@ -429,33 +438,19 @@ module ZK
429
438
  #
430
439
  #
431
440
  def stat(path, opts={})
432
- # ===== exist node asynchronously
433
- #
434
- # class StatCallback
435
- # def process_result(return_code, path, context, stat)
436
- # # do processing here
437
- # end
438
- # end
439
- #
440
- # callback = StatCallback.new
441
- # context = Object.new
442
- #
443
- # zk.exists?("/path", :callback => callback, :context => context)
444
-
445
-
446
441
  h = { :path => path }.merge(opts)
447
442
 
448
- setup_watcher!(:data, h)
443
+ setup_watcher!(:data, h) do
444
+ rv = cnx.stat(h)
449
445
 
450
- rv = cnx.stat(h)
446
+ return rv if opts[:callback]
451
447
 
452
- return rv if opts[:callback]
453
-
454
- case rv[:rc]
455
- when Zookeeper::ZOK, Zookeeper::ZNONODE
456
- rv[:stat]
457
- else
458
- check_rc(rv, h) # throws the appropriate error
448
+ case rv[:rc]
449
+ when Zookeeper::ZOK, Zookeeper::ZNONODE
450
+ rv[:stat]
451
+ else
452
+ check_rc(rv, h) # throws the appropriate error
453
+ end
459
454
  end
460
455
  end
461
456
 
@@ -539,9 +534,10 @@ module ZK
539
534
 
540
535
  h = { :path => path }.merge(opts)
541
536
 
542
- setup_watcher!(:child, h)
537
+ rv = setup_watcher!(:child, h) do
538
+ check_rc(cnx.get_children(h), h)
539
+ end
543
540
 
544
- rv = check_rc(cnx.get_children(h), h)
545
541
  opts[:callback] ? rv : rv[:children]
546
542
  end
547
543
 
@@ -771,8 +767,9 @@ module ZK
771
767
  end
772
768
 
773
769
  # @private
774
- def assert_we_are_not_on_the_event_dispatch_thread!
775
- raise Exceptions::EventDispatchThreadException, "blocking method called on dispatch thread" if event_dispatch_thread?
770
+ def assert_we_are_not_on_the_event_dispatch_thread!(msg=nil)
771
+ msg ||= "blocking method called on dispatch thread"
772
+ raise Exceptions::EventDispatchThreadException, msg if event_dispatch_thread?
776
773
  end
777
774
 
778
775
  protected
@@ -788,8 +785,8 @@ module ZK
788
785
  end
789
786
 
790
787
  # @private
791
- def setup_watcher!(watch_type, opts)
792
- event_handler.setup_watcher!(watch_type, opts)
788
+ def setup_watcher!(watch_type, opts, &b)
789
+ event_handler.setup_watcher!(watch_type, opts, &b)
793
790
  end
794
791
 
795
792
  # used in #inspect, doesn't raise an error if we're not connected
@@ -21,7 +21,7 @@ module ZK
21
21
  end
22
22
  end
23
23
 
24
- call_with_continuation :create, :get, :set, :stat, :children, :delete, :get_acl, :set_acl
24
+ call_with_continuation :get, :set, :stat, :children, :delete, :get_acl, :set_acl
25
25
 
26
26
  def initialize(zookeeper_cnx=nil)
27
27
  @zookeeper_cnx = zookeeper_cnx
@@ -29,6 +29,14 @@ module ZK
29
29
  @dropboxen = []
30
30
  end
31
31
 
32
+ # create is "special" because the return hash doesn't have a :path, it has a :string
33
+ def create(opts)
34
+ logger.debug { "_call_continue(create, #{opts.inspect})" }
35
+ _call_continue(:create, opts).tap do |rval|
36
+ rval[:path] = rval.delete(:string)
37
+ end
38
+ end
39
+
32
40
  # called by the multiplxed client to wake up threads that are waiting for
33
41
  # results (with an exception)
34
42
  # @private
@@ -66,7 +74,7 @@ module ZK
66
74
 
67
75
  _with_drop_box do |db|
68
76
  cb = lambda do |hash|
69
- logger.debug { "#{self.class}##{__method__} block pushing: #{hash.inspect}" }
77
+ # logger.debug { "#{self.class}##{__method__} block pushing: #{hash.inspect}" }
70
78
  db.push(hash)
71
79
  end
72
80
 
@@ -74,9 +82,11 @@ module ZK
74
82
 
75
83
  @zookeeper_cnx.__send__(meth, opts)
76
84
 
77
- db.pop.tap do |obj|
78
- logger.debug { "#{self.class}##{__method__} popped and returning: #{obj.inspect}" }
79
- end
85
+ # db.pop.tap do |obj|
86
+ # logger.debug { "#{self.class}##{__method__} popped and returning: #{obj.inspect}" }
87
+ # end
88
+
89
+ db.pop
80
90
  end
81
91
  end
82
92
 
@@ -50,6 +50,16 @@ module ZK
50
50
 
51
51
  # @see ZK::Client::Base#close!
52
52
  def close!
53
+ if event_dispatch_thread?
54
+ msg = ["ZK ERROR: You called #{self.class}#close! on the event dispatch thread!!",
55
+ "This will cause the client to deadlock and possibly your main thread as well!"]
56
+
57
+ warn_msg = [nil, msg, nil, "See ZK error log output (stderr by default) for a backtrace", nil].join("\n")
58
+
59
+ Kernel.warn(warn_msg)
60
+ assert_we_are_not_on_the_event_dispatch_thread!(msg.join(' '))
61
+ end
62
+
53
63
  @threadpool.shutdown
54
64
  super
55
65
  nil
@@ -153,26 +153,52 @@ module ZK
153
153
  end
154
154
  end
155
155
 
156
+ # returns true if there's a pending watch of type for path
157
+ # @private
158
+ def restricting_new_watches_for?(watch_type, path)
159
+ synchronize do
160
+ if set = @outstanding_watches[watch_type]
161
+ return set.include?(path)
162
+ end
163
+ end
164
+
165
+ false
166
+ end
167
+
156
168
  # implements not only setting up the watcher callback, but deduplicating
157
169
  # event delivery. Keeps track of in-flight watcher-type+path requests and
158
170
  # doesn't re-register the watcher with the server until a response has been
159
171
  # fired. This prevents one event delivery to *every* callback per :watch => true
160
172
  # argument.
161
173
  #
174
+ # due to somewhat poor design, we destructively modify opts before we yield
175
+ # and the client implictly knows this
176
+ #
162
177
  # @private
163
178
  def setup_watcher!(watch_type, opts)
164
- return unless opts.delete(:watch)
179
+ return yield unless opts.delete(:watch)
165
180
 
166
181
  synchronize do
167
182
  set = @outstanding_watches.fetch(watch_type)
168
183
  path = opts[:path]
169
184
 
170
185
  if set.add?(path)
171
- # this path has no outstanding watchers, let it do its thing
172
- opts[:watcher] = watcher_callback
186
+ # if we added the path to the set, blocking further registration of
187
+ # watches and an exception is raised then we rollback
188
+ begin
189
+ # this path has no outstanding watchers, let it do its thing
190
+ opts[:watcher] = watcher_callback
191
+
192
+ yield opts
193
+ rescue Exception
194
+ set.delete(path)
195
+ raise
196
+ end
173
197
  else
174
- # outstanding watch for path and data pair already exists, so ignore
175
- # logger.debug { "outstanding watch request for path #{path.inspect} and watcher type #{watch_type.inspect}, not re-registering" }
198
+ # we did not add the path to the set, which means we are not
199
+ # responsible for removing a block on further adds if the operation
200
+ # fails, therefore, we just yield
201
+ yield opts
176
202
  end
177
203
  end
178
204
  end
@@ -1,3 +1,3 @@
1
1
  module ZK
2
- VERSION = "0.9.0"
2
+ VERSION = "0.9.1"
3
3
  end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'zk'
4
+
5
+ LOG = Logger.new($stderr).tap { |n| n.level = Logger::DEBUG }
6
+
7
+ ZK.logger = LOG
8
+ Zookeeper.logger = LOG
9
+
10
+ class CloseInEventThread
11
+ include ZookeeperConstants
12
+
13
+ def initialize
14
+ @zk = ZK.new
15
+ @q = Queue.new
16
+ end
17
+
18
+ def run
19
+ @zk.on_connecting do |event|
20
+ if @ok_do_it
21
+ logger.debug { "ok, calling close, in event thread? #{@zk.event_dispatch_thread?}" }
22
+ @zk.close!
23
+ logger.debug { "close! returned, continuing" }
24
+ @q.push(:OK)
25
+ else
26
+ logger.debug { "on_connecting, got event #{event}" }
27
+ end
28
+ end
29
+
30
+ @ok_do_it = true
31
+ logger.debug { "push bogus ZOO_CONNECTING_STATE event into queue" }
32
+ @zk.__send__(:cnx).event_queue.push(:req_id => -1, :type => -1, :state => ZOO_CONNECTING_STATE, :path => '')
33
+
34
+ rval = @q.pop
35
+
36
+ logger.debug { "got #{rval.inspect}" }
37
+
38
+ @zk.close!
39
+ end
40
+
41
+ def logger
42
+ LOG
43
+ end
44
+ end
45
+
46
+ CloseInEventThread.new.run
47
+
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'zk'
4
+
5
+ def new_stderr_logger
6
+ Logger.new($stderr).tap { |l| l.level = Logger::DEBUG }
7
+ end
8
+
9
+ ZK.logger = new_stderr_logger
10
+
11
+ class WhatTheFork
12
+ attr_reader :logger
13
+
14
+ def initialize
15
+ @zk = ZK.new
16
+ @base_path = '/what-the-fork'
17
+ @path_to_delete = "#{@base_path}/delete_me"
18
+ end
19
+
20
+ def setup_logs!
21
+ Zookeeper.logger = ZK.logger = @logger = new_stderr_logger
22
+ end
23
+
24
+ def run
25
+ setup_logs!
26
+
27
+ @zk.mkdir_p(@path_to_delete)
28
+
29
+ @zk.on_connected do |event|
30
+ _debug "on_connected: #{event.inspect}"
31
+ end
32
+
33
+ @zk.on_connecting do |event|
34
+ _debug "on_connecting: #{event.inspect}"
35
+ end
36
+
37
+ @zk.on_expired_session do |event|
38
+ _debug "on_expired_session: #{event.inspect}"
39
+ end
40
+
41
+ fork_it!
42
+
43
+ @zk.block_until_node_deleted(@path_to_delete)
44
+ _debug "exiting main process!"
45
+ end
46
+
47
+ def fork_it!
48
+ pid = fork do
49
+ setup_logs!
50
+
51
+ _debug "closing zk"
52
+ @zk.close!
53
+ _debug "closed zk"
54
+ @zk = ZK.new
55
+ _debug "created new zk"
56
+
57
+ @zk.delete(@path_to_delete)
58
+
59
+ _debug "deleted path #{@path_to_delete}, closing new zk instance"
60
+
61
+ @zk.close!
62
+
63
+ _debug "EXITING!!"
64
+ exit 0
65
+ end
66
+
67
+ _, stat = Process.waitpid2(pid)
68
+ _debug "child exited, stat: #{stat.inspect}"
69
+ ensure
70
+ if pid
71
+ _debug "ensuring #{pid} is really dead"
72
+ Process.kill(9, pid) rescue Errno::ESRCH
73
+ end
74
+ end
75
+
76
+ def _debug(str)
77
+ logger.debug { str }
78
+ end
79
+ end
80
+
81
+ WhatTheFork.new.run if __FILE__ == $0
82
+
@@ -1,12 +1,29 @@
1
1
  shared_context 'threaded client connection' do
2
2
  before do
3
3
  @connection_string = "localhost:#{ZK_TEST_PORT}"
4
+ @base_path = '/zktests'
4
5
  @zk = ZK::Client::Threaded.new(@connection_string).tap { |z| wait_until { z.connected? } }
5
- @zk.rm_rf('/test')
6
+ @zk.rm_rf(@base_path)
6
7
  end
7
8
 
8
9
  after do
9
- @zk.rm_rf('/test')
10
+ @zk.rm_rf(@base_path)
11
+ @zk.close!
12
+
13
+ wait_until(2) { @zk.closed? }
14
+ end
15
+ end
16
+
17
+ shared_context 'multiplexed client connection' do
18
+ before do
19
+ @connection_string = "localhost:#{ZK_TEST_PORT}"
20
+ @base_path = '/zktests'
21
+ @zk = ZK::Client::Multiplexed.new(@connection_string).tap { |z| wait_until { z.connected? } }
22
+ @zk.rm_rf(@base_path)
23
+ end
24
+
25
+ after do
26
+ @zk.rm_rf(@base_path)
10
27
  @zk.close!
11
28
 
12
29
  wait_until(2) { @zk.closed? }
@@ -6,6 +6,7 @@ shared_examples_for 'client' do
6
6
  end
7
7
 
8
8
  it %[should create all intermediate paths for the path givem] do
9
+ @zk.rm_rf('/test')
9
10
  @zk.should_not be_exists(@bogus_path)
10
11
  @zk.should_not be_exists(File.dirname(@bogus_path))
11
12
  @zk.mkdir_p(@bogus_path)
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), %w[spec_helper])
1
+ require 'spec_helper'
2
2
 
3
3
  describe ZK do
4
4
  describe do
@@ -8,15 +8,18 @@ describe ZK do
8
8
 
9
9
  @path = "/_testWatch"
10
10
  wait_until { @zk.connected? }
11
+
12
+ # make sure we start w/ clean state
13
+ @zk.rm_rf(@path)
11
14
  end
12
15
 
13
16
  after do
14
- if @zk.connected?
15
- @zk.close!
16
- wait_until { !@zk.connected? }
17
- end
18
-
19
17
  mute_logger do
18
+ if @zk.connected?
19
+ @zk.close!
20
+ wait_until { !@zk.connected? }
21
+ end
22
+
20
23
  ZK.open(@cnx_str) { |zk| zk.rm_rf(@path) }
21
24
  end
22
25
  end
@@ -25,7 +28,7 @@ describe ZK do
25
28
  locker = Mutex.new
26
29
  callback_called = false
27
30
 
28
- @zk.watcher.register(@path) do |event|
31
+ @zk.register(@path) do |event|
29
32
  locker.synchronize do
30
33
  callback_called = true
31
34
  end
@@ -52,7 +55,7 @@ describe ZK do
52
55
  it %[should only deliver an event once to each watcher registered for exists?] do
53
56
  events = []
54
57
 
55
- sub = @zk.watcher.register(@path) do |ev|
58
+ sub = @zk.register(@path) do |ev|
56
59
  logger.debug "got event #{ev}"
57
60
  events << ev
58
61
  end
@@ -73,7 +76,7 @@ describe ZK do
73
76
 
74
77
  @zk.create(@path, 'one', :mode => :ephemeral)
75
78
 
76
- sub = @zk.watcher.register(@path) do |ev|
79
+ sub = @zk.register(@path) do |ev|
77
80
  logger.debug "got event #{ev}"
78
81
  events << ev
79
82
  end
@@ -96,7 +99,7 @@ describe ZK do
96
99
 
97
100
  @zk.create(@path, '')
98
101
 
99
- sub = @zk.watcher.register(@path) do |ev|
102
+ sub = @zk.register(@path) do |ev|
100
103
  logger.debug "got event #{ev}"
101
104
  events << ev
102
105
  end
@@ -112,6 +115,40 @@ describe ZK do
112
115
 
113
116
  events.length.should == 1
114
117
  end
118
+
119
+ it %[should restrict_new_watches_for? if a successul watch has been set] do
120
+ @zk.stat(@path, :watch => true)
121
+ @zk.event_handler.should be_restricting_new_watches_for(:data, @path)
122
+ end
123
+
124
+ it %[should not a block on new watches after an operation fails] do
125
+ # this is a situation where we did get('/blah', :watch => true) but
126
+ # got an exception, the next watch set should work
127
+
128
+ events = []
129
+
130
+ sub = @zk.register(@path) do |ev|
131
+ logger.debug { "got event #{ev}" }
132
+ events << ev
133
+ end
134
+
135
+ # get a path that doesn't exist with a watch
136
+
137
+ lambda { @zk.get(@path, :watch => true) }.should raise_error(ZK::Exceptions::NoNode)
138
+
139
+ @zk.event_handler.should_not be_restricting_new_watches_for(:data, @path)
140
+
141
+ @zk.stat(@path, :watch => true)
142
+
143
+ @zk.event_handler.should be_restricting_new_watches_for(:data, @path)
144
+
145
+ @zk.create(@path, '')
146
+
147
+ wait_while { events.empty? }
148
+
149
+ events.should_not be_empty
150
+
151
+ end
115
152
  end
116
153
 
117
154
  describe 'state watcher' do
@@ -141,10 +178,6 @@ describe ZK do
141
178
  m.should_receive(:state).and_return(ZookeeperConstants::ZOO_CONNECTED_STATE)
142
179
  end
143
180
  end
144
-
145
- it %[should only fire the callback once] do
146
- pending "not sure if this is the behavior we want"
147
- end
148
181
  end
149
182
  end
150
183
  end
@@ -295,6 +295,7 @@ describe ZK::Election do
295
295
  end
296
296
 
297
297
  it %[should have seen both the death and life events] do
298
+ pending 'this test is flapping'
298
299
  @got_life_event.should be_true
299
300
  @got_death_event.should be_true
300
301
  end
@@ -494,7 +494,7 @@ describe 'ZK::Locker Multiplexed client', :client => :multiplexed do
494
494
 
495
495
  before do
496
496
  wait_until{ connections.all?(&:connected?) }
497
- pending "Mutliplexed client locking is broken"
497
+ # pending "Mutliplexed client locking is broken"
498
498
  end
499
499
 
500
500
  after do
@@ -507,5 +507,3 @@ describe 'ZK::Locker Multiplexed client', :client => :multiplexed do
507
507
  it_should_behave_like 'shared-exclusive interaction'
508
508
  end # ZK::Locker
509
509
 
510
-
511
-
@@ -1,17 +1,8 @@
1
- require File.join(File.dirname(__FILE__), %w[spec_helper])
1
+ require 'spec_helper'
2
2
 
3
- describe ZK do
3
+ shared_examples_for 'ZK basic' do
4
4
  before do
5
- @zk = ZK.new("localhost:#{ZK_TEST_PORT}")
6
-
7
- @base_path = "/zktests"
8
- @zk.rm_rf(@base_path)
9
- @zk.mkdir_p(@base_path)
10
- end
11
-
12
- after do
13
- @zk.rm_rf(@base_path)
14
- @zk.close!
5
+ @zk.create(@base_path) rescue ZK::Exceptions::NodeExists
15
6
  end
16
7
 
17
8
  describe ZK, "with no paths" do
@@ -125,3 +116,15 @@ describe ZK do
125
116
  end
126
117
  end
127
118
  end
119
+
120
+ describe 'basic multiplexed', :client => :multiplexed do
121
+ include_context 'multiplexed client connection'
122
+ it_should_behave_like 'ZK basic'
123
+ end
124
+
125
+ describe 'basic threaded', :client => :threaded do
126
+ include_context 'threaded client connection'
127
+ it_should_behave_like 'ZK basic'
128
+ end
129
+
130
+
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zk
3
3
  version: !ruby/object:Gem::Version
4
- hash: 59
4
+ hash: 57
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 0
10
- version: 0.9.0
9
+ - 1
10
+ version: 0.9.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jonathan D. Simms
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2012-04-23 00:00:00 Z
19
+ date: 2012-04-26 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: slyphon-zookeeper
@@ -81,7 +81,9 @@ files:
81
81
  - lib/z_k/threadpool.rb
82
82
  - lib/z_k/version.rb
83
83
  - lib/zk.rb
84
+ - spec/informal/close-in-event-thread.rb
84
85
  - spec/informal/lock_with_dead_session.rb
86
+ - spec/informal/what-the-fork.rb
85
87
  - spec/log4j.properties
86
88
  - spec/message_queue_spec.rb
87
89
  - spec/shared/client_contexts.rb
@@ -141,7 +143,9 @@ signing_key:
141
143
  specification_version: 3
142
144
  summary: A high-level wrapper around the zookeeper driver
143
145
  test_files:
146
+ - spec/informal/close-in-event-thread.rb
144
147
  - spec/informal/lock_with_dead_session.rb
148
+ - spec/informal/what-the-fork.rb
145
149
  - spec/log4j.properties
146
150
  - spec/message_queue_spec.rb
147
151
  - spec/shared/client_contexts.rb