zkruby 3.4.3 → 3.4.4.rc3

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/zkruby/util.rb CHANGED
@@ -31,111 +31,96 @@ module ZooKeeper
31
31
 
32
32
  op = create(sub_path,"",acl) { if i == -1 then callback.call() else :true end }
33
33
 
34
- op.errback do |err|
35
- if i == -1
36
- if ZK::Error::NODE_EXISTS === err
37
- callback.call()
38
- elsif ZK::Error::CONNECTION_LOST === err || ( ZK::Error::NO_NODE && connection_lost )
39
- # try again
40
- mkpath(path,acl)
41
- callback.call()
42
- else
43
- raise err
44
- end
45
- elsif ZK::Error::CONNECTION_LOST === err
46
- connection_lost = true
47
- :connection_lost
48
- else
49
- # we don't care about any other errors, but we will log them
50
- logger.warn { "Error creating #{sub_path}, #{err}" }
51
- end
34
+ if i == -1
35
+ op.on_error(ZK::Error::NODE_EXISTS) { callback.call() }
36
+ op.on_error(ZK::Error::CONNECTION_LOST, ZK::Error::NO_NODE) { |ex|
37
+ connection_lost = true if ZK::Error::CONNECTION_LOST === ex
38
+ raise ex unless connection_lost
39
+ mkpath(path,acl)
40
+ callback.call()
41
+ }
42
+ last_op = op
43
+ else
44
+ op.on_error(ZK::Error::CONNECTION_LOST) { connection_lost = true }
45
+ op.on_error(ZK::Error::NODE_EXISTS) { }
46
+ op.on_error() { |err| logger.debug { "mkpath error creating #{sub_path}, #{err}" } }
52
47
  end
53
- last_op = op if (i == -1)
54
- end
55
-
56
- return WrappedOp.new(last_op)
57
48
  end
58
49
 
59
- # Recursive delete
60
- #
61
- # Although this method itself can be called synchronously all the zk activity
62
- # is asynchronous, ie all subnodes are removed in parallel
63
- #
64
- # Will retry on connection loss, or if some other activity is adding nodes in parallel.
65
- # If you get a session expiry in the middle of this you will end up with a
66
- # partially completed recursive delete. In all other circumstances it will eventually
67
- # complete.
68
- #
69
- # @overload rmpath(path,version)
70
- # @param [String] path this node and all its children will be recursively deleted
71
- # @param [FixNum] version the expected version to be deleted (-1 to match any version).
72
- # Only applies to the top level path
73
- # @return
74
- # @raise [Error]
75
- # @overload rmpath(path,version)
76
- # @return [AsyncOp]
77
- # @yield [] callback invoked if delete is successful
78
- def rmpath(path,version = -1, &callback)
79
-
80
- return synchronous_call(:rmpath,path,version) unless block_given?
81
-
82
- del_op = delete(path,version) { callback.call() }
83
-
84
- del_op.errback do |err|
85
- # we don't leave this method unless we get an exception
86
- # or have completed and called the callback
87
- case err
88
- when ZK::Error::NO_NODE
89
- when ZK::Error::CONNECTION_LOST
90
- rmpath(path,version)
91
- when ZK::Error::NOT_EMPTY
92
-
93
- stat, child_list = children(path)
94
-
95
- unless child_list.empty?
96
- child_ops = {}
97
- child_list.each do |child|
98
- child_path = "#{path}/#{child}"
99
-
100
- rm_op = rmpath(child_path,-1) { :success }
101
-
102
- rm_op.errback { |err| child_results[child_path] = err }
103
-
104
- child_ops[child_path] = rm_op
105
- end
106
-
107
- # Wait until all our children are done (or error'd)
108
- child_ops.each { |child_path,op| op.value }
109
- rmpath(path,version)
110
- end
111
- else
112
- raise err
113
- end
114
- callback.call()
115
- end
50
+ return WrappedOp.new(last_op)
51
+ end
116
52
 
