slyphon-zookeeper 0.3.0-java → 0.8.0-java

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/CHANGELOG CHANGED
@@ -1,3 +1,36 @@
1
+ v0.8.0 Refactor C implementaion, EventMachine client
2
+
3
+ * separated CZookeeper and ZookeeperBase implementation
4
+
5
+ This solves issues with reopen not working properly, makes for a much
6
+ cleaner event delivery implementation. ZookeeperBase controls the lifecycle
7
+ of the event dispatch thread now, rather than it being tied to CZookeeper.
8
+
9
+ * added support for the 'sync' API call
10
+
11
+ * Refactored zookeeper_c.c and zookeeper_lib.c
12
+
13
+ More error checking in zookeeper_lib.c and restructure some things to make
14
+ logic easier to follow
15
+
16
+ Fix bug in method_get_next_event that made the shutdown case so complicated
17
+
18
+ * Massively simplified EMClient implementation
19
+
20
+ Rather than trying to hook the IO used by zookeeper_lib to notify zookeeper_c
21
+ about event availabiltiy directly into EventMachine, use the same event delivery
22
+ thread, but wrap the dispatch call in EM.schedule.
23
+
24
+ * Improve implementation of spin-lock-esque code that waits for the connection to be
25
+ established before returning.
26
+
27
+ This cut the test runtime down from 1m 20s to 2s.
28
+
29
+ * Java client refactoring, similar correctness changes
30
+
31
+ * Change ZookeeperException base class to StandardError instead of Exception
32
+
33
+
1
34
  v0.4.5 Upgrade to ZooKeeper 3.3.3
2
35
 
3
36
  v0.4.4 Fix race condition on close, possible data corruption on async get.
@@ -24,3 +57,4 @@ v0.2. Bundle C dependencies, like memcached.gem.
24
57
 
25
58
  v0.1. First release.
26
59
 
60
+ # vim:ft=text:ts=2:sw=2:et
data/Rakefile CHANGED
@@ -15,10 +15,15 @@ gemset_name = 'zookeeper'
15
15
  %w[1.8.7 1.9.2 1.9.3 jruby].each do |rvm_ruby|
16
16
  ruby_with_gemset = "#{rvm_ruby}@#{gemset_name}"
17
17
 
18
- clobber_task_name = "mb:#{rvm_ruby}:clobber"
19
- build_task_name = "mb:#{rvm_ruby}:build"
20
- bundle_task_name = "mb:#{rvm_ruby}:bundle_install"
21
- rspec_task_name = "mb:#{rvm_ruby}:run_rspec"
18
+ create_gemset_name = "mb:#{rvm_ruby}:create_gemset"
19
+ clobber_task_name = "mb:#{rvm_ruby}:clobber"
20
+ build_task_name = "mb:#{rvm_ruby}:build"
21
+ bundle_task_name = "mb:#{rvm_ruby}:bundle_install"
22
+ rspec_task_name = "mb:#{rvm_ruby}:run_rspec"
23
+
24
+ task create_gemset_name do
25
+ sh "rvm #{rvm_ruby} do rvm gemset create #{gemset_name}"
26
+ end
22
27
 
23
28
  task clobber_task_name do
24
29
  unless rvm_ruby == 'jruby'
@@ -28,7 +33,7 @@ gemset_name = 'zookeeper'
28
33
  end
29
34
  end
30
35
 
31
- task build_task_name => clobber_task_name do
36
+ task build_task_name => [create_gemset_name, clobber_task_name] do
32
37
  unless rvm_ruby == 'jruby'
33
38
  cd 'ext' do
34
39
  sh "rvm #{ruby_with_gemset} do rake build"
@@ -48,4 +53,19 @@ gemset_name = 'zookeeper'
48
53
  task "mb:test_all_rubies" => rspec_task_name
49
54
  end
50
55
 
56
+ namespace :build do
57
+ task :clean do
58
+ cd 'ext' do
59
+ sh 'rake clean'
60
+ end
61
+
62
+ Rake::Task['build'].invoke
63
+ end
64
+ end
65
+
66
+ task :build do
67
+ cd 'ext' do
68
+ sh "rake"
69
+ end
70
+ end
51
71
 
