zookeeper 1.2.4 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,24 @@
1
+ v1.2.5 cleanup locking in ZookeeperBase
2
+
3
+ * There were several situations where we would hold the lock before calling
4
+ a method on CZookeeper (inquisitors and #create in particular). This
5
+ exposed us to deadlocks in situations where an async event would be
6
+ delivered (probably a SESSION_EXPIRED event), but the callback block could
7
+ not be called because the dispatch thread would block on the mutex being
8
+ held by the caller of create.
9
+
10
+ This version cleans up that usage, and ensures that the only time we hold
11
+ the mutex is during startup/shutdown (when the value of @czk may be changing),
12
+ and in all other cases we grab the mutex, dereference, and unlock then perform
13
+ whatever action on the reference.
14
+
15
+ * Add a safety net to Continuation (which will soon be called 'Promise' or
16
+ 'Future' at the request of @eric). If any operation takes more than 30s
17
+ an exception will be raised in the calling thread. The session timeout
18
+ setting makes it so that no operation should take more than 20s, so we know
19
+ if we haven't received a reply in *longer* than that, something has gone
20
+ awry.
21
+
1
22
  v1.2.4 fix buffer overflow in CZookeeper client_id code
2
23
 
3
24
  * the 'passwd' part of the struct is a char[16], but isn't null terminated.
data/ext/c_zookeeper.rb CHANGED
@@ -176,6 +176,7 @@ class CZookeeper
176
176
  return false unless wait_until_running(timeout)
177
177
 
178
178
  @mutex.synchronize do
179
+ # TODO: use deadline here
179
180
  @state_cond.wait(timeout) unless (@state == ZOO_CONNECTED_STATE)
180
181
  end
181
182
 
@@ -191,15 +192,12 @@ class CZookeeper
191
192
  end
192
193
 
193
194
  cnt = Continuation.new(meth, *args)
194
- @reg.lock
195
- begin
195
+ @reg.synchronize do |r|
196
196
  if meth == :state
197
- @reg.state_check << cnt
197
+ r.state_check << cnt
198
198
  else
199
- @reg.pending << cnt
199
+ r.pending << cnt
200
200
  end
201
- ensure
202
- @reg.unlock rescue nil
203
201
  end
204
202
  wake_event_loop!
205
203
  cnt.value
data/ext/extconf.rb CHANGED
@@ -42,6 +42,11 @@ $LDFLAGS = "#{$libraries} #{$LDFLAGS}"
42
42
  $LIBPATH = ["#{HERE}/lib"]
43
43
  $DEFLIBPATH = []
44
44
 
45
+ # fix for rbenv?
46
+ if $LIBRUBYARG == ''
47
+ $LIBRUBYARG = RbConfig::CONFIG['LIBRUBYARG']
48
+ end
49
+
45
50
  def safe_sh(cmd)
46
51
  puts cmd
47
52
  system(cmd)
@@ -87,6 +92,8 @@ Dir.chdir("#{HERE}/lib") do
87
92
  end
88
93
  $LIBS << " -lzookeeper_st_gem"
89
94
 
95
+ have_func('rb_thread_blocking_region')
96
+
90
97
  $CFLAGS << ' -Wall' if ZK_DEV
91
98
  create_makefile 'zookeeper_c'
92
99
 
data/ext/zkrb.c CHANGED
@@ -824,7 +824,6 @@ static VALUE method_zkrb_iterate_event_loop(VALUE self) {
824
824
  return INT2FIX(rc);
825
825
  }
826
826
 
827
-
828
827
  static VALUE method_has_events(VALUE self) {
829
828
  VALUE rb_event;
830
829
  FETCH_DATA_PTR(self, zk);
@@ -882,16 +881,8 @@ static VALUE method_recv_timeout(VALUE self) {
882
881
  // returns a CZookeeper::ClientId object with the values set for session_id and passwd
883
882
  static VALUE method_client_id(VALUE self) {
884
883
  FETCH_DATA_PTR(self, zk);
885
- char buf[32];
886
884
  const clientid_t *cid = zoo_client_id(zk->zh);
887
885
 
888
- /* if (strlen(cid->passwd) != 16) { */
889
- /* zkrb_debug("passwd is not null-terminated");*/
890
- /* } else {*/
891
- /* hexbufify(buf, cid->passwd, 16);*/
892
- /* zkrb_debug("password in hex is: %s", buf);*/
893
- /* }*/
894
-
895
886
  VALUE session_id = LL2NUM(cid->client_id);
896
887
  VALUE passwd = rb_str_new(cid->passwd, 16);
897
888
 
@@ -4,10 +4,10 @@
4
4
 
5
5
  VALUE zkrb_thread_blocking_region(zkrb_blocking_function_t *func, void *data1) {
6
6
 
7
- #ifdef ZKRB_RUBY_187
8
- return func(data1);
9
- #else
7
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
10
8
  return rb_thread_blocking_region((rb_blocking_function_t *)func, data1, RUBY_UBF_IO, 0);
9
+ #else
10
+ return func(data1);
11
11
  #endif
12
12
 
13
13
  }
@@ -31,17 +31,15 @@ class ZookeeperBase
31
31
  ZOO_LOG_LEVEL_DEBUG = 4
32
32
 
33
33
 
34
- def_delegators :@czk, :get_children, :exists, :delete, :get, :set,
34
+ def_delegators :czk, :get_children, :exists, :delete, :get, :set,
35
35
  :set_acl, :get_acl, :client_id, :sync, :wait_until_connected
36
36
 
37
- # some state methods need to be more paranoid about locking to ensure the correct
38
- # state is returned
39
- #
40
37
  def self.threadsafe_inquisitor(*syms)
41
38
  syms.each do |sym|
42
39
  class_eval(<<-EOM, __FILE__, __LINE__+1)
43
40
  def #{sym}
44
- false|@mutex.synchronize { @czk and @czk.#{sym} }
41
+ c = @mutex.synchronize { @czk }
42
+ false|(c && c.#{sym})
45
43
  end
46
44
  EOM
47
45
  end
@@ -117,7 +115,7 @@ class ZookeeperBase
117
115
  # if either of these happen, the user will need to renegotiate a connection via reopen
118
116
  def assert_open
119
117
  @mutex.synchronize do
120
- raise Exceptions::NotConnected if closed?
118
+ raise Exceptions::NotConnected if !@czk or @czk.closed?
121
119
  if forked?
122
120
  raise InheritedConnectionError, <<-EOS.gsub(/(?:^|\n)\s*/, ' ').strip
123
121
  You tried to use a connection inherited from another process
@@ -152,7 +150,7 @@ class ZookeeperBase
152
150
  # is pretty damn annoying. this is used to clean things up.
153
151
  def create(*args)
154
152
  # since we don't care about the inputs, just glob args
155
- rc, new_path = @mutex.synchronize { @czk.create(*args) }
153
+ rc, new_path = czk.create(*args)
156
154
  [rc, strip_chroot_from(new_path)]
157
155
  end
158
156
 
@@ -173,7 +171,7 @@ class ZookeeperBase
173
171
 
174
172
  def state
175
173
  return ZOO_CLOSED_STATE if closed?
176
- @mutex.synchronize { @czk.state }
174
+ czk.state
177
175
  end
178
176
 
179
177
  def session_id
@@ -190,7 +188,9 @@ class ZookeeperBase
190
188
 
191
189
  # we are closed if there is no @czk instance or @czk.closed?
192
190
  def closed?
193
- @mutex.synchronize { !@czk or @czk.closed? }
191
+ czk.closed?
192
+ rescue Exceptions::NotConnected
193
+ true
194
194
  end
195
195
 
196
196
  def pause_before_fork_in_parent
@@ -243,6 +243,12 @@ protected
243
243
  super(req_id, meth_name, call_opts)
244
244
  end
245
245
 
246
+ def czk
247
+ rval = @mutex.synchronize { @czk }
248
+ raise Exceptions::NotConnected unless rval
249
+ rval
250
+ end
251
+
246
252
  # if we're chrooted, this method will strip the chroot prefix from +path+
247
253
  def strip_chroot_from(path)
248
254
  return path unless (chrooted? and path and path.start_with?(chroot_path))
@@ -6,6 +6,8 @@ module Zookeeper
6
6
  include Constants
7
7
  include Logger
8
8
 
9
+ OPERATION_TIMEOUT = 30 # seconds
10
+
9
11
  # for keeping track of which continuations are pending, and which ones have
10
12
  # been submitted and are awaiting a repsonse
11
13
  #
@@ -79,8 +81,8 @@ module Zookeeper
79
81
  def initialize(meth, *args)
80
82
  @meth = meth
81
83
  @args = args
82
- @mutex = Mutex.new
83
- @cond = ConditionVariable.new
84
+ @mutex = Monitor.new
85
+ @cond = @mutex.new_cond
84
86
  @rval = nil
85
87
 
86
88
  # make this error reporting more robust if necessary, right now, just set to state
@@ -88,9 +90,28 @@ module Zookeeper
88
90
  end
89
91
 
90
92
  # the caller calls this method and receives the response from the async loop
93
+ # this method has a hard-coded 30 second timeout as a safety feature. No
94
+ # call should take more than 20s (as the session timeout is set to 20s)
95
+ # so if any call takes longer than that, something has gone horribly wrong.
96
+ #
97
+ # @raise [ContinuationTimeoutError] if a response is not received within 30s
98
+ #
91
99
  def value
100
+ time_to_stop = Time.now + OPERATION_TIMEOUT
101
+ now = nil
102
+
92
103
  @mutex.synchronize do
93
- @cond.wait(@mutex) until @rval or @error
104
+ while true
105
+ now = Time.now
106
+ break if @rval or @error or (now > time_to_stop)
107
+
108
+ deadline = time_to_stop.to_f - now.to_f
109
+ @cond.wait(deadline)
110
+ end
111
+
112
+ if now > time_to_stop
113
+ raise ContinuationTimeoutError, "response for meth: #{meth.inspect}, args: #{args.inspect}, not received within #{OPERATION_TIMEOUT} seconds"
114
+ end
94
115
 
95
116
  case @error
96
117
  when nil
@@ -84,6 +84,10 @@ stacktrace:
84
84
  # maybe use this for continuation
85
85
  class InterruptedException < ZookeeperException ; end
86
86
 
87
+ # raised when a continuation operation takes more time than is reasonable and
88
+ # the thread should be awoken. (i.e. prevents a call that never returns)
89
+ class ContinuationTimeoutError < ZookeeperException; end
90
+
87
91
  # raised when the user tries to use a connection after a fork()
88
92
  # without calling reopen() in the C client
89
93
  #
@@ -1,4 +1,4 @@
1
1
  module Zookeeper
2
- VERSION = '1.2.4'
2
+ VERSION = '1.2.5'
3
3
  DRIVER_VERSION = '3.3.5'
4
4
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zookeeper
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 2
9
- - 4
10
- version: 1.2.4
9
+ - 5
10
+ version: 1.2.5
11
11
  platform: ruby
12
12
  authors:
13
13
  - Phillip Pearson
@@ -20,7 +20,7 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2012-05-24 00:00:00 Z
23
+ date: 2012-06-03 00:00:00 Z
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: backports