slyphon-zookeeper 0.3.0-java → 0.8.0-java

Sign up to get free protection for your applications and to get access to all the features.
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])}" }