117
- return WrappedOp.new(del_op)
118
- end
119
- end #Client
53
+ # Recursive delete
54
+ #
55
+ # Although this method itself can be called synchronously all the zk activity
56
+ # is asynchronous, ie all subnodes are removed in parallel
57
+ #
58
+ # Will retry on connection loss, or if some other activity is adding nodes in parallel.
59
+ # If you get a session expiry in the middle of this you will end up with a
60
+ # partially completed recursive delete. In all other circumstances it will eventually
61
+ # complete.
62
+ #
63
+ # @overload rmpath(path,version)
64
+ # @param [String] path this node and all its children will be recursively deleted
65
+ # @param [FixNum] version the expected version to be deleted (-1 to match any version).
66
+ # Only applies to the top level path
67
+ # @return
68
+ # @raise [Error]
69
+ # @overload rmpath(path,version)
70
+ # @return [AsyncOp]
71
+ # @yield [] callback invoked if delete is successful
72
+ def rmpath(path,version = -1, &callback)
120
73
 
121
- class WrappedOp < AsyncOp
122
- def initialize(delegate_op)
123
- @delegate_op = delegate_op
124
- @errback = nil
125
- end
74
+ return synchronous_call(:rmpath,path,version) unless block_given?
75
+
76
+ del_op = delete(path,version) { callback.call() }
77
+
78
+ del_op.on_error(ZK::Error::NO_NODE) { callback.call() }
126
79
 
127
- private
128
- def wait_value()
129
- begin
130
- @delegate_op.value()
131
- rescue StandardError > err
132
- @errback ? @errback.call(err) : raise
80
+ del_op.on_error(ZK::Error::CONNECTION_LOST) { del_op.try_again() }
81
+
82
+ del_op.on_error(ZK::Error::NOT_EMPTY) do
83
+
84
+ stat, child_list = children(path)
85
+
86
+ unless child_list.empty?
87
+
88
+ child_ops = {}
89
+ child_list.each do |child|
90
+ child_path = "#{path}/#{child}"
91
+
92
+ rm_op = rmpath(child_path,-1) { :success }
93
+
94
+ rm_op.on_error { |err| child_results[child_path] = err }
95
+
96
+ child_ops[child_path] = rm_op
97
+ ZK.pass
98
+ end
99
+
100
+ # Wait until all our children are done (or error'd)
101
+ child_ops.each { |child_path,op| op.value }
102
+ rmpath(path,version)
133
103
  end
134
104
  end
135
105
 
136
- def set_error_handler(errback)
137
- @errback = errback
106
+ return WrappedOp.new(del_op)
107
+ end
108
+ end #Client
109
+
110
+ class WrappedOp < AsyncOp
111
+ #TODO you can't retry a wrapped op
112
+ def initialize(delegate_op)
113
+ @delegate_op = delegate_op
114
+ end
115
+
116
+ private
117
+ def wait_value()
118
+ begin
119
+ @delegate_op.value()
120
+ rescue StandardError => err
121
+ @errback ? @errback.call(err) : raise
138
122
  end
139
123
  end
124
+ end
140
125
 
141
126
  end #ZooKeeper
@@ -0,0 +1,5 @@
1
+
2
+ module ZooKeeper
3
+ # Major/Minor numbers track zookeeper itself, final digit is our build number
4
+ VERSION = "3.4.4.rc3"
5
+ end
data/lib/zkruby/zkruby.rb CHANGED
@@ -4,12 +4,6 @@
4
4
  # than calling the zk client libraries
5
5
  #
6
6
  module ZooKeeper
7
- # Major/Minor numbers track zookeeper itself, final digit is our build number
8
- VERSION = "3.4.3"
9
- @bindings = []
10
- def self.add_binding(binding)
11
- @bindings << binding unless @bindings.include?(binding)
12
- end
13
7
  end
14
8
 
15
9
  # Shorthand
