zookeeper 1.0.6-java → 1.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,3 @@
1
- # require File.expand_path('../c_zookeeper', __FILE__)
2
-
3
1
  require_relative 'c_zookeeper'
4
2
  require 'forwardable'
5
3
 
@@ -33,29 +31,8 @@ class ZookeeperBase
33
31
  ZOO_LOG_LEVEL_DEBUG = 4
34
32
 
35
33
 
36
- # this is unfortunately necessary to prevent a really horrendous race in
37
- # shutdown, where some other thread calls close in the middle of a
38
- # synchronous operation (thanks to the GIL-releasing wrappers, we now
39
- # have this problem). so we need to make sure only one thread can be calling
40
- # a synchronous operation at a time.
41
- #
42
- # this might be solved by waiting for a condition where there are no "in flight"
43
- # operations (thereby allowing multiple threads to make requests simultaneously),
44
- # but this would represent quite a bit of added complexity, and questionable
45
- # performance gains.
46
- #
47
- def self.synchronized_delegation(provider, *syms)
48
- syms.each do |sym|
49
- class_eval(<<-EOM, __FILE__, __LINE__+1)
50
- def #{sym}(*a, &b)
51
- @mutex.synchronize { #{provider}.#{sym}(*a, &b) }
52
- end
53
- EOM
54
- end
55
- end
56
-
57
- synchronized_delegation :@czk, :get_children, :exists, :delete, :get, :set,
58
- :set_acl, :get_acl, :client_id, :sync, :wait_until_connected
34
+ def_delegators :@czk, :get_children, :exists, :delete, :get, :set,
35
+ :set_acl, :get_acl, :client_id, :sync, :wait_until_connected, :pause, :resume
59
36
 
60
37
  # some state methods need to be more paranoid about locking to ensure the correct
61
38
  # state is returned
@@ -216,7 +193,8 @@ class ZookeeperBase
216
193
  def closed?
217
194
  @mutex.synchronize { !@czk or @czk.closed? }
218
195
  end
219
-
196
+
197
+
220
198
  protected
221
199
  # this is a hack: to provide consistency between the C and Java drivers when
222
200
  # using a chrooted connection, we wrap the callback in a block that will
@@ -224,6 +202,9 @@ protected
224
202
  # sequential call). This is the only place where we can hook *just* the C
225
203
  # version. The non-async manipulation is handled in ZookeeperBase#create.
226
204
  #
205
+ # TODO: need to move the continuation setup into here, so that it can get
206
+ # added to the callback hash
207
+ #
227
208
  def setup_completion(req_id, meth_name, call_opts)
228
209
  if (meth_name == :create) and cb = call_opts[:callback]
229
210
  call_opts[:callback] = lambda do |hash|
@@ -98,7 +98,7 @@ module ClientMethods
98
98
  #
99
99
  # @note There is a discrepancy between the zkc and java versions. zkc takes
100
100
  # a string_callback_t, java takes a VoidCallback. You should most likely use
101
- # the ZookeeperCallbacks::VoidCallback and not rely on the string value.
101
+ # the Zookeeper::Callbacks::VoidCallback and not rely on the string value.
102
102
  #
103
103
  def sync(options = {})
104
104
  assert_open
@@ -143,14 +143,15 @@ protected
143
143
  # we want to rerun the callback at a later time when we eventually do have
144
144
  # a valid response.
145
145
  if hash[:type] == Zookeeper::Constants::ZOO_SESSION_EVENT
146
+ # XXX: setup_completion changed arity, is this setup_completion necessary anymore?
146
147
  is_completion ? setup_completion(hash[:req_id], callback_context) : setup_watcher(hash[:req_id], callback_context)
147
148
  end
149
+
148
150
  if callback_context
149
151
  callback = is_completion ? callback_context[:callback] : callback_context[:watcher]
150
152
 
151
153
  hash[:context] = callback_context[:context]
152
154
 
153
- # TODO: Eventually enforce derivation from Zookeeper::Callback
154
155
  if callback.respond_to?(:call)
155
156
  callback.call(hash)
156
157
  else
@@ -164,7 +165,7 @@ protected
164
165
 
165
166
  def assert_supported_keys(args, supported)
166
167
  unless (args.keys - supported).empty?
167
- raise Zookeeper::Exceptions::BadArguments, # this heirarchy is kind of retarded
168
+ raise Zookeeper::Exceptions::BadArguments,
168
169
  "Supported arguments are: #{supported.inspect}, but arguments #{args.keys.inspect} were supplied instead"
169
170
  end
170
171
  end
@@ -57,6 +57,7 @@ module Constants
57
57
  ZSESSIONMOVED = -118
58
58
 
59
59
  ZKRB_GLOBAL_CB_REQ = -1
60
+ ZKRB_ASYNC_CONTN_ID = -2
60
61
 
61
62
  # @private
62
63
  CONNECTED_EVENT_VALUES = [Constants::ZKRB_GLOBAL_CB_REQ,
@@ -0,0 +1,155 @@
1
+ module Zookeeper
2
+ # @private
3
+ # sigh, slightly different than the userland callbacks, the continuation
4
+ # provides sync call semantics around an async api
5
+ class Continuation
6
+ include Constants
7
+ include Logger
8
+
9
+ # for keeping track of which continuations are pending, and which ones have
10
+ # been submitted and are awaiting a repsonse
11
+ class Registry < Struct.new(:pending, :in_flight)
12
+ extend Forwardable
13
+
14
+ def_delegators :@mutex, :lock, :unlock
15
+
16
+ def initialize
17
+ super([], {})
18
+ @mutex = Mutex.new
19
+ end
20
+
21
+ def synchronized
22
+ @mutex.lock
23
+ begin
24
+ yield self
25
+ ensure
26
+ @mutex.unlock
27
+ end
28
+ end
29
+
30
+ # does not lock the mutex, returns true if there are pending jobs
31
+ def pending?
32
+ !self.pending.empty?
33
+ end
34
+ end
35
+
36
+ # *sigh* what is the index in the *args array of the 'callback' param
37
+ CALLBACK_ARG_IDX = {
38
+ :get => 2,
39
+ :set => 3,
40
+ :exists => 2,
41
+ :create => 3,
42
+ :delete => 3,
43
+ :get_acl => 2,
44
+ :set_acl => 3,
45
+ :get_children => 2,
46
+ }
47
+
48
+ # maps the method name to the async return hash keys it should use to
49
+ # deliver the results
50
+ METH_TO_ASYNC_RESULT_KEYS = {
51
+ :get => [:rc, :data, :stat],
52
+ :set => [:rc, :stat],
53
+ :exists => [:rc, :stat],
54
+ :create => [:rc, :string],
55
+ :delete => [:rc],
56
+ :get_acl => [:rc, :acl, :stat],
57
+ :set_acl => [:rc],
58
+ :get_children => [:rc, :strings, :stat],
59
+ }
60
+
61
+ attr_accessor :meth, :block, :rval
62
+
63
+ def initialize(meth, *args)
64
+ @meth = meth
65
+ @args = args
66
+ @mutex = Mutex.new
67
+ @cond = ConditionVariable.new
68
+ @rval = nil
69
+
70
+ # set to true when an event occurs that would cause the caller to
71
+ # otherwise block forever
72
+ @interrupt = false
73
+ end
74
+
75
+ # the caller calls this method and receives the response from the async loop
76
+ def value
77
+ @mutex.lock
78
+ begin
79
+ @cond.wait(@mutex) until @rval
80
+
81
+ case @rval.length
82
+ when 1
83
+ return @rval.first
84
+ else
85
+ return @rval
86
+ end
87
+ ensure
88
+ @mutex.unlock
89
+ end
90
+ end
91
+
92
+ # receive the response from the server, set @rval, notify caller
93
+ def call(hash)
94
+ logger.debug { "continuation req_id #{req_id}, got hash: #{hash.inspect}" }
95
+ @rval = hash.values_at(*METH_TO_ASYNC_RESULT_KEYS.fetch(meth))
96
+ logger.debug { "delivering result #{@rval.inspect}" }
97
+ deliver!
98
+ end
99
+
100
+ def user_callback?
101
+ !!@args.at(callback_arg_idx)
102
+ end
103
+
104
+ # this method is called by the event thread to submit the request
105
+ # passed the CZookeeper instance, makes the async call and deals with the results
106
+ #
107
+ # BTW: in case you were wondering this is a completely stupid
108
+ # implementation, but it's more important to get *something* working and
109
+ # passing specs, then refactor to make everything sane
110
+ #
111
+ def submit(czk)
112
+ rc, *_ = czk.__send__(:"zkrb_#{@meth}", *async_args)
113
+
114
+ if user_callback? or (rc != ZOK) # if this is an async call, or we failed to submit it
115
+ @rval = [rc] # create the repsonse
116
+ deliver! # wake the caller and we're out
117
+ end
118
+ end
119
+
120
+ def req_id
121
+ @args.first
122
+ end
123
+
124
+ protected
125
+
126
+ # an args array with the only difference being that if there's a user
127
+ # callback provided, we don't handle delivering the end result
128
+ def async_args
129
+ ary = @args.dup
130
+
131
+ logger.debug { "async_args, meth: #{meth} ary: #{ary.inspect}, #{callback_arg_idx}" }
132
+
133
+ # this is not already an async call
134
+ # so we replace the req_id with the ZKRB_ASYNC_CONTN_ID so the
135
+ # event thread knows to dispatch it itself
136
+ ary[callback_arg_idx] ||= self
137
+
138
+ ary
139
+ end
140
+
141
+ def callback_arg_idx
142
+ CALLBACK_ARG_IDX.fetch(meth) { raise ArgumentError, "unknown method #{meth.inspect}" }
143
+ end
144
+
145
+ def deliver!
146
+ @mutex.lock
147
+ begin
148
+ @cond.signal
149
+ ensure
150
+ @mutex.unlock
151
+ end
152
+ end
153
+ end # Base
154
+ end
155
+
@@ -77,6 +77,13 @@ stacktrace:
77
77
  class ShuttingDownException < ZookeeperException; end
78
78
  class DataTooLargeException < ZookeeperException; end
79
79
 
80
+ # raised when an operation is performed on an instance without a valid
81
+ # zookeeper handle. (C version)
82
+ class HandleClosedException < ZookeeperException; end
83
+
84
+ # maybe use this for continuation
85
+ class InterruptedException < ZookeeperException ; end
86
+
80
87
  # raised when the user tries to use a connection after a fork()
81
88
  # without calling reopen() in the C client
82
89
  #
@@ -14,6 +14,13 @@ module Zookeeper
14
14
  end
15
15
 
16
16
  private
17
+ def log_realtime(what)
18
+ logger.debug do
19
+ t = Benchmark.realtime { yield }
20
+ "#{what} took #{t} sec"
21
+ end
22
+ end
23
+
17
24
  def logger
18
25
  ::Zookeeper.logger
19
26
  end
@@ -0,0 +1,19 @@
1
+ module Zookeeper
2
+ # just like stdlib Monitor but provides the SAME API AS MUTEX, FFS!
3
+ class Monitor
4
+ include MonitorMixin
5
+
6
+ alias try_enter try_mon_enter
7
+ alias enter mon_enter
8
+ alias exit mon_exit
9
+
10
+ # here, HERE!
11
+ # *here* are the methods that are the same
12
+ # god *dammit*
13
+
14
+ alias lock mon_enter
15
+ alias unlock mon_exit
16
+ alias try_lock try_mon_enter
17
+ end
18
+ end
19
+
@@ -1,4 +1,4 @@
1
1
  module Zookeeper
2
- VERSION = '1.0.6'
2
+ VERSION = '1.1.0'
3
3
  DRIVER_VERSION = '3.3.5'
4
4
  end
data/lib/zookeeper.rb CHANGED
@@ -4,6 +4,7 @@ require 'thread'
4
4
  require 'monitor'
5
5
  require 'forwardable'
6
6
  require 'logger'
7
+ require 'benchmark'
7
8
 
8
9
  module Zookeeper
9
10
  # establishes the namespace
@@ -13,12 +14,14 @@ require File.expand_path('../zookeeper/core_ext', __FILE__)
13
14
 
14
15
  require 'backports' if RUBY_VERSION =~ /\A1\.8\./
15
16
 
17
+ require_relative 'zookeeper/monitor'
16
18
  require_relative 'zookeeper/logger'
17
19
  require_relative 'zookeeper/forked'
18
20
  require_relative 'zookeeper/latch'
19
21
  require_relative 'zookeeper/acls'
20
22
  require_relative 'zookeeper/constants'
21
23
  require_relative 'zookeeper/exceptions'
24
+ require_relative 'zookeeper/continuation'
22
25
  require_relative 'zookeeper/common'
23
26
  require_relative 'zookeeper/callbacks'
24
27
  require_relative 'zookeeper/stat'
@@ -15,6 +15,7 @@ unless defined?(::JRUBY_VERSION)
15
15
  end
16
16
 
17
17
  before do
18
+
18
19
  if defined?(::Rubinius)
19
20
  pending("this test is currently broken in rbx")
20
21
  # elsif ENV['TRAVIS']
@@ -23,9 +24,11 @@ unless defined?(::JRUBY_VERSION)
23
24
  @zk = Zookeeper.new(connection_string)
24
25
  rm_rf(@zk, path)
25
26
  end
27
+ logger.debug { "----------------< BEFORE: END >-------------------" }
26
28
  end
27
29
 
28
30
  after do
31
+ logger.debug { "----------------< AFTER: BEGIN >-------------------" }
29
32
  if @pid and process_alive?(@pid)
30
33
  begin
31
34
  Process.kill('KILL', @pid)
@@ -53,6 +56,7 @@ unless defined?(::JRUBY_VERSION)
53
56
  end
54
57
 
55
58
  it %[should do the right thing and not fail] do
59
+ logger.debug { "----------------< TEST: BEGIN >-------------------" }
56
60
  @zk.wait_until_connected
57
61
 
58
62
  mkdir_p(@zk, pids_root)
@@ -73,20 +77,24 @@ unless defined?(::JRUBY_VERSION)
73
77
 
74
78
  logger.debug { "-------------------> FORK <---------------------------" }
75
79
 
80
+ @zk.pause
81
+
76
82
  @pid = fork do
77
83
  logger.debug { "reopening connection in child: #{$$}" }
78
84
  @zk.reopen
79
85
  logger.debug { "creating path" }
80
86
  rv = @zk.create(:path => "#{pids_root}/child", :data => $$.to_s)
81
- logger.debug { "created path #{rv}" }
87
+ logger.debug { "created path #{rv[:path]}" }
82
88
  @zk.close
83
89
 
84
90
  logger.debug { "close finished" }
85
91
  exit!(0)
86
92
  end
87
93
 
94
+ @zk.resume
95
+
88
96
  event_waiter_th = Thread.new do
89
- @latch.await(10) unless @event
97
+ @latch.await(5) unless @event
90
98
  @event
91
99
  end
92
100
 
@@ -99,8 +107,7 @@ unless defined?(::JRUBY_VERSION)
99
107
  status.should be_success
100
108
 
101
109
  event_waiter_th.join(5).should == event_waiter_th
102
- @event.should_not be nil
110
+ @event.should_not be_nil
103
111
  end
104
112
  end
105
113
  end
106
-
@@ -21,7 +21,7 @@ shared_examples_for "connection" do
21
21
  # unfortunately, we can't test w/o exercising other parts of the driver, so
22
22
  # if "set" is broken, this test will fail as well (but whaddyagonnado?)
23
23
  describe :get do
24
- describe :sync do
24
+ describe :sync, :sync => true do
25
25
  it_should_behave_like "all success return values"
26
26
 
27
27
  before do
@@ -38,7 +38,7 @@ shared_examples_for "connection" do
38
38
  end
39
39
  end
40
40
 
41
- describe :sync_watch do
41
+ describe :sync_watch, :sync => true do
42
42
  it_should_behave_like "all success return values"
43
43
 
44
44
  before do
@@ -65,7 +65,7 @@ shared_examples_for "connection" do
65
65
  end
66
66
  end
67
67
 
68
- describe :async do
68
+ describe :async, :async => true do
69
69
  before do
70
70
  @cb = Zookeeper::Callbacks::DataCallback.new
71
71
 
@@ -90,16 +90,18 @@ shared_examples_for "connection" do
90
90
  end
91
91
  end
92
92
 
93
- describe :async_watch do
93
+ describe :async_watch, :async => true, :method => :get, :watch => true do
94
94
  it_should_behave_like "all success return values"
95
95
 
96
96
  before do
97
+ logger.debug { "-----------------> MAKING ASYNC GET REQUEST WITH WATCH <--------------------" }
97
98
  @cb = Zookeeper::Callbacks::DataCallback.new
98
99
  @watcher = Zookeeper::Callbacks::WatcherCallback.new
99
100
 
100
101
  @rv = zk.get(:path => path, :callback => @cb, :callback_context => path, :watcher => @watcher, :watcher_context => path)
101
102
  wait_until(1.0) { @cb.completed? }
102
103
  @cb.should be_completed
104
+ logger.debug { "-----------------> ASYNC GET REQUEST WITH WATCH COMPLETE <--------------------" }
103
105
  end
104
106
 
105
107
  it %[should have the stat object in the callback] do
@@ -140,7 +142,7 @@ shared_examples_for "connection" do
140
142
  @stat = zk.stat(:path => path)[:stat]
141
143
  end
142
144
 
143
- describe :sync do
145
+ describe :sync, :sync => true do
144
146
  describe 'without version' do
145
147
  it_should_behave_like "all success return values"
146
148
 
@@ -195,7 +197,7 @@ shared_examples_for "connection" do
195
197
  end
196
198
  end # sync
197
199
 
198
- describe :async do
200
+ describe :async, :async => true do
199
201
  before do
200
202
  @cb = Zookeeper::Callbacks::StatCallback.new
201
203
  end
@@ -286,7 +288,7 @@ shared_examples_for "connection" do
286
288
  end
287
289
  end
288
290
 
289
- describe :sync do
291
+ describe :sync, :sync => true do
290
292
  it_should_behave_like "all success return values"
291
293
 
292
294
  before do
@@ -308,7 +310,7 @@ shared_examples_for "connection" do
308
310
  end
309
311
  end
310
312
 
311
- describe :sync_watch do
313
+ describe :sync_watch, :sync => true do
312
314
  it_should_behave_like "all success return values"
313
315
 
314
316
  before do
@@ -349,7 +351,7 @@ shared_examples_for "connection" do
349
351
  end
350
352
  end
351
353
 
352
- describe :async do
354
+ describe :async, :async => true do
353
355
  it_should_behave_like "all success return values"
354
356
 
355
357
  before do
@@ -377,7 +379,7 @@ shared_examples_for "connection" do
377
379
  end
378
380
  end
379
381
 
380
- describe :async_watch do
382
+ describe :async_watch, :async => true do
381
383
  it_should_behave_like "all success return values"
382
384
 
383
385
  before do
@@ -429,7 +431,7 @@ shared_examples_for "connection" do
429
431
  # NOTE: the jruby version of stat on non-existent node will have a
430
432
  # return_code of 0, but the C version will have a return_code of -101
431
433
  describe :stat do
432
- describe :sync do
434
+ describe :sync, :sync => true do
433
435
  it_should_behave_like "all success return values"
434
436
 
435
437
  before do
@@ -441,7 +443,7 @@ shared_examples_for "connection" do
441
443
  end
442
444
  end
443
445
 
444
- describe :sync_watch do
446
+ describe :sync_watch, :sync => true do
445
447
  it_should_behave_like "all success return values"
446
448
 
447
449
  before do
@@ -468,7 +470,7 @@ shared_examples_for "connection" do
468
470
  end
469
471
  end
470
472
 
471
- describe :async do
473
+ describe :async, :async => true do
472
474
  it_should_behave_like "all success return values"
473
475
 
474
476
  before do
@@ -488,7 +490,7 @@ shared_examples_for "connection" do
488
490
  end
489
491
  end
490
492
 
491
- describe :async_watch do
493
+ describe :async_watch, :async => true do
492
494
  it_should_behave_like "all success return values"
493
495
 
494
496
  before do
@@ -536,7 +538,7 @@ shared_examples_for "connection" do
536
538
  zk.delete(:path => path)
537
539
  end
538
540
 
539
- describe :sync do
541
+ describe :sync, :sync => true do
540
542
  describe 'error' do
541
543
  it %[should barf if the data size is too large], :input_size => true do
542
544
  large_data = '0' * (1024 ** 2)
@@ -640,7 +642,7 @@ shared_examples_for "connection" do
640
642
  end
641
643
  end
642
644
 
643
- describe :async do
645
+ describe :async, :async => true do
644
646
  before do
645
647
  @cb = Zookeeper::Callbacks::StringCallback.new
646
648
  end
@@ -775,7 +777,7 @@ shared_examples_for "connection" do
775
777
  end # create
776
778
 
777
779
  describe :delete do
778
- describe :sync do
780
+ describe :sync, :sync => true do
779
781
  describe 'without version' do
780
782
  it_should_behave_like "all success return values"
781
783
 
@@ -819,7 +821,7 @@ shared_examples_for "connection" do
819
821
  end
820
822
  end # sync
821
823
 
822
- describe :async do
824
+ describe :async, :async => true do
823
825
  before do
824
826
  @cb = Zookeeper::Callbacks::VoidCallback.new
825
827
  end
@@ -878,7 +880,7 @@ shared_examples_for "connection" do
878
880
  end # delete
879
881
 
880
882
  describe :get_acl do
881
- describe :sync do
883
+ describe :sync, :sync => true do
882
884
  it_should_behave_like "all success return values"
883
885
 
884
886
  before do
@@ -902,7 +904,7 @@ shared_examples_for "connection" do
902
904
  end
903
905
  end
904
906
 
905
- describe :async do
907
+ describe :async, :async => true do
906
908
  it_should_behave_like "all success return values"
907
909
 
908
910
  before do
@@ -939,7 +941,7 @@ shared_examples_for "connection" do
939
941
  pending("No idea how to set ACLs")
940
942
  end
941
943
 
942
- describe :sync do
944
+ describe :sync, :sync => true do
943
945
  it_should_behave_like "all success return values"
944
946
 
945
947
  before do
@@ -960,7 +962,7 @@ shared_examples_for "connection" do
960
962
  end
961
963
  end
962
964
 
963
- describe :sync do
965
+ describe :sync, :sync => true do
964
966
  describe :success do
965
967
  it_should_behave_like "all success return values"
966
968
 
data/spec/spec_helper.rb CHANGED
@@ -32,8 +32,8 @@ end
32
32
 
33
33
  RSpec.configure do |config|
34
34
  config.mock_with :rspec
35
- config.include Zookeeper::SpecHeleprs
36
- config.extend Zookeeper::SpecHeleprs
35
+ config.include Zookeeper::SpecHelpers
36
+ config.extend Zookeeper::SpecHelpers
37
37
 
38
38
  if Zookeeper.spawn_zookeeper?
39
39
  require 'zk-server'
@@ -1,5 +1,5 @@
1
1
  module Zookeeper
2
- module SpecHeleprs
2
+ module SpecHelpers
3
3
  class TimeoutError < StandardError; end
4
4
  include Zookeeper::Constants
5
5
  include Zookeeper::Logger