@@ -0,0 +1,214 @@
1
+ require 'zookeeper/constants'
2
+ require File.expand_path('../zookeeper_c', __FILE__)
3
+
4
+ # TODO: see if we can get the destructor to handle thread/event queue teardown
5
+ # when we're garbage collected
6
+ class CZookeeper
7
+ include ZookeeperCommon
8
+ include ZookeeperConstants
9
+ include ZookeeperExceptions
10
+
11
+ DEFAULT_SESSION_TIMEOUT_MSEC = 10000
12
+
13
+ class GotNilEventException < StandardError; end
14
+
15
+ # assume we're at debug level
16
+ def self.get_debug_level
17
+ @debug_level ||= ZOO_LOG_LEVEL_INFO
18
+ end
19
+
20
+ def self.set_debug_level(value)
21
+ @debug_level = value
22
+ set_zkrb_debug_level(value)
23
+ end
24
+
25
+ def initialize(host, event_queue, opts={})
26
+ @host = host
27
+ @event_queue = event_queue
28
+
29
+ # used by the C layer. CZookeeper sets this to true when the init method
30
+ # has completed. once this is set to true, it stays true.
31
+ #
32
+ # you should grab the @start_stop_mutex before messing with this flag
33
+ @_running = nil
34
+
35
+ # This is set to true after destroy_zkrb_instance has been called and all
36
+ # CZookeeper state has been cleaned up
37
+ @_closed = false # also used by the C layer
38
+
39
+ # set by the ruby side to indicate we are in shutdown mode used by method_get_next_event
40
+ @_shutting_down = false
41
+
42
+ # the actual C data is stashed in this ivar. never *ever* touch this
43
+ @_data = nil
44
+
45
+ @_session_timeout_msec = DEFAULT_SESSION_TIMEOUT_MSEC
46
+
47
+ @start_stop_mutex = Monitor.new
48
+
49
+ # used to signal that we're running
50
+ @running_cond = @start_stop_mutex.new_cond
51
+
52
+ @event_thread = nil
53
+
54
+ setup_event_thread!
55
+
56
+ zkrb_init(@host)
57
+
58
+ logger.debug { "init returned!" }
59
+ end
60
+
61
+ def closed?
62
+ @start_stop_mutex.synchronize { !!@_closed }
63
+ end
64
+
65
+ def running?
66
+ @start_stop_mutex.synchronize { !!@_running }
67
+ end
68
+
69
+ def shutting_down?
70
+ @start_stop_mutex.synchronize { !!@_shutting_down }
71
+ end
72
+
73
+ def connected?
74
+ state == ZOO_CONNECTED_STATE
75
+ end
76
+
77
+ def connecting?
78
+ state == ZOO_CONNECTING_STATE
79
+ end
80
+
81
+ def associating?
82
+ state == ZOO_ASSOCIATING_STATE
83
+ end
84
+
85
+ def close
86
+ return if closed?
87
+
88
+ shut_down!
89
+ stop_event_thread!
90
+
91
+ @start_stop_mutex.synchronize do
92
+ if !@_closed and @_data
93
+ close_handle
94
+ end
95
+ end
96
+
97
+ nil
98
+ end
99
+
100
+ def state
101
+ return ZOO_CLOSED_STATE if closed?
102
+ zkrb_state
103
+ end
104
+
105
+ # this implementation is gross, but i don't really see another way of doing it
106
+ # without more grossness
107
+ #
108
+ # returns true if we're connected, false if we're not
109
+ #
110
+ # if timeout is nil, we never time out, and wait forever for CONNECTED state
111
+ #
112
+ def wait_until_connected(timeout=10)
113
+ return false unless wait_until_running(timeout)
114
+
115
+ time_to_stop = timeout ? (Time.now + timeout) : nil
116
+
117
+ until connected? or (time_to_stop and Time.now > time_to_stop)
118
+ Thread.pass
119
+ end
120
+
121
+ connected?
122
+ end
123
+
124
+ private
125
+ # will wait until the client has entered the running? state
126
+ # or until timeout seconds have passed.
127
+ #
128
+ # returns true if we're running, false if we timed out
129
+ def wait_until_running(timeout=5)
130
+ @start_stop_mutex.synchronize do
131
+ return true if @_running
132
+ @running_cond.wait(timeout)
133
+ !!@_running
134
+ end
135
+ end
136
+
137
+ def setup_event_thread!
138
+ @event_thread ||= Thread.new do
139
+ Thread.current.abort_on_exception = true # remove this once this is confirmed to work
140
+
141
+ logger.debug { "event_thread waiting until running: #{@_running}" }
142
+
143
+ @start_stop_mutex.synchronize do
144
+ @running_cond.wait_until { @_running }
145
+
146
+ if @_shutting_down
147
+ logger.error { "event thread saw @_shutting_down, bailing without entering loop" }
148
+ return
149
+ end
150
+ end
151
+
152
+ logger.debug { "event_thread running: #{@_running}" }
153
+
154
+ while true
155
+ begin
156
+ _iterate_event_delivery
157
+ rescue GotNilEventException
158
+ logger.debug { "#{self.class}##{__method__}: event delivery thread is exiting" }
159
+ break
160
+ end
161
+ end
162
+
163
+ # TODO: should we try iterating events after this point? to see if any are left?
164
+ end
165
+ end
166
+
167
+ def _iterate_event_delivery
168
+ get_next_event(true).tap do |hash|
169
+ raise GotNilEventException if hash.nil?
170
+ @event_queue.push(hash)
171
+ end
172
+ end
173
+
174
+ # use this method to set the @_shutting_down flag to true
175
+ def shut_down!
176
+ logger.debug { "#{self.class}##{__method__}" }
177
+
178
+ @start_stop_mutex.synchronize do
179
+ @_shutting_down = true
180
+ end
181
+ end
182
+
183
+ # this method is part of the reopen/close code, and is responsible for
184
+ # shutting down the dispatch thread.
185
+ #
186
+ # @dispatch will be nil when this method exits
187
+ #
188
+ def stop_event_thread!
189
+ logger.debug { "#{self.class}##{__method__}" }
190
+
191
+ if @event_thread
192
+ unless @_closed
193
+ wake_event_loop! # this is a C method
194
+ end
195
+ @event_thread.join
196
+ @event_thread = nil
197
+ end
198
+ end
199
+
200
+ def logger
201
+ Zookeeper.logger
202
+ end
203
+
204
+ # called by underlying C code to signal we're running
205
+ def zkc_set_running_and_notify!
206
+ logger.debug { "#{self.class}##{__method__}" }
207
+
208
+ @start_stop_mutex.synchronize do
209
+ @_running = true
210
+ @running_cond.broadcast
211
+ end
212
+ end
213
+ end
214
+
data/ext/dbg.h CHANGED
@@ -22,16 +22,32 @@
22
22
 
