zk 0.9.1 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +2 -2
  2. data/Gemfile +3 -4
  3. data/README.markdown +14 -5
  4. data/RELEASES.markdown +69 -1
  5. data/Rakefile +50 -18
  6. data/docs/examples/block_until_node_deleted_ex.rb +63 -0
  7. data/docs/examples/events_01.rb +36 -0
  8. data/docs/examples/events_02.rb +41 -0
  9. data/lib/{z_k → zk}/client/base.rb +87 -30
  10. data/lib/{z_k → zk}/client/conveniences.rb +0 -1
  11. data/lib/{z_k → zk}/client/state_mixin.rb +0 -0
  12. data/lib/zk/client/threaded.rb +196 -0
  13. data/lib/{z_k → zk}/client/unixisms.rb +0 -0
  14. data/lib/zk/client.rb +59 -0
  15. data/lib/zk/core_ext.rb +75 -0
  16. data/lib/{z_k → zk}/election.rb +0 -0
  17. data/lib/zk/event.rb +168 -0
  18. data/lib/{z_k → zk}/event_handler.rb +53 -28
  19. data/lib/zk/event_handler_subscription.rb +68 -0
  20. data/lib/{z_k → zk}/exceptions.rb +38 -23
  21. data/lib/zk/extensions.rb +79 -0
  22. data/lib/{z_k → zk}/find.rb +0 -0
  23. data/lib/{z_k → zk}/locker.rb +0 -0
  24. data/lib/{z_k → zk}/logging.rb +0 -0
  25. data/lib/{z_k → zk}/message_queue.rb +8 -4
  26. data/lib/{z_k → zk}/mongoid.rb +0 -0
  27. data/lib/{z_k → zk}/pool.rb +0 -0
  28. data/lib/zk/stat.rb +115 -0
  29. data/lib/{z_k → zk}/threadpool.rb +52 -4
  30. data/lib/zk/version.rb +3 -0
  31. data/lib/zk.rb +238 -1
  32. data/spec/message_queue_spec.rb +2 -2
  33. data/spec/shared/client_contexts.rb +8 -20
  34. data/spec/shared/client_examples.rb +136 -2
  35. data/spec/spec_helper.rb +4 -2
  36. data/spec/support/event_catcher.rb +11 -0
  37. data/spec/support/exist_matcher.rb +6 -0
  38. data/spec/support/logging.rb +2 -1
  39. data/spec/watch_spec.rb +194 -10
  40. data/spec/{z_k → zk}/client/locking_and_session_death_spec.rb +0 -32
  41. data/spec/zk/client_spec.rb +23 -0
  42. data/spec/{z_k → zk}/election_spec.rb +0 -0
  43. data/spec/{z_k → zk}/extensions_spec.rb +0 -0
  44. data/spec/{z_k → zk}/locker_spec.rb +0 -40
  45. data/spec/zk/module_spec.rb +185 -0
  46. data/spec/{z_k → zk}/mongoid_spec.rb +0 -2
  47. data/spec/{z_k → zk}/pool_spec.rb +0 -2
  48. data/spec/{z_k → zk}/threadpool_spec.rb +32 -4
  49. data/spec/zookeeper_spec.rb +1 -6
  50. data/zk.gemspec +2 -2
  51. metadata +64 -56
  52. data/lib/z_k/client/continuation_proxy.rb +0 -109
  53. data/lib/z_k/client/drop_box.rb +0 -98
  54. data/lib/z_k/client/multiplexed.rb +0 -28
  55. data/lib/z_k/client/threaded.rb +0 -76
  56. data/lib/z_k/client.rb +0 -35
  57. data/lib/z_k/event_handler_subscription.rb +0 -36
  58. data/lib/z_k/extensions.rb +0 -155
  59. data/lib/z_k/version.rb +0 -3
  60. data/lib/z_k.rb +0 -97
  61. data/spec/z_k/client/drop_box_spec.rb +0 -90
  62. data/spec/z_k/client/multiplexed_spec.rb +0 -20
  63. data/spec/z_k/client_spec.rb +0 -7
