zk 0.8.8 → 0.8.9

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.
@@ -0,0 +1,17 @@
1
+ ---
2
+ notifications:
3
+ email:
4
+ - slyphon@gmail.com
5
+
6
+ rvm:
7
+ - 1.8.7
8
+ - 1.9.2
9
+ - 1.9.3
10
+ - jruby
11
+
12
+ before_install:
13
+ - sudo apt-get update
14
+ - sudo apt-get install zookeeperd
15
+
16
+ bundler_args: --without development
17
+
data/Gemfile CHANGED
@@ -2,6 +2,17 @@
2
2
  source ENV['MBOX_BUNDLER_SOURCE'] if ENV['MBOX_BUNDLER_SOURCE']
3
3
  source "http://rubygems.org"
4
4
 
5
+ group :development do
6
+ gem 'pry'
7
+ end
8
+
9
+ group :test do
10
+ gem 'rspec', '~> 2.8.0'
11
+ gem 'flexmock', '~> 0.8.10'
12
+ gem 'ZenTest', '~> 4.5.0'
13
+ gem 'rake'
14
+ end
15
+
5
16
  # Specify your gem's dependencies in zk.gemspec
6
17
  gemspec
7
18
 
@@ -1,8 +1,6 @@
1
1
  # ZK
2
2
 
3
- ZK is a high-level interface to the Apache [ZooKeeper][] server. It is based on the [zookeeper gem][] which is a multi-Ruby low-level driver. Currently MRI 1.8.7, 1.9.2, and JRuby are supported (rubinius 1.2 is experimental but _should_ work). It is licensed under the [MIT][] license.
4
-
5
- Note: 1.9.3-p0 support is currently under development, there are a few bugs to work out still...
3
+ ZK is a high-level interface to the Apache [ZooKeeper][] server. It is based on the [zookeeper gem][] which is a multi-Ruby low-level driver. Currently MRI 1.8.7, 1.9.2, 1.9.3, and JRuby are supported (rubinius 1.2 is experimental but _should_ work). It is licensed under the [MIT][] license.
6
4
 
7
5
  This library is heavily used in a production deployment and is actively developed and maintained.
8
6
 
@@ -35,13 +33,13 @@ The [zookeeper gem][] provides a low-level, cross platform library for interfaci
35
33
  ZK provides:
36
34
 
37
35
  * a robust lock implementation (both shared and exclusive locks)
38
- * an extension for the [Mongoid][] ORM to provide advisory locks on mongodb records
39
36
  * a leader election implementation with both "leader" and "observer" roles
40
37
  * a higher-level interface to the ZooKeeper callback/watcher mechanism than the [zookeeper gem][] provides
41
38
  * a simple threadpool implementation
42
39
  * a bounded, dynamically-growable (threadsafe) client pool implementation
43
40
  * a recursive Find class (like the Find module in ruby-core)
44
41
  * unix-like rm\_rf and mkdir\_p methods (useful for functional testing)
42
+ * an extension for the [Mongoid][] ORM to provide advisory locks on mongodb records
45
43
 
46
44
  In addition to all of that, I would like to think that the public API the ZK::Client provides is more convenient to use for the common (synchronous) case. For use with [EventMachine][] there is [zk-eventmachine][] which provides a convenient API for writing evented code that uses the ZooKeeper server.
47
45
 
@@ -58,7 +56,7 @@ ZK strives to be a complete, correct, and convenient way of interacting with Zoo
58
56
 
59
57
  * ZK::Client supports asynchronous calls of all basic methods (get, set, delete, etc.) however these versions are kind of inconvenient to use. For a fully evented stack, try [zk-eventmachine][], which is designed to be compatible and convenient to use in event-driven code.
60
58
 