@@ -2,49 +2,28 @@ require 'server_helper'
2
2
  require 'shared/binding'
3
3
  require 'zkruby/eventmachine'
4
4
 
5
- module EMHelper
6
- alias :restart_cluster_orig :restart_cluster
7
- def restart_cluster(delay=0)
8
- if EM.reactor_running?
9
- cv = Strand::ConditionVariable.new()
10
- op = Proc.new do
11
- begin
12
- restart_cluster_orig(delay)
13
- rescue Exception => ex
14
- logger.error ("Exception restarting cluster #{ex}")
15
- end
16
- true
17
- end
18
- cb = Proc.new { |result| cv.signal() }
19
- defer = EM.defer(op,cb)
20
- cv.wait()
21
- else
22
- restart_cluster_orig(delay)
23
- end
24
- end
25
-
26
- def sleep(delay)
27
- Strand.sleep(delay)
28
- end
29
- end
30
-
31
5
  describe ZooKeeper::EventMachine::Binding do
32
6
 
33
7
  include Slf4r::Logger
34
- include EMHelper
35
8
 
36
9
  around(:each) do |example|
37
10
  EventMachine.run {
38
11
  Strand.new() do
39
- begin
12
+ begin
40
13
  example.run
41
- ensure
14
+ rescue Exception => ex
15
+ logger.error("Exception in example",ex)
16
+ ensure
42
17
  EM::stop
43
- end
18
+ end
44
19
  end
45
20
  }
46
21
  end
47
22
 
23
+ it "should be running in event machine" do
24
+ Strand.event_machine?.should be_true
25
+ end
26
+
48
27
  it_should_behave_like "a zookeeper client binding"
49
28
 
50
29
  end
@@ -4,15 +4,18 @@ module ZooKeeperServerHelper
4
4
 
5
5
  include Slf4r::Logger
6
6
 
7
+ JRUBY_COMPAT_SYSTEM = (RUBY_PLATFORM == "java" && Gem::Version.new(JRUBY_VERSION.dup) < Gem::Version.new("1.6.5"))
8
+
9
+ def jruby_safe_system(arg)
10
+ arg = "#{arg} &" if JRUBY_COMPAT_SYSTEM
11
+ system(arg)
12
+ Strand.sleep(3) if JRUBY_COMPAT_SYSTEM
13
+ end
14
+
7
15
  def restart_cluster(delay=0)
8
- system("../../bin/zkServer.sh stop >> zk.out")
9
- Kernel::sleep(delay) if delay > 0
10
- if (::RUBY_PLATFORM == "java")
11
- #in JRuby 1.6.3 system does not return
12
- system("../../bin/zkServer.sh start >> zk.out &")
13
- else
14
- system("../../bin/zkServer.sh start >> zk.out")
15
- end
16
+ jruby_safe_system("../../bin/zkServer.sh stop >> zk.out")
17
+ Strand::sleep(delay) if delay > 0
18
+ jruby_safe_system("../../bin/zkServer.sh start >> zk.out")
16
19
  end
17
20
 
18
21
  def get_addresses()
@@ -34,7 +37,7 @@ end
34
37
  include ZooKeeperServerHelper
35
38
 
36
39
  restart_cluster()
37
- sleep(3)
40
+ Strand.sleep(3)
38
41
  require 'net/telnet'
39
42
  t = Net::Telnet.new("Host" => "localhost", "Port" => 2181)
40
43
  properties = t.cmd("mntr")
@@ -42,4 +45,5 @@ properties = t.cmd("mntr")
42
45
  RSpec.configure do |c|
43
46
  #Exclude multi unless we are on a 3.4 server
44
47
  c.filter_run_excluding :multi => true unless properties
48
+ c.filter_run_excluding :perf => true
45
49
  end
data/spec/shared/basic.rb CHANGED
@@ -52,6 +52,7 @@ shared_examples_for "basic integration" do
52
52
  # only because JRuby 1.9 doesn't support the === syntax for exceptions