@@ -0,0 +1,68 @@
1
+ module ZK
2
+ # the subscription object that is passed back from subscribing
3
+ # to events.
4
+ # @see ZK::Client::Base#register
5
+ class EventHandlerSubscription
6
+ # the event handler associated with this subscription
7
+ # @return [EventHandler]
8
+ attr_accessor :event_handler
9
+
10
+ # the path this subscription is for
11
+ # @return [String]
12
+ attr_accessor :path
13
+
14
+ # the block associated with the path
15
+ # @return [Proc]
16
+ attr_accessor :callback
17
+
18
+ # an array of what kinds of events this handler is interested in receiving
19
+ #
20
+ # @return [Set] containing any combination of :create, :change, :delete,
21
+ # or :children
22
+ #
23
+ # @private
24
+ attr_accessor :interests
25
+
26
+ ALL_EVENTS = [:created, :deleted, :changed, :child].freeze unless defined?(ALL_EVENTS)
27
+ ALL_EVENT_SET = Set.new(ALL_EVENTS).freeze unless defined?(ALL_EVENT_SET)
28
+
29
+ # @private
30
+ def initialize(event_handler, path, callback, interests)
31
+ @event_handler, @path, @callback = event_handler, path, callback
32
+ @interests = prep_interests(interests)
33
+ end
34
+
35
+ # unsubscribe from the path or state you were watching
36
+ # @see ZK::Client::Base#register
37
+ def unsubscribe
38
+ @event_handler.unregister(self)
39
+ end
40
+ alias :unregister :unsubscribe
41
+
42
+ # @private
43
+ def call(event)
44
+ callback.call(event)
45
+ end
46
+
47
+ private
48
+ def prep_interests(a)
49
+ return ALL_EVENT_SET if a.nil?
50
+
51
+ rval =
52
+ case a
53
+ when Array
54
+ Set.new(a)
55
+ when Symbol
56
+ Set.new([a])
57
+ else
58
+ raise ArgumentError, "Don't know how to handle interests: #{a.inspect}"
59
+ end
60
+
61
+ rval.tap do |rv|
62
+ invalid = (rv - ALL_EVENT_SET)
63
+ raise ArgumentError, "Invalid event name(s) #{invalid.to_a.inspect} given" unless invalid.empty?
64
+ end
65
+ end
66
+ end
67
+ end
68
+
@@ -1,27 +1,29 @@
1
1
  module ZK
2
2
  module Exceptions
3
- OK = 0
4
- # System and server-side errors
5
- SYSTEMERROR = -1
6
- RUNTIMEINCONSISTENCY = SYSTEMERROR - 1
7
- DATAINCONSISTENCY = SYSTEMERROR - 2
8
- CONNECTIONLOSS = SYSTEMERROR - 3
9
- MARSHALLINGERROR = SYSTEMERROR - 4
10
- UNIMPLEMENTED = SYSTEMERROR - 5
11
- OPERATIONTIMEOUT = SYSTEMERROR - 6
12
- BADARGUMENTS = SYSTEMERROR - 7
13
- # API errors
14
- APIERROR = -100;
15
- NONODE = APIERROR - 1 # Node does not exist
16
- NOAUTH = APIERROR - 2 # Current operation not permitted
17
- BADVERSION = APIERROR - 3 # Version conflict
18
- NOCHILDRENFOREPHEMERALS = APIERROR - 8
19
- NODEEXISTS = APIERROR - 10
20
- NOTEMPTY = APIERROR - 11
21
- SESSIONEXPIRED = APIERROR - 12
22
- INVALIDCALLBACK = APIERROR - 13
23
- INVALIDACL = APIERROR - 14
24
- AUTHFAILED = APIERROR - 15 # client authentication failed
3
+ silence_warnings do
4
+ OK = 0
5
+ # System and server-side errors
6
+ SYSTEMERROR = -1
7
+ RUNTIMEINCONSISTENCY = SYSTEMERROR - 1
8
+ DATAINCONSISTENCY = SYSTEMERROR - 2
9
+ CONNECTIONLOSS = SYSTEMERROR - 3
10
+ MARSHALLINGERROR = SYSTEMERROR - 4
11
+ UNIMPLEMENTED = SYSTEMERROR - 5
12
+ OPERATIONTIMEOUT = SYSTEMERROR - 6
13
+ BADARGUMENTS = SYSTEMERROR - 7
14
+ # API errors
15
+ APIERROR = -100;
16
+ NONODE = APIERROR - 1 # Node does not exist
17
+ NOAUTH = APIERROR - 2 # Current operation not permitted
18
+ BADVERSION = APIERROR - 3 # Version conflict
19
+ NOCHILDRENFOREPHEMERALS = APIERROR - 8
20
+ NODEEXISTS = APIERROR - 10
21
+ NOTEMPTY = APIERROR - 11
22
+ SESSIONEXPIRED = APIERROR - 12
23
+ INVALIDCALLBACK = APIERROR - 13
24
+ INVALIDACL = APIERROR - 14
25
+ AUTHFAILED = APIERROR - 15 # client authentication failed
26
+ end
25
27
 
