zk 0.9.1 → 1.0.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.
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