23
23
  #define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
24
24
 
25
+ // acts to assert that A is true
25
26
  #define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }
26
27
 
28
+ // like check, but provide an explicit goto label name
29
+ #define check_goto(A, L, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto L; }
30
+
31
+ // like check, but implicit jump to 'unlock' label
32
+ #define check_unlock(A, M, ...) check_goto(A, unlock, M, ##__VA_ARGS__)
33
+
27
34
  #define sentinel(M, ...) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }
28
35
 
29
36
  #define check_mem(A) check((A), "Out of memory.")
30
37
 
31
- #define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }
38
+ // checks the condition A, if not true, logs the message M given using zkrb_debug
39
+ // then does a goto to the label L
40
+ #define check_debug_goto(A, L, M, ...) if(!(A)) { zkrb_debug(M, ##__VA_ARGS__); errno=0; goto L; }
41
+
42
+ // check_debug_goto with implicit 'unlock' label
43
+ #define check_debug_unlock(A, M, ...) check_debug_goto(A, unlock, M, ##__VA_ARGS__)
44
+
45
+ // like check_debug_goto, but the label is implicitly 'error'
46
+ #define check_debug(A, M, ...) check_debug_goto(A, error, M, ##__VA_ARGS__)
32
47
 
33
- #define zkrb_debug(M, ...) if (ZKRBDebugging) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
48
+ #define zkrb_debug(M, ...) if (ZKRBDebugging) fprintf(stderr, "DEBUG %p:%s:%d: " M "\n", pthread_self(), __FILE__, __LINE__, ##__VA_ARGS__)
34
49
  #define zkrb_debug_inst(O, M, ...) zkrb_debug("obj_id: %lx, " M, FIX2LONG(rb_obj_id(O)), ##__VA_ARGS__)
35
50
 
51
+ // __dbg_h__
36
52
  #endif
37
53
 
data/ext/depend CHANGED
@@ -1,4 +1,3 @@
1
- zookeeper_c.c: zookeeper_lib.c dbg.h
2
-
3
1
  zookeeper_lib.c: zookeeper_lib.h
2
+ zookeeper_c.c: zookeeper_lib.c zookeeper_lib.h dbg.h
4
3
 
@@ -1,6 +1,10 @@
1
+ require File.expand_path('../c_zookeeper', __FILE__)
2
+ require 'forwardable'
3
+
1
4
  # The low-level wrapper-specific methods for the C lib
2
5
  # subclassed by the top-level Zookeeper class
3
- class ZookeeperBase < CZookeeper
6
+ class ZookeeperBase
7
+ extend Forwardable
4
8
  include ZookeeperCommon
5
9
  include ZookeeperCallbacks
6
10
  include ZookeeperConstants