53
53
  ZooKeeper::Error::NO_NODE.should === ex
54
54
  ex.message.should =~ /\/anunknownpath/
55
+ ex.message.should =~ /no_node/
55
56
  skip = if defined?(JRUBY_VERSION) then 2 else 1 end
56
57
  ex.backtrace[skip..-1].should == get_caller
57
58
  end
@@ -67,11 +68,13 @@ shared_examples_for "basic integration" do
67
68
  rescue ZooKeeper::Error => ex
68
69
  ZooKeeper::Error::NO_NODE.should === ex
69
70
  ex.message.should =~ /\/an\/unknown\/path/
71
+ ex.message.should =~ /no_node/
70
72
  ex.backtrace[1..-1].should == get_caller
71
73
  end
72
74
  end
73
75
 
74
76
  it "should call the error call back for asynchronous errors" do
77
+ get_caller = caller
75
78
  op = @zk.get("/an/unknown/path") do
76
79
  :callback_invoked_unexpectedly
77
80
  end
@@ -79,6 +82,9 @@ shared_examples_for "basic integration" do
79
82
  op.on_error do |err|
80
83
  case err
81
84
  when ZK::Error::NO_NODE
85
+ err.message.should =~ /\/an\/unknown\/path/
86
+ err.message.should =~ /no_node/
87
+ err.backtrace[1..-1].should == get_caller
82
88
  :found_no_node_error
83
89
  else
84
90
  raise err
@@ -88,6 +94,33 @@ shared_examples_for "basic integration" do
88
94
  op.value.should == :found_no_node_error
89
95
  end
90
96
 
97
+ it "should rescue errors" do
98
+ op = @zk.get("/an/unknown/path") do
99
+ :callback_invoked_unexpectedly
100
+ end
101
+
102
+ op.async_rescue (ZK::Error::NO_NODE) { :found_no_node_error }
103
+
104
+ op.value.should == :found_no_node_error
105
+ end
106
+
107
+ it "should retry on error" do
108
+ @zk.create("/retrypath","",ZK::ACL_OPEN_UNSAFE,:ephemeral)
109
+
110
+ result = :not_ready_yet
111
+ op = @zk.set("/retrypath","new data",2) do
112
+ result
113
+ end
114
+
115
+ op.async_rescue ZK::Error::BAD_VERSION do
116
+ @zk.set("/retrypath","version 2",-1)
117
+ result = :all_done_now
118
+ op.async_retry
119
+ end
120
+
121
+ op.value.should == :all_done_now
122
+ end
123
+
91
124
  it "should capture exceptions from asynchronous callback" do
92
125
  async_caller = nil
93
126
  op = @zk.exists?("/zkruby") do |stat|
@@ -114,7 +147,7 @@ shared_examples_for "basic integration" do
114
147
  context "auto reconnect" do
115
148
 
116
149
  it "should stay connected" do
117
- sleep(@zk.timeout * 2.0)
150
+ Strand.sleep(@zk.timeout * 2.0)
118
151
  @zk.exists?("/zkruby").should be_true
119
152
  end
120
153
 
@@ -125,7 +158,7 @@ shared_examples_for "basic integration" do
125
158
  watcher.should_receive(:process_watch).with(ZK::KeeperState::CONNECTED,nil,ZK::WatchEvent::NONE)
126
159
  watcher.should_not_receive(:process_watch).with(ZK::KeeperState::EXPIRED,nil,ZK::WatchEvent::NONE)
127
160
  @zk.watcher = watcher
128
- restart_cluster(2)
161
+ restart_cluster(1.5)
129
162
  @zk.exists?("/zkruby").should be_true
130
163
  end
131
164
 
@@ -148,6 +181,7 @@ shared_examples_for "basic integration" do
148
181
  end
149
182
 
150
183
  it "should handle a synchronous call inside an asynchronous callback" do