26
28
 
27
29
  # these errors are returned rather than the driver level errors
@@ -92,7 +94,7 @@ module ZK
92
94
  INVALIDCALLBACK => InvalidCallback,
93
95
  INVALIDACL => InvalidACL,
94
96
  AUTHFAILED => AuthFailed,
95
- }
97
+ }.freeze unless defined?(ERROR_MAP)
96
98
 
97
99
  # base class of ZK generated errors (not driver-level errors)
98
100
  class ZKError < StandardError; end
@@ -120,6 +122,19 @@ module ZK
120
122
 
121
123
  # raised when someone performs a blocking ZK operation on the event dispatch thread.
122
124
  class EventDispatchThreadException < ZKError; end
125
+
126
+ # raised when a chrooted conection is requested but the root doesn't exist
127
+ class ChrootPathDoesNotExistError < NoNode
128
+ def initialize(host_string, chroot_path)
129
+ super("Chrooted connection to #{host_string} at #{chroot_path} requested, but path did not exist")
130
+ end
131
+ end
132
+
133
+ class ChrootMustStartWithASlashError < ArgumentError
134
+ def initialize(erroneous_string)
135
+ super("Chroot strings must start with a '/' you provided: #{erroneous_string.inspect}")
136
+ end
137
+ end
123
138
  end
124
139
  end
125
140
 
@@ -0,0 +1,79 @@
1
+ module ZK
2
+ # this is taken from activesupport-3.2.3, and pasted here so that we don't conflict if someone
3
+ # is using us as part of a rails app
4
+ #
5
+ # i've removed the code that includes InstanceMethods (tftfy)
6
+ # @private
7
+ module Concern
8
+ def self.extended(base)
9
+ base.instance_variable_set("@_dependencies", [])
10
+ end
11
+
12
+ def append_features(base)
13
+ if base.instance_variable_defined?("@_dependencies")
14
+ base.instance_variable_get("@_dependencies") << self
15
+ return false
16
+ else
17
+ return false if base < self
18
+ @_dependencies.each { |dep| base.send(:include, dep) }
19
+ super
20
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
21
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
22
+ end
23
+ end
24
+
25
+ def included(base = nil, &block)
26
+ if base.nil?
27
+ @_included_block = block
28
+ else
29
+ super
30
+ end
31
+ end
32
+ end
33
+
34
+ module Extensions
35
+ # some extensions to the ZookeeperCallbacks classes, mainly convenience
36
+ # interrogators
37
+ module Callbacks
38
+ module Callback
39
+ extend Concern
40
+
41
+ # allow access to the connection that fired this callback
42
+ attr_accessor :zk
43
+
44
+ module ClassMethods
45
+ # allows for easier construction of a user callback block that will be
46
+ # called with the callback object itself as an argument.
47
+ #
48
+ # *args, if given, will be passed on *after* the callback
49
+ #
50
+ # @example
51
+ #
52
+ # WatcherCallback.create do |cb|
53
+ # puts "watcher callback called with argument: #{cb.inspect}"
54
+ # end
55
+ #
56
+ # "watcher callback called with argument: #<ZookeeperCallbacks::WatcherCallback:0x1018a3958 @state=3, @type=1, ...>"
57
+ #
58
+ #
59
+ def create(*args, &block)
60
+ cb_inst = new { block.call(cb_inst) }
61
+ end
62
+ end
63
+ end # Callback
64
+ end # Callbacks
65
+ end # Extensions
66
+ end # ZK
67
+
68
+ # ZookeeperCallbacks::Callback.extend(ZK::Extensions::Callbacks::Callback)
69
+ ZookeeperCallbacks::Callback.send(:include, ZK::Extensions::Callbacks::Callback)
70
+
71
+ # Include the InterruptedSession module in key ZookeeperExceptions to allow
72
+ # clients to catch a single error type when waiting on a node (for example)
73
+
74
+ [:ConnectionClosed, :NotConnected, :SessionExpired, :SessionMoved, :ConnectionLoss].each do |class_name|
75
+ ZookeeperExceptions::ZookeeperException.const_get(class_name).tap do |klass|
76
+ klass.__send__(:include, ZK::Exceptions::InterruptedSession)
77
+ end
78
+ end
79
+
File without changes
File without changes
File without changes
@@ -13,7 +13,6 @@ module ZK
13
13
  # #handle message
14
14
  # end
15
15
  class MessageQueue
16
-
17
16
  # @private
18
17
  # :nodoc:
19
18
  attr_accessor :zk
@@ -79,15 +78,19 @@ module ZK
79
78
  # the message
80
79
  def subscribe(&block)
81
80
  @subscription_block = block
82
- @subscription_reference = @zk.watcher.subscribe(full_queue_path) do |event, zk|
81
+ @sub = @zk.register(full_queue_path) do |event, zk|
83
82
  find_and_process_next_available(@zk.children(full_queue_path, :watch => true))
84
83
  end
84
+
85
85
  find_and_process_next_available(@zk.children(full_queue_path, :watch => true))
86
86
  end
87
87
 
88
88
  # stop listening to this queue
89
89
  def unsubscribe
90
- @subscription_reference.unsubscribe
90
+ if @sub
91
+ @sub.unsubscribe
92
+ @sub = nil
93
+ end
91
94
  end
92
95
 
93
96
  # a list of the message titles in the queue
@@ -98,6 +101,7 @@ module ZK
98
101
  # highly destructive method!
99
102
  # WARNING! Will delete the queue and all messages in it
100
103
  def destroy!
104
+ unsubscribe # first thing, make sure we don't get any callbacks related to this
101
105
  children = @zk.children(full_queue_path)
102
106
  locks = []
103
107
  children.each do |path|
@@ -120,7 +124,7 @@ module ZK
120
124
  messages.each do |message_title|
121
125
  message_path = "#{full_queue_path}/#{message_title}"
122
126
  locker = @zk.locker(message_path)
123
- if locker.lock!
127
+ if locker.lock! # non-blocking lock
124
128
  begin
125
129
  data = @zk.get(message_path).first
126
130
  result = @subscription_block.call(message_title, data)
File without changes
File without changes
data/lib/zk/stat.rb ADDED
@@ -0,0 +1,115 @@
1
+ module ZK
2
+ # Included in ZookeeperStat::Stat, extends it with some conveniences for
3
+ # dealing with Stat objects. Also provides docuemntation here for the meaning
4
+ # of these values.
5
+ #
6
+ # Some of the methods added are to match the names in [the documentation][]
7
+ #
8
+ # Some of this may eventually be pushed down to slyphon-zookeeper
9
+ #
10
+ # [the documentation]: http://zookeeper.apache.org/doc/r3.3.5/zookeeperProgrammers.html#sc_zkStatStructure
11
+ module Stat
12
+ MEMBERS = [:version, :exists, :czxid, :mzxid, :ctime, :mtime, :cversion, :aversion, :ephemeralOwner, :dataLength, :numChildren, :pzxid].freeze
13
+
14
+ def ==(other)
15
+ MEMBERS.all? { |m| self.__send__(m) == other.__send__(m) }
16
+ end
17
+
18
+ # returns true if the node is ephemeral (will be cleaned up when the
19
+ # current session expires)
20
+ def ephemeral?
21
+ ephemeral_owner && (ephemeral_owner != 0)
22
+ end
23
+
24
+ # The zxid of the change that caused this znode to be created.
25
+ #
26
+ # (also: czxid)
27
+ def created_zxid
28
+ # @!parse alias czxid created_zxid
29
+ czxid
30
+ end
31
+
32
+ # The zxid of the change that last modified this znode.
33
+ #
34
+ # (also: mzxid)
35
+ def last_modified_zxid
36
+ mzxid
37
+ end
38
+
39
+ # The time in milliseconds from epoch when this znode was created
40
+ #
41
+ # (also: created\_time)
42
+ #
43
+ # @return [Fixnum]
44
+ # @see #ctime_t
45
+ def created_time
46
+ ctime
47
+ end
48
+
49
+ # The time when this znode was created
50
+ #
51
+ # @return [Time] creation time of this znode
52
+ def ctime_t
53
+ Time.at(ctime * 0.001)
54
+ end
55
+
56
+ # The time in milliseconds from epoch when this znode was last modified.
57
+ #
58
+ # (also: mtime)
59
+ # @return [Fixnum]
60
+ # @see #mtime_t
61
+ def last_modified_time
62
+ mtime
63
+ end
64
+
65
+ # The time when this znode was last modified
66
+ #
67
+ # @return [Time] last modification time of this znode
68
+ # @see #last_modified_time
69
+ def mtime_t
70
+ Time.at(mtime * 0.001)
71
+ end
72
+
73
+ # The number of changes to the children of this znode
74
+ #
75
+ # (also: #cversion)
76
+ # @return [Fixnum]
77
+ def child_list_version
78
+ cversion
79
+ end
80
+
81
+ # The number of changes to the ACL of this znode.
82
+ #
83
+ # (also: #aversion)
84
+ # @return [Fixnum]
85
+ def acl_list_version
86
+ aversion
87
+ end
88
+
89
+ # The number of changes to the data of this znode.
90
+ #
91
+ # @return [Fixnum]
92
+ def version
93
+ super
94
+ end
95
+
96
+ # The length of the data field of this znode.
97
+ #
98
+ # @return [Fixnum]
99
+ def data_length
100
+ super
101
+ end
102
+
103
+ # The number of children of this znode.
104
+ #
105
+ # @return [Fixnum]
106
+ def num_children
107
+ super
108
+ end
109
+ end # Stat
110
+ end # ZK
111
+
112
+ class ZookeeperStat::Stat
113
+ include ZK::Stat
114
+ end
115
+
@@ -22,6 +22,8 @@ module ZK
22
22
 
