zk 0.9.0 → 0.9.1

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/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