61
- * ZooKeeper "chroot" [connection syntax][chroot] _(search for "chroot" in page)_ is not currently working in the C drivers, and I don't have tests for the Java version. This hasn't been an incredibly high priority item, but support for this feature is intended.
59
+ * ZooKeeper "chroot" [connection syntax][chroot] is currently being developed and should work for most cases. Right now we require that the root path exist before the chrooted client is used, but that may change [in the near future](https://github.com/slyphon/zk/issues/7).
62
60
 
63
61
  * I am currently in the process of cleaning up the API documentation and converting it to use [YARD][].
64
62
 
data/Rakefile CHANGED
@@ -31,3 +31,19 @@ gemset_name = 'zk'
31
31
  task "mb:test_all" => rspec_task_name
32
32
  end
33
33
 
34
+ namespace :spec do
35
+ task :define do
36
+ require 'rubygems'
37
+ require 'bundler/setup'
38
+ require 'rspec/core/rake_task'
39
+
40
+ RSpec::Core::RakeTask.new('spec:runner')
41
+ end
42
+
43
+ task :run => :define do
44
+ Rake::Task['spec:runner'].invoke
45
+ end
46
+ end
47
+
48
+ task :default => 'spec:run'
49
+
@@ -48,8 +48,6 @@ module ZK
48
48
  end
49
49
  end
50
50
 
51
- # @private
52
- #
53
51
  # Register a block to be called on connection, when the client has
54
52
  # connected.
55
53
  #
@@ -1,6 +1,8 @@
1
1
  module ZK
2
2
  module Client
3
3
  module Unixisms
4
+ include ZookeeperConstants
5
+
4
6
  # Creates all parent paths and 'path' in zookeeper as persistent nodes with
5
7
  # zero data.
6
8
  #
@@ -62,7 +64,7 @@ module ZK
62
64
  # watcher, you *will* deadlock!
63
65
  def block_until_node_deleted(abs_node_path)
64
66
  queue = Queue.new
65
- ev_sub = nil
67
+ subs = []
66
68
 
67
69
  node_deletion_cb = lambda do |event|
68
70
  if event.node_deleted?
@@ -72,16 +74,35 @@ module ZK
72
74
  end
73
75
  end
74
76
 
75
- ev_sub = watcher.register(abs_node_path, &node_deletion_cb)
77
+ subs << event_handler.register(abs_node_path, &node_deletion_cb)
78
+
79
+ # NOTE: this pattern may be necessary for other features with blocking semantics!
80
+
81
+ session_cb = lambda do |event|
82
+ queue.enq(event.state)
83
+ end
76
84
 
85
+ [:expired_session, :connecting, :closed].each do |sym|
86
+ subs << event_handler.register_state_handler(sym, &session_cb)
87
+ end
88
+
77
89
  # set up the callback, but bail if we don't need to wait
78
90
  return true unless exists?(abs_node_path, :watch => true)
79
91
 
80
- queue.pop # block waiting for node deletion
81
- true
92
+ case queue.pop
93
+ when :deleted
94
+ true
95
+ when ZOO_EXPIRED_SESSION_STATE
96
+ raise ZookeeperExceptions::ZookeeperException::SessionExpired
97
+ when ZOO_CONNECTING_STATE
98
+ raise ZookeeperExceptions::ZookeeperException::NotConnected
99
+ when ZOO_CLOSED_STATE
100
+ raise ZookeeperExceptions::ZookeeperException::ConnectionClosed
101
+ else
102
+ raise "Hit unexpected case in block_until_node_deleted"
103
+ end
82
104
  ensure
83
- # be sure we clean up after ourselves
84
- ev_sub.unregister if ev_sub
105
+ subs.each(&:unregister)
85
106
  end
86
107
  end
87
108
  end
@@ -85,9 +85,16 @@ module ZK
85
85
  end
86
86
  alias :subscribe :register
87
87
 
88
- # registers a "state of the connection" handler
88
+ # Registers a "state of the connection" handler
89
89
  #
90
- # @param [String] state the state you want to register for
90
+ # Valid states are: connecting, associating, connected, auth_failed,
91
+ # expired_session. Of all of these, you are probably most likely
92
+ # interested in `expired_session` and `connecting`, which are fired
93
+ # when you either lose your session (and have to completely reconnect),
94
+ # or when there's a temporary loss in connection and Zookeeper recommends
95
+ # you go into 'safe mode'.
96
+ #
97
+ # @param [String] state The state you want to register for.
91
98
  # @param [Block] block the block to execute on state changes
92
99
  # @yield [event] yields your block with
93
100
  #
@@ -71,6 +71,7 @@ module ZK
71
71
  def state_event?
72
72
  path.nil? or path.empty?
73
73
  end
74
+ alias session_event? state_event?
74
75
 
75
76
  # has this watcher been called because of a change to a zookeeper node?
76
77
  def node_event?
@@ -1,3 +1,3 @@
1
1
  module ZK
2
- VERSION = "0.8.8"
2
+ VERSION = "0.8.9"
3
3
  end
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'zk'
4
+ require 'logger'
5
+
6
+ $stderr.sync = true
7
+
8
+ ZK.logger = log = Logger.new('informal.log').tap { |l| l.level = Logger::DEBUG }
9
+ Zookeeper.logger = log
10
+ Zookeeper.set_debug_level(4)
11
+
12
+ class ::Exception
13
+ def to_std_format
14
+ "#{self.class}: #{message}\n" + backtrace {|n| "\t#{n}"}.join("\n")
15
+ end
16
+ end
17
+
18
+ def safe_join(th, timeout=nil)
19
+ begin
20
+ th.join(timeout)
21
+ rescue Exception => e
22
+ $stderr.puts "#{th[:name]} raised #{e.to_std_format}"
23
+ end
24
+ end
25
+
26
+ def print_error
27
+ yield
28
+ rescue Exception => e
29
+ $stderr.puts "caught exception in #{Thread.current[:name]}: #{e.to_std_format}"
30
+ end
31
+
32
+ lock_name = 'the_big_sleep'
33
+
34
+ q = Queue.new
35
+
36
+
37
+ th1 = Thread.new do
38
+ print_error do
39
+ ZK.open do |zk|
40
+ $stderr.puts "first connection session_id: 0x%x" % zk.session_id
41
+ sub = zk.on_expired_session do |state|
42
+ $stderr.puts "OH NOES! thread 1 got an expired session! #{state.inspect}"
43
+ end
44
+
45
+ zk.with_lock(lock_name) do
46
+ q.push(:ok_sleeping)
47
+ sleep # we now sleep with the fishes
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ th1[:name] = 'thread 1'
54
+
55
+ q.pop
56
+
57
+ Thread.pass until th1.status == 'sleep'
58
+
59
+ $stderr.puts "ok, now try to acquire lock"
60
+
61
+ th2 = Thread.new do
62
+ print_error do
63
+ ZK.open do |zk|
64
+ $stderr.puts "second connection session_id: 0x%x" % zk.session_id
65
+
66
+ sub = zk.on_expired_session do |state|
67
+ $stderr.puts "OH NOES! thread 2 got an expired session! #{state.inspect}"
68
+ end
69
+
70
+ zk.with_lock(lock_name) do
71
+ $stderr.puts "acquired the lock in second thread"
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ th2[:name] = 'thread 2'
78
+
79
+ [th1, th2].each(&method(:safe_join))
80
+
@@ -11,7 +11,9 @@ ZK_TEST_PORT = 2181
11
11
  LOG_FILE = File.open(File.join(ZK::ZK_ROOT, 'test.log'), 'a').tap { |f| f.sync = true }
12
12
 
13
13
  ZK.logger = Logger.new(LOG_FILE).tap { |log| log.level = Logger::DEBUG }
14
- #Zookeeper.logger = ZK.logger
14
+ Zookeeper.logger = ZK.logger
15
+
16
+ ZK.logger.debug { "LOG OPEN" }
15
17
 
16
18
  # Requires supporting ruby files with custom matchers and macros, etc,
17
19
  # in spec/support/ and its subdirectories.
@@ -2,7 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe ZK::Client do
4
4
  before do
5
- @zk = ZK.new("localhost:#{ZK_TEST_PORT}")
5
+ @connection_string = "localhost:#{ZK_TEST_PORT}"
6
+ @zk = ZK.new(@connection_string)
6
7
  wait_until{ @zk.connected? }
7
8
  @zk.rm_rf('/test')
8
9
  end
@@ -82,6 +83,7 @@ describe ZK::Client do
82
83
  @a = false
83
84
 
84
85
  th = Thread.new do
86
+
85
87
  @zk.block_until_node_deleted(@path)
86
88
  @a = true
87
89
  end
@@ -94,6 +96,69 @@ describe ZK::Client do
94
96
  wait_until(2) { @a }
95
97
  @a.should be_true
96
98
  end
99
+
100
+ shared_examples_for 'session death' do
101
+ def deliver_session_event_to(event_num, zk)
102
+ # jeez, Zookeeper callbacks are so frustratingly stupid
103
+ bogus_event = ZookeeperCallbacks::WatcherCallback.new
104
+ bogus_event.initialize_context(:type => -1, :state => event_num, :path => '', :context => 'bogustestevent')
105
+ # XXX: this is bad because we're in the wrong thread, but we'll fix this after the next Zookeeper release
106
+ zk.event_handler.process(bogus_event)
107
+ end
108
+
109
+ before do
110
+ @other_zk = ZK.new(@connection_string)
111
+ end
112
+
113
+ after do
114
+ @other_zk.close! unless @other_zk.closed?
115
+ end
116
+
117
+ it %[should wake up in the case of an expired session and throw an exception] do
118
+ @a = false
119
+
120
+ @other_zk.event_handler.register_state_handler(zoo_state) do |event|
121
+ @a = event
122
+ end
123
+
124
+ th = Thread.new do
125
+ @other_zk.block_until_node_deleted(@path)
126
+ end
127
+
128
+ wait_until(2) { th.status == 'sleep' }
129
+
130
+ # not on the other thread, this may be bad
131
+ deliver_session_event_to(zoo_state, @other_zk)
132
+
133
+ # ditto, this is probably happening synchrnously
134
+ wait_until(2) { @a }
135
+
136
+ lambda { th.join(2) }.should raise_error(zoo_error_class)
137
+ end
138
+ end
139
+
140
+ describe 'exceptional conditions' do
141
+ describe 'ZOO_EXPIRED_SESSION_STATE' do
142
+ let(:zoo_state) { ZookeeperConstants::ZOO_EXPIRED_SESSION_STATE }
143
+ let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::SessionExpired }
144
+
145
+ it_behaves_like 'session death'
146
+ end
147
+
148
+ describe 'ZOO_CONNECTING_STATE' do
149
+ let(:zoo_state) { ZookeeperConstants::ZOO_CONNECTING_STATE }
150
+ let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::NotConnected }
151
+
152
+ it_behaves_like 'session death'
153
+ end
154
+
155
+ describe 'ZOO_CLOSED_STATE' do
156
+ let(:zoo_state) { ZookeeperConstants::ZOO_CLOSED_STATE }
157
+ let(:zoo_error_class) { ZookeeperExceptions::ZookeeperException::ConnectionClosed }
158
+
159
+ it_behaves_like 'session death'
160
+ end
161
+ end
97
162
  end