184
+ ZK.current.should equal(@zk)
151
185
  op = @zk.create("/zkruby/sync_async","somedata",ZK::ACL_OPEN_UNSAFE) do
152
186
  ZK.current.should equal(@zk)
153
187
  stat, data = @zk.get("/zkruby/sync_async")
@@ -5,6 +5,7 @@ require 'shared/chroot'
5
5
  require 'shared/watch'
6
6
  require 'shared/multi'
7
7
  require 'shared/auth'
8
+ require 'shared/performance'
8
9
 
9
10
  shared_examples_for "a zookeeper client binding" do
10
11
 
@@ -23,6 +24,7 @@ shared_examples_for "a zookeeper client binding" do
23
24
  include_examples("basic integration")
24
25
  include_examples("util recipes")
25
26
  include_examples("multi")
27
+ include_examples("performance")
26
28
 
27
29
  end
28
30
 
@@ -57,5 +57,35 @@ shared_examples_for "chrooted connection" do
57
57
  end
58
58
 
59
59
  end
60
+
61
+ context "multi", :multi => true do
62
+
63
+ it "should do chrooted operations in a transaction" do
64
+ new_path = nil
65
+
66
+ # we're gonna delete this one
67
+ delete_path = @zk.create("/zkruby/multi","hello multi",ZK::ACL_OPEN_UNSAFE,:ephemeral,:sequential)
68
+
69
+ # we use the version in the check command below
70
+ stat, data = @zk.get("/zkruby")
71
+ @ch_zk.transaction() do |txn|
72
+ txn.create("/multi","chroot world", ZK::ACL_OPEN_UNSAFE,:ephemeral,:sequential) { |path| logger.debug{ "New path #{path}"} ; new_path = path }
73
+ txn.check("/",stat.version)
74
+ txn.set("/","chroot multi data",-1)
75
+ txn.delete(delete_path[7..-1],-1)
76
+ end
77
+ new_path.should_not be_nil
78
+
79
+ stat, data = @zk.get("/zkruby#{new_path}")
80
+ data.should == "chroot world"
81
+
82
+ stat, data = @zk.get("/zkruby")
83
+ data.should == "chroot multi data"
84
+
85
+ @zk.exists?(delete_path).should be_nil
86
+
87
+ end
88
+
89
+ end
60
90
  end
61
91
  end
data/spec/shared/multi.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  shared_examples_for "multi" do
2
2
 
3
3
  context "only in 3.4 and up", :multi => true do
4
- it "should do a create in a multi-op" do
4
+ it "should do all the multi ops" do
5
5
  delete_path = @zk.create("/zkruby/multi","hello multi",ZK::ACL_OPEN_UNSAFE,:ephemeral,:sequential)
6
6
 
7
7
  new_path = nil
@@ -0,0 +1,50 @@
1
+ shared_examples_for "performance" do
2
+
3
+ describe "performance", :perf => true do
4
+
5
+ it "should create and retrieve lots of nodes in a reasonable amount of time" do
6
+
7
+ path = "/zkruby/rspec-perf"
8
+
9
+ @zk.mkpath(path)
10
+
11
+ op = nil
12
+ start = Time.now
13
+ count = 0
14
+ pass_every = 5
15
+ first_error = true
16
+ 6000.times do
17
+ count += 1
18
+ this_index = count
19
+ op = @zk.create("#{path}/","hello", ZK::ACL_OPEN_UNSAFE,:sequential,:ephemeral) { }
20
+ op.on_error { |ex| puts "Error @ #{this_index}" if first_error; first_error = false }
21
+ if count % pass_every == 0
22
+ #puts "Passing @ #{count}"
23
+ ZK.pass
24
+ end
25
+ end
26
+
27
+ op.value
28
+ finish = Time.now
29
+ diff = finish - start
30
+ puts "Created #{count} in #{diff}"
31
+ stat,children = @zk.children(path)
32
+
33
+ count = 0
34
+ children.each do |child|
35
+ op = @zk.get("#{path}/#{child}") { }
36
+ count += 1
37
+ if count % pass_every == 0
38
+ #puts "Passing @ #{count}"
39
+ ZK.pass
40
+ end
41
+ end
42
+
43
+ op.value
44
+ finish_get = Time.now
45
+ diff = finish_get - finish
46
+ puts "Retrieved #{count} in #{diff}"
47
+ end
48
+
49
+ end
50
+ end
data/spec/shared/watch.rb CHANGED
@@ -18,7 +18,7 @@ shared_examples_for "watches" do
18
18
  stat,data = @zk.get(path,watch)