23
23
  @mutex = Mutex.new
24
24
 
25
+ @error_callbacks = []
26
+
25
27
  start!
26
28
  end
27
29
 
@@ -35,7 +37,7 @@ module ZK
35
37
  callable ||= blk
36
38
 
37
39
  # XXX(slyphon): do we care if the threadpool is not running?
38
- raise Exceptions::ThreadpoolIsNotRunningException unless running?
40
+ # raise Exceptions::ThreadpoolIsNotRunningException unless running?
39
41
  raise ArgumentError, "Argument to Threadpool#defer must respond_to?(:call)" unless callable.respond_to?(:call)
40
42
 
41
43
  @threadqueue << callable
@@ -46,6 +48,12 @@ module ZK
46
48
  @mutex.synchronize { @running }
47
49
  end
48
50
 
51
+ # returns true if the current thread is one of the threadpool threads
52
+ def on_threadpool?
53
+ tp = @mutex.synchronize { @threadpool.dup }
54
+ tp and tp.respond_to?(:include?) and tp.include?(Thread.current)
55
+ end
56
+
49
57
  # starts the threadpool if not already running
50
58
  def start!
51
59
  @mutex.synchronize do
@@ -56,6 +64,18 @@ module ZK
56
64
  true
57
65
  end
58
66
 
67
+ # register a block to be called back with unhandled exceptions that occur
68
+ # in the threadpool.
69
+ #
70
+ # @note if your exception callback block itself raises an exception, I will
71
+ # make fun of you.
72
+ #
73
+ def on_exception(&blk)
74
+ @mutex.synchronize do
75
+ @error_callbacks << blk
76
+ end
77
+ end
78
+
59
79
  # join all threads in this threadpool, they will be given a maximum of +timeout+
60
80
  # seconds to exit before they are considered hung and will be ignored (this is an
61
81
  # issue with threads in general: see
@@ -90,18 +110,46 @@ module ZK
90
110
  end
91
111
 
92
112
  private
113
+ def dispatch_to_error_handler(e)
114
+ # make a copy that will be free from thread manipulation
115
+ # and doesn't require holding the lock
116
+ cbs = @mutex.synchronize { @error_callbacks.dup }
117
+
118
+ if cbs.empty?
119
+ default_exception_handler(e)
120
+ else
121
+ while cb = cbs.shift
122
+ begin
123
+ cb.call(e)
124
+ rescue Exception => e
125
+ msg = [
126
+ "Exception caught in user supplied on_exception handler.",
127
+ "Just meditate on the irony of that for a moment. There. Good.",
128
+ "The callback that errored was: #{cb.inspect}, the exception was",
129
+ ""
130
+ ]
131
+
132
+ default_exception_handler(e, msg.join("\n"))
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ def default_exception_handler(e, msg=nil)
139
+ msg ||= 'Exception caught in threadpool'
140
+ logger.error { "#{msg}: #{e.to_std_format}" }
141
+ end
142
+
93
143
  def spawn_threadpool #:nodoc:
94
144
  until @threadpool.size >= @size.to_i
95
145
  thread = Thread.new do
96
146
  while @running
97
147
  begin
98
148
  op = @threadqueue.pop
99
- # $stderr.puts "thread #{Thread.current.inspect} got #{op.inspect}"
100
149
  break if op == KILL_TOKEN
101
150
  op.call
102
151
  rescue Exception => e
103
- logger.error { "Exception caught in threadpool" }
104
- logger.error { e.to_std_format }
152
+ dispatch_to_error_handler(e)
105
153
  end
106
154
  end
107
155
  end
data/lib/zk/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module ZK
2
+ VERSION = "1.0.0.rc.1"
3
+ end