slyphon-zookeeper 0.3.0 → 0.8.0.rc.1

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/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,54 @@ class ZookeeperBase < CZookeeper
17
26
  ZOO_LOG_LEVEL_INFO = 3
18
27
  ZOO_LOG_LEVEL_DEBUG = 4
19
28
 
20
-
21
- def reopen(timeout = 10, watcher=nil)
22
- watcher ||= @default_watcher
29
+ def_delegators :czk,
30
+ :get_children, :exists, :delete, :get, :set, :set_acl, :get_acl, :client_id, :sync, :selectable_io
23
31
 
24
- @req_mutex.synchronize do
25
- # flushes all outstanding watcher reqs.
26
- @watcher_req = {}
27
- set_default_global_watcher(&watcher)
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
28
42
  end
43
+ end
29
44
 
30
- @start_stop_mutex.synchronize do
31
- # $stderr.puts "%s: calling init, self.obj_id: %x" % [self.class, object_id]
32
- init(@host)
45
+ threadsafe_inquisitor :connected?, :connecting?, :associating?, :running?
33
46
 
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
47
+ attr_reader :event_queue
48
+
49
+ def reopen(timeout = 10, watcher=nil)
50
+ if watcher and (watcher != @default_watcher)
51
+ raise "You cannot set the watcher to a different value this way anymore!"
52
+ end
53
+
54
+ @mutex.synchronize do
55
+ # flushes all outstanding watcher reqs.
56
+ @watcher_reqs.clear
57
+ set_default_global_watcher
58
+
59
+ @czk = CZookeeper.new(@host, @event_queue)
60
+ @czk.wait_until_connected(timeout)
42
61
  end
43
62
 
63
+ setup_dispatch_thread!
44
64
  state
45
65
  end
46
66
 
47
67
  def initialize(host, timeout = 10, watcher=nil)
48
68
  @watcher_reqs = {}
49
69
  @completion_reqs = {}
50
- @req_mutex = Monitor.new
51
- @current_req_id = 1
70
+
71
+ @mutex = Monitor.new
72
+ @dispatch_shutdown_cond = @mutex.new_cond
73
+
74
+ @current_req_id = 0
75
+ @event_queue = QueueWithPipe.new
76
+ @czk = nil
52
77
 
53
78
  # approximate the java behavior of raising java.lang.IllegalArgumentException if the host
54
79
  # argument ends with '/'
@@ -56,18 +81,17 @@ class ZookeeperBase < CZookeeper
56
81
 
57
82
  @host = host
58
83
 
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
84
+ @default_watcher = (watcher or get_default_global_watcher)
65
85
 
66
86
  yield self if block_given?
67
87
 
68
- reopen(timeout, watcher)
69
- return nil unless connected?
70
- setup_dispatch_thread!
88
+ reopen(timeout)
89
+ end
90
+
91
+ # synchronized accessor to the @czk instance
92
+ # @private
93
+ def czk
94
+ @mutex.synchronize { @czk }
71
95
  end
72
96
 
73
97
  # if either of these happen, the user will need to renegotiate a connection via reopen
@@ -76,41 +100,10 @@ class ZookeeperBase < CZookeeper
76
100
  raise ZookeeperException::NotConnected unless connected?
77
101
  end
78
102
 
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
103
  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
104
+ @mutex.synchronize do
105
+ stop_dispatch_thread!
106
+ @czk.close
114
107
  end
115
108
  end
116
109
 
@@ -118,7 +111,7 @@ class ZookeeperBase < CZookeeper
118
111
  # is pretty damn annoying. this is used to clean things up.
119
112
  def create(*args)
120
113
  # since we don't care about the inputs, just glob args
121
- rc, new_path = super(*args)
114
+ rc, new_path = czk.create(*args)
122
115
  [rc, strip_chroot_from(new_path)]
123
116
  end
124
117
 
@@ -128,34 +121,33 @@ class ZookeeperBase < CZookeeper
128
121
  end
129
122
 
130
123
  # 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
124
+ def set_default_global_watcher
125
+ warn "DEPRECATION WARNING: #{self.class}#set_default_global_watcher ignores block" if block_given?
126
+
127
+ @mutex.synchronize do
128
+ # @default_watcher = block # save this here for reopen() to use
134
129
  @watcher_reqs[ZKRB_GLOBAL_CB_REQ] = { :watcher => @default_watcher, :watcher_context => nil }
135
130
  end
136
131
  end
137
132
 
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
133
  def state
147
134
  return ZOO_CLOSED_STATE if closed?
148
- super
135
+ czk.state
149
136
  end
150
137
 
151
138
  def session_id
152
- client_id.session_id
139
+ cid = client_id and cid.session_id
153
140
  end
154
141
 
155
142
  def session_passwd
156
- client_id.passwd
143
+ cid = client_id and cid.passwd
157
144
  end
158
145
 
146
+ # we are closed if there is no @czk instance or @czk.closed?
147
+ def closed?
148
+ @mutex.synchronize { !@czk or @czk.closed? }
149
+ end
150
+
159
151
  protected
160
152
  # this is a hack: to provide consistency between the C and Java drivers when
161
153
  # using a chrooted connection, we wrap the callback in a block that will
@@ -164,7 +156,6 @@ protected
164
156
  # version. The non-async manipulation is handled in ZookeeperBase#create.
165
157
  #
166
158
  def setup_completion(req_id, meth_name, call_opts)
167
-
168
159
  if (meth_name == :create) and cb = call_opts[:callback]
169
160
  call_opts[:callback] = lambda do |hash|
170
161
  # in this case the string will be the absolute zookeeper path (i.e.
@@ -186,26 +177,6 @@ protected
186
177
  path[chroot_path.length..-1]
187
178
  end
188
179
 
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
180
  def get_default_global_watcher
210
181
  Proc.new { |args|
211
182
  logger.debug { "Ruby ZK Global CB called type=#{event_by_value(args[:type])} state=#{state_by_value(args[:state])}" }