98
163
  end
99
164
 
@@ -106,6 +171,64 @@ describe ZK::Client do
106
171
  @zk.session_passwd.should be_kind_of(String)
107
172
  end
108
173
  end
174
+
175
+ describe 'reopen' do
176
+ describe 'watchers' do
177
+ before do
178
+ @path = '/testwatchers'
179
+ @queue = Queue.new
180
+ end
181
+
182
+ after do
183
+ @zk.delete(@path)
184
+ end
185
+
186
+ def ensure_event_delivery!
187
+ @sub ||= @zk.event_handler.register(@path) do |event|
188
+ logger.debug { "got event: #{event.inspect}" }
189
+ @queue << event
190
+ end
191
+
192
+ @zk.exists?(@path, :watch => true).should be_false
193
+ @zk.create(@path, '')
194
+
195
+ logger.debug { "waiting for event delivery" }
196
+
197
+ wait_until(2) do
198
+ begin
199
+ @events << @queue.pop(true)
200
+ true
201
+ rescue ThreadError
202
+ false
203
+ end
204
+ end
205
+
206
+ # first watch delivered correctly
207
+ @events.length.should > 0
208
+ end
209
+
210
+ it %[should fire re-registered watchers after reopen (#9)] do
211
+ @events = []
212
+
213
+ logger.debug { "ensure event delivery" }
214
+ ensure_event_delivery!
215
+
216
+ logger.debug { "reopening connection" }
217
+ @zk.reopen
218
+
219
+ wait_until(2) { @zk.connected? }
220
+
221
+ logger.debug { "deleting path" }
222
+ @zk.delete(@path)
223
+
224
+ logger.debug { "clearing events" }
225
+ @events.clear
226
+
227
+ logger.debug { "taunt them a second time" }
228
+ ensure_event_delivery!
229
+ end
230
+ end
231
+ end
109
232
  end
110
233
 
111
234
 
data/zk.gemspec CHANGED
@@ -14,10 +14,6 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.add_runtime_dependency 'slyphon-zookeeper', '~> 0.3.0'
16
16
 
17
- s.add_development_dependency 'rspec', '~> 2.8.0'
18
- s.add_development_dependency 'flexmock', '~> 0.8.10'
19
- s.add_development_dependency 'ZenTest', '~> 4.5.0'
20
- s.add_development_dependency 'pry'
21
17
 
22
18
  s.files = `git ls-files`.split("\n")
23
19
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
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: 47
4
+ hash: 45
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 8
9
- - 8
10
- version: 0.8.8
9
+ - 9
10
+ version: 0.8.9
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-13 00:00:00 Z
19
+ date: 2012-04-21 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: slyphon-zookeeper
@@ -34,68 +34,6 @@ dependencies:
34
34
  version: 0.3.0
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: rspec
39
- prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- hash: 47
46
- segments:
47
- - 2
48
- - 8
49
- - 0
50
- version: 2.8.0
51
- type: :development
52
- version_requirements: *id002
53
- - !ruby/object:Gem::Dependency
54
- name: flexmock
55
- prerelease: false
56
- requirement: &id003 !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ~>
60
- - !ruby/object:Gem::Version
61
- hash: 43
62
- segments:
63
- - 0
64
- - 8
65
- - 10
66
- version: 0.8.10
67
- type: :development
68
- version_requirements: *id003
69
- - !ruby/object:Gem::Dependency
70
- name: ZenTest
71
- prerelease: false
72
- requirement: &id004 !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- hash: 43
78
- segments:
79
- - 4
80
- - 5
81
- - 0
82
- version: 4.5.0
83
- type: :development
84
- version_requirements: *id004
85
- - !ruby/object:Gem::Dependency
86
- name: pry
87
- prerelease: false
88
- requirement: &id005 !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ">="
92
- - !ruby/object:Gem::Version
93
- hash: 3
94
- segments:
95
- - 0
96
- version: "0"
97
- type: :development
98
- version_requirements: *id005
99
37
  description: |
100
38
  A high-level wrapper around the zookeeper driver
101
39
 
@@ -112,6 +50,7 @@ files:
112
50
  - .dotfiles/rspec-logging
113
51
  - .dotfiles/rvmrc
114
52
  - .gitignore
53
+ - .travis.yml
115
54
  - .yardopts
116
55
  - Gemfile
117
56
  - LICENSE
@@ -138,6 +77,7 @@ files:
138
77
  - lib/z_k/threadpool.rb
139
78
  - lib/z_k/version.rb
140
79
  - lib/zk.rb
80
+ - spec/informal/lock_with_dead_session.rb
141
81
  - spec/log4j.properties
142
82
  - spec/message_queue_spec.rb
143
83
  - spec/spec_helper.rb
@@ -189,6 +129,7 @@ signing_key:
189
129
  specification_version: 3
190
130
  summary: A high-level wrapper around the zookeeper driver
191
131
  test_files:
132
+ - spec/informal/lock_with_dead_session.rb
192
133
  - spec/log4j.properties
193
134
  - spec/message_queue_spec.rb
194
135
  - spec/spec_helper.rb