19
19
  # set the data on the 2nd session
20
20
  @zk2.set(path,"newdata",stat.version)
21
- sleep(5)
21
+ Strand.sleep(2)
22
22
  watch_results.size().should == 1
23
23
  watch_results[0][1].should == path
24
24
  watch_results[0][2].should === :node_data_changed
@@ -31,7 +31,7 @@ shared_examples_for "watches" do
31
31
 
32
32
  stat,children = @zk.children("/zkruby",watch)
33
33
  path = @zk2.create("/zkruby/rspec_watch","somedata",ZK::ACL_OPEN_UNSAFE,:ephemeral,:sequential)
34
- sleep(5)
34
+ Strand.sleep(2)
35
35
  watch_results.size().should == 1
36
36
  watch_results[0][1].should == "/zkruby"
37
37
  watch_results[0][2].should === :node_children_changed
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'slf4r/logging_logger'
2
2
  require 'zkruby/zkruby'
3
3
 
4
- Logging.logger.root.level = :error
5
- Logging.logger.root.appenders = Logging.appenders.stdout(:layout => Logging.layouts.pattern(:pattern => '%c [%T] %-5l: %m\n'))
4
+ Logging.logger.root.level = :warn
5
+ Logging.logger.root.appenders = Logging.appenders.stdout(:layout => Logging.layouts.pattern(:pattern => '%r %c [%T] %-5l: %m\n'))
6
6
  #Logging.logger[ZooKeeper::RubyIO::Connection].level = :error
7
- #Logging.logger[ZooKeeper::RubyIO::Binding].level = :error
7
+ #Logging.logger["ZooKeeper::RubyIO::Binding"].level = :debug
8
8
  #Logging.logger[ZooKeeper::Session].level = :debug
9
9
  #Logging.logger["ZooKeeper::EventMachine::ClientConn"].level = :debug
10
10
  #Logging.logger["ZooKeeper::Session::Ping"].level = :error
data/zkruby.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "zkruby/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "zkruby"
8
+ s.version = ZooKeeper::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Grant Gardner"]
11
+ s.email = ["grant@lastweekend.com.au"]
12
+ s.homepage = "http://rubygems.org/gems/zkruby"
13
+ s.summary = %q{Pure Ruby language binding for ZooKeeper}
14
+ s.description = %q{Supports full ZooKeeper API, synchronous or asynchronous style, watches etc.. with implementations over EventMachine or plain old Ruby IO/Threads}
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.files << 'lib/jute/zookeeper.rb'
18
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
19
+ s.require_paths = ["lib"]
20
+
21
+ s.has_rdoc = 'yard'
22
+ # Yard options in .yardopts
23
+
24
+ s.add_dependency 'slf4r' , '~> 0.4.2'
25
+ s.add_dependency 'strand', '~> 0.2.0.rc0'
26
+ s.add_dependency 'bindata', '~> 1.4.1'
27
+
28
+ s.add_development_dependency 'eventmachine', '>= 0.12.10'
29
+ s.add_development_dependency 'logging', '>= 1.4.1'
30
+
31
+ s.add_development_dependency("rake")
32
+ s.add_development_dependency("rspec")
33
+ s.add_development_dependency("yard")
34
+
35
+ # s.add_development_dependency("jute")
36
+ s.add_development_dependency "citrus" , '~> 2.4.0'
37
+
38
+ end