@@ -8,6 +12,11 @@ class ZookeeperBase < CZookeeper
8
12
  include ZookeeperACLs
9
13
  include ZookeeperStat
10
14
 
15
+ # @private
16
+ class ClientShutdownException < StandardError; end
17
+
18
+ # @private
19
+ KILL_TOKEN = Object.new unless defined?(KILL_TOKEN)
11
20
 
12
21
  ZKRB_GLOBAL_CB_REQ = -1
13
22
 
@@ -17,38 +26,57 @@ class ZookeeperBase < CZookeeper
17
26
  ZOO_LOG_LEVEL_INFO = 3
18
27
  ZOO_LOG_LEVEL_DEBUG = 4
19
28
 
29
+ def_delegators :czk,
30
+ :get_children, :exists, :delete, :get, :set, :set_acl, :get_acl, :client_id, :sync, :selectable_io
31
+
32
+ # some state methods need to be more paranoid about locking to ensure the correct
33
+ # state is returned
34
+ #
35
+ def self.threadsafe_inquisitor(*syms)
36
+ syms.each do |sym|
37
+ class_eval(<<-EOM, __FILE__, __LINE__+1)
38
+ def #{sym}
39
+ false|@mutex.synchronize { @czk and @czk.#{sym} }
40
+ end
41
+ EOM
42
+ end
43
+ end
44
+
45
+ threadsafe_inquisitor :connected?, :connecting?, :associating?, :running?
46
+
47
+ attr_reader :event_queue
20
48
 
21
49
  def reopen(timeout = 10, watcher=nil)
22
- watcher ||= @default_watcher
23
-
24
- @req_mutex.synchronize do
25
- # flushes all outstanding watcher reqs.
26
- @watcher_req = {}
27
- set_default_global_watcher(&watcher)
50
+ if watcher and (watcher != @default_watcher)
51
+ raise "You cannot set the watcher to a different value this way anymore!"
28
52
  end
53
+
54
+ @mutex.synchronize do
55
+ # flushes all outstanding watcher reqs.
56
+ @watcher_reqs.clear
57
+ set_default_global_watcher
29
58
 
30
- @start_stop_mutex.synchronize do
31
- # $stderr.puts "%s: calling init, self.obj_id: %x" % [self.class, object_id]
32
- init(@host)
59
+ orig_czk, @czk = @czk, CZookeeper.new(@host, @event_queue)
33
60
 
34
- # XXX: replace this with a callback
35
- if timeout > 0
36
- time_to_stop = Time.now + timeout
37
- until state == Zookeeper::ZOO_CONNECTED_STATE
38
- break if Time.now > time_to_stop
39
- sleep 0.1
40
- end
41
- end
61
+ orig_czk.close if orig_czk
62
+
63
+ @czk.wait_until_connected(timeout)
42
64
  end
43
65
 
66
+ setup_dispatch_thread!
44
67
  state
45
68
  end
46
69
 
47
70
  def initialize(host, timeout = 10, watcher=nil)
48
71
  @watcher_reqs = {}
49
72
  @completion_reqs = {}
50
- @req_mutex = Monitor.new
51
- @current_req_id = 1
73
+
74
+ @mutex = Monitor.new
75
+ @dispatch_shutdown_cond = @mutex.new_cond
76
+
77
+ @current_req_id = 0
78
+ @event_queue = QueueWithPipe.new
79
+ @czk = nil
52
80
 
53
81
  # approximate the java behavior of raising java.lang.IllegalArgumentException if the host
54
82
  # argument ends with '/'
@@ -56,18 +84,17 @@ class ZookeeperBase < CZookeeper
56
84
 
57
85
  @host = host
58
86
 
59
- @start_stop_mutex = Monitor.new
60
-
61
- watcher ||= get_default_global_watcher
62
-
63
- @_running = nil # used by the C layer
64
- @_closed = false # also used by the C layer
87
+ @default_watcher = (watcher or get_default_global_watcher)
65
88
 
66
89
  yield self if block_given?
67
90
 
68
- reopen(timeout, watcher)
69
- return nil unless connected?
70
- setup_dispatch_thread!
91
+ reopen(timeout)
92
+ end
93
+
94
+ # synchronized accessor to the @czk instance
95
+ # @private
96
+ def czk
97
+ @mutex.synchronize { @czk }
71
98
  end
72
99
 
73
100
  # if either of these happen, the user will need to renegotiate a connection via reopen
@@ -76,41 +103,10 @@ class ZookeeperBase < CZookeeper
76
103
  raise ZookeeperException::NotConnected unless connected?
77
104
  end
78
105
 
79
- def connected?
80
- state == ZOO_CONNECTED_STATE
81
- end
82
-
83
- def connecting?
84
- state == ZOO_CONNECTING_STATE
85
- end
86
-
87
- def associating?
88
- state == ZOO_ASSOCIATING_STATE
89
- end
90
-
91
106
  def close
92
- @start_stop_mutex.synchronize do
93
- @_running = false if @_running
94
- end
95
-
96
- if @dispatcher
97
- unless @_closed
98
- wake_event_loop!
99
- end
100
- @dispatcher.join
101
- end
102
-
103
- @start_stop_mutex.synchronize do
104
- unless @_closed
105
- close_handle
106
- end
107
- end
108
-
109
- # this is set up in the C init method, but it's easier to
110
- # do the teardown here
111
- begin
112
- @selectable_io.close if @selectable_io
113
- rescue IOError
107
+ @mutex.synchronize do
108
+ stop_dispatch_thread!
109
+ @czk.close
114
110
  end
115
111
  end
116
112
 
@@ -118,7 +114,7 @@ class ZookeeperBase < CZookeeper
118
114
  # is pretty damn annoying. this is used to clean things up.
119
115
  def create(*args)
120
116
  # since we don't care about the inputs, just glob args
121
- rc, new_path = super(*args)
117
+ rc, new_path = czk.create(*args)
122
118
  [rc, strip_chroot_from(new_path)]
123
119
  end
124
120
 
@@ -128,34 +124,33 @@ class ZookeeperBase < CZookeeper
128
124
  end
129
125
 
130
126
  # set the watcher object/proc that will receive all global events (such as session/state events)
131
- def set_default_global_watcher(&block)
132
- @req_mutex.synchronize do
133
- @default_watcher = block # save this here for reopen() to use
127
+ def set_default_global_watcher
128
+ warn "DEPRECATION WARNING: #{self.class}#set_default_global_watcher ignores block" if block_given?
129
+
130
+ @mutex.synchronize do
131
+ # @default_watcher = block # save this here for reopen() to use
134
132
  @watcher_reqs[ZKRB_GLOBAL_CB_REQ] = { :watcher => @default_watcher, :watcher_context => nil }
135
133
  end
136
134
  end
137
135
 
138
- def closed?
139
- @start_stop_mutex.synchronize { false|@_closed }
140
- end
141
-
142
- def running?
143
- @start_stop_mutex.synchronize { false|@_running }
144
- end
145
-
146
136
  def state
147
137
  return ZOO_CLOSED_STATE if closed?
148
- super
138
+ czk.state
149
139
  end
150
140
 
151
141
  def session_id
152
- client_id.session_id
142
+ cid = client_id and cid.session_id
153
143
  end
154
144
 
155
145
  def session_passwd
156
- client_id.passwd
146
+ cid = client_id and cid.passwd
157
147
  end
158
148
 
149
+ # we are closed if there is no @czk instance or @czk.closed?
150
+ def closed?
151
+ @mutex.synchronize { !@czk or @czk.closed? }
152
+ end
153
+
159
154
  protected
160
155
  # this is a hack: to provide consistency between the C and Java drivers when
161
156
  # using a chrooted connection, we wrap the callback in a block that will
@@ -164,7 +159,6 @@ protected
164
159
  # version. The non-async manipulation is handled in ZookeeperBase#create.
165
160
  #
166
161
  def setup_completion(req_id, meth_name, call_opts)
167
-
168
162
  if (meth_name == :create) and cb = call_opts[:callback]
169
163
  call_opts[:callback] = lambda do |hash|
170
164
  # in this case the string will be the absolute zookeeper path (i.e.
@@ -186,26 +180,6 @@ protected
186
180
  path[chroot_path.length..-1]
187
181
  end
188
182
 
189
- def barf_unless_running!
190
- @start_stop_mutex.synchronize do
191
- raise ShuttingDownException unless (@_running and not @_closed)
192
- yield
193
- end
194
- end
195
-
196
- def setup_dispatch_thread!
197
- @dispatcher = Thread.new do
198
- while running?
199
- begin # calling user code, so protect ourselves
200
- dispatch_next_callback
201
- rescue Exception => e
202
- $stderr.puts "Error in dispatch thread, #{e.class}: #{e.message}\n" << e.backtrace.map{|n| "\t#{n}"}.join("\n")
203
- end
204
- end
205
- end
206
- end
207
-
208
- # TODO: Make all global puts configurable
209
183
  def get_default_global_watcher
210
184
  Proc.new { |args|
211
185
  logger.debug { "Ruby ZK Global CB called type=#{event_by_value(args[:type])} state=#{state_by_value(args[:state])}" }