zkruby 3.4.3

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.
@@ -0,0 +1,108 @@
1
+
2
+ # Including in a class and us the "enum" DSL method to create class instances
3
+ # As a special case, if the including class is descendant from StandardError, the enumerated
4
+ # instances will be classes descendent from the including class (eg like Errno)
5
+ module Enumeration
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ # In the normal case we just include our methods in the base class
10
+ # but for Errors we need to include them on each instance
11
+ base.send :include,InstanceMethods unless base < StandardError
12
+ end
13
+
14
+ module InstanceMethods
15
+ def === (other)
16
+ case other
17
+ when Symbol
18
+ to_sym == other
19
+ when Fixnum
20
+ to_int == other
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def to_i
27
+ to_int
28
+ end
29
+
30
+ def to_int
31
+ @index
32
+ end
33
+
34
+ def |(num)
35
+ to_int | num
36
+ end
37
+
38
+ def &(num)
39
+ to_int & num
40
+ end
41
+
42
+ def to_sym
43
+ @name
44
+ end
45
+
46
+ def to_s
47
+ "#{super} (:#{@name} [#{@index}])"
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+
53
+ # @param [] ref Symbol, Fixnum or Enumeration
54
+ # @return [Enumeration] instance representing ref or nil if not found
55
+ def get(ref)
56
+ @enums[ref]
57
+ end
58
+
59
+ # @param [] ref Symbol, Fixnum
60
+ # @raise [KeyError] if ref not found
61
+ # @return [Enumeration]
62
+ def fetch(ref)
63
+ @enums.fetch(ref)
64
+ end
65
+
66
+ # Will dynamically create a new enum instance if ref is not found
67
+ def lookup(ref)
68
+ case ref
69
+ when Enumeration,Class
70
+ ref
71
+ when Symbol
72
+ get(ref) || enum(ref,nil)
73
+ when Fixnum
74
+ get(ref) || enum(nil,ref)
75
+ else
76
+ nil
77
+ end
78
+ end
79
+
80
+ def enum(name,index,*args)
81
+ @enums ||= {}
82
+
83
+ instance = if (self < StandardError) then Class.new(self) else self.new(*args) end
84
+
85
+ instance.extend(InstanceMethods) if (self < StandardError)
86
+
87
+ const_name = if name.nil?
88
+ "ENUM" + index.to_s.sub("-","_")
89
+ else
90
+ name.to_s.upcase
91
+ end
92
+
93
+ self.const_set(const_name.to_s.upcase,instance)
94
+
95
+ @enums[instance] = instance
96
+ @enums[name] = instance if name
97
+ @enums[index] = instance if index
98
+ instance.instance_variable_set(:@name,name)
99
+ instance.instance_variable_set(:@index,index)
100
+ instance.freeze
101
+ end
102
+
103
+ def method_missing(method,*args)
104
+ @enums[method.downcase] || super
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,186 @@
1
+ require 'eventmachine'
2
+
3
+ if defined?(JRUBY_VERSION) && JRUBY_VERSION =~ /1\.6\.5.*/
4
+ raise "Fibers are broken in JRuby 1.6.5 (See JRUBY-6170)"
5
+ end
6
+
7
+ require 'strand'
8
+
9
+ module ZooKeeper
10
+ module EventMachine
11
+
12
+ class ClientConn < ::EM::Connection
13
+ include Protocol
14
+ include Slf4r::Logger
15
+
16
+ unless EventMachine.methods.include?(:set_pending_connect_timeout)
17
+ def set_pending_connect_timeout(timeout)
18
+
19
+ end
20
+ end
21
+
22
+ def initialize(session,connect_timeout)
23
+ @session = session
24
+ @connect_timeout = connect_timeout
25
+ set_pending_connect_timeout(connect_timeout)
26
+ rescue Exception => ex
27
+ logger.warn("Exception in initialize",ex)
28
+ end
29
+
30
+ def post_init()
31
+ rescue Exception => ex
32
+ logger.warn("Exception in post_init",ex)
33
+ end
34
+
35
+ def connection_completed()
36
+ @session.prime_connection(self)
37
+
38
+ # Make sure we connect within the timeout period
39
+ # TODO this should really be the amount of connect timeout left over
40
+ @timer = EM.add_timer(@connect_timeout) do
41
+ if @session.connected?
42
+ # Start the ping timer
43
+ ping = @session.ping_interval
44
+ @timer = EM.add_periodic_timer ( ping ) do
45
+ case @ping
46
+ when 1 then @session.ping()
47
+ when 2 then close_connection()
48
+ end
49
+ @ping += 1
50
+ end
51
+ else
52
+ close_connection()
53
+ end
54
+ end
55
+
56
+ rescue Exception => ex
57
+ logger.warn("Exception in connection_completed",ex)
58
+ end
59
+
60
+ def receive_records(packet_io)
61
+ @ping = 0
62
+ @session.receive_records(packet_io)
63
+ end
64
+
65
+ def disconnect()
66
+ close_connection()
67
+ end
68
+
69
+ def unbind
70
+ EM.cancel_timer(@timer) if @timer
71
+ @session.disconnected()
72
+ rescue Exception => ex
73
+ logger.warn("Exception in unbind",ex)
74
+ end
75
+
76
+ end
77
+
78
+
79
+ # The EventMachine binding is very simple because there is only one thread!
80
+ # and we have good stuff like timers provided for us
81
+ class Binding
82
+ include Slf4r::Logger
83
+ # We can use this binding if we are running in the reactor thread
84
+ def self.available?()
85
+ EM.reactor_running? && EM.reactor_thread?
86
+ end
87
+
88
+ def self.context(&context_block)
89
+ s = Strand.new() do
90
+ context_block.call(Strand)
91
+ end
92
+ s.join
93
+ end
94
+
95
+ attr_reader :client, :session
96
+ def start(client,session)
97
+ @client = client
98
+ @session = session
99
+ @session.start()
100
+ end
101
+
102
+ def connect(host,port,delay,timeout)
103
+ EM.add_timer(delay) do
104
+ EM.connect(host,port,ZooKeeper::EventMachine::ClientConn,@session,timeout)
105
+ end
106
+ end
107
+
108
+ # You are working in event machine it is up to you to ensure your callbacks do not block
109
+ def invoke(callback,*args)
110
+ callback.call(*args)
111
+ end
112
+
113
+ def queue_request(*args,&callback)
114
+ op = AsyncOp.new(self,&callback)
115
+ begin
116
+ @session.queue_request(*args) do |error,response|
117
+ op.resume(error,response)
118
+ end
119
+ rescue ZooKeeper::Error => ex
120
+ op.resume(ex,nil)
121
+ end
122
+
123
+ op
124
+ end
125
+
126
+ def close(&callback)
127
+
128
+ op = AsyncOp.new(self,&callback)
129
+
130
+ begin
131
+ @session.close() do |error,response|
132
+ op.resume(error,response)
133
+ end
134
+ rescue ZooKeeper::Error => ex
135
+ op.resume(ex,nil)
136
+ end
137
+
138
+ op
139
+ end
140
+
141
+ end #class Binding
142
+
143
+ class AsyncOp < ZooKeeper::AsyncOp
144
+
145
+ def initialize(binding,&callback)
146
+ @em_binding = binding
147
+
148
+ # Wrap the callback in its own Strand
149
+ @op_strand = Strand.new do
150
+ #immediately pause
151
+ error, response = Strand.yield
152
+ Strand.current[ZooKeeper::CURRENT] = [ @em_binding.client ]
153
+ raise error if error
154
+ callback.call(response)
155
+ end
156
+ end
157
+
158
+ def resume(error,response)
159
+ #TODO - raise issue in strand for resume to take arguments
160
+ op_strand.fiber.resume(error,response)
161
+ end
162
+
163
+ private
164
+
165
+ attr_reader :op_strand,:err_strand
166
+
167
+ def set_error_handler(errcallback)
168
+ @err_strand = Strand.new() do
169
+ begin
170
+ op_strand.value()
171
+ rescue StandardError => ex
172
+ Strand.current[ZooKeeper::CURRENT] = [ @em_binding.client ]
173
+ errcallback.call(ex)
174
+ end
175
+ end
176
+ end
177
+
178
+ def wait_value()
179
+ err_strand ? err_strand.value : op_strand.value
180
+ end
181
+
182
+ end #class AsyncOp
183
+ end #module EventMachine
184
+ end #module ZooKeeper
185
+
186
+ ZooKeeper.add_binding(ZooKeeper::EventMachine::Binding)
@@ -0,0 +1,47 @@
1
+ module ZooKeeper
2
+
3
+ module Proto
4
+
5
+ #MultiTransactionRecord and MultiResponse in java
6
+ #are not able to be compiled from Jute because jute doesn't support
7
+ #discriminators
8
+ class MultiRequest < BinData::Record
9
+ array :requests, :read_until => lambda { element.header._type < 0 } do
10
+ multi_header :header
11
+ choice :request, :selection => lambda { header._type },
12
+ :onlyif => lambda { header._type > 0 } do
13
+ create_request 1
14
+ delete_request 2
15
+ set_data_request 5
16
+ check_version_request 13
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ class MultiResponseItem < BinData::Record
23
+ multi_header :header
24
+ choice :response, :selection => lambda { header._type },
25
+ :onlyif => lambda { has_response? } do
26
+ error_response -1
27
+ create_response 1
28
+ set_data_response 5
29
+ end
30
+
31
+ def has_response?
32
+ !done? && [ -1, 1, 5 ] .include?(header._type)
33
+ end
34
+
35
+ def done?
36
+ # Boolean's don't actually work properly in bindata
37
+ header.done == true
38
+ end
39
+
40
+ end
41
+
42
+ class MultiResponse < BinData::Record
43
+ array :responses, :type => :multi_response_item, :read_until => lambda { element.done? }
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,182 @@
1
+ require 'stringio'
2
+
3
+ module ZooKeeper
4
+ # Represents a problem communicating with the ZK cluster
5
+ # client's shouldn't see this
6
+ class ProtocolError < IOError;end
7
+
8
+ # Raw object protocol, very similar to EM's native ObjectProtocol
9
+ # Expects:
10
+ # #receive_data and #send_records to be invoked
11
+ # #receive_records and #send_data to be implemented
12
+ module Protocol
13
+ MIN_PACKET = 5 #TODO Work out what the min packet size really is
14
+ include Slf4r::Logger
15
+
16
+ def receive_data data # :nodoc:
17
+
18
+ @buffer ||= StringIO.new()
19
+ @buffer.seek(0, IO::SEEK_END)
20
+ @buffer << data
21
+ @buffer.rewind
22
+ logger.debug { "Received #{data.length} bytes: buffer length = #{@buffer.length} pos = #{@buffer.pos}" }
23
+ loop do
24
+ if @buffer.length - @buffer.pos > MIN_PACKET
25
+ packet_size = @buffer.read(4).unpack("N").first
26
+ if (@buffer.length - @buffer.pos >= packet_size)
27
+ expected_pos = @buffer.pos + packet_size
28
+ # We just pass the buffer around and expect packet_size to be consumed
29
+ receive_records(@buffer)
30
+ if (@buffer.pos != expected_pos)
31
+ #this can happen during disconnection with left over packets
32
+ #the connection is dying anyway
33
+ leftover = @buffer.read(packet_size).unpack("H*")[0]
34
+ raise ProtocolError, "Records not consumed #{leftover}"
35
+ end
36
+ logger.debug { "Consumed packet #{packet_size}. Buffer pos=#{@buffer.pos}, length=#{@buffer.length}" }
37
+ next
38
+ else
39
+ # found the last partial packet
40
+ @buffer.seek(-4, IO::SEEK_CUR)
41
+ logger.debug { "Buffer contains #{@buffer.length} of #{packet_size} packet" }
42
+ end
43
+ end
44
+ break
45
+ end
46
+ # reset the buffer
47
+ @buffer = StringIO.new(@buffer.read()) if @buffer.pos > 0
48
+ end
49
+
50
+ def receive_records(packet_io)
51
+ #stub
52
+ #we don't unpack records here because we don't know what kind of records they are!
53
+ end
54
+
55
+ def send_records(*records)
56
+ length = 0
57
+ bindata = records.collect { |r| s = r.to_binary_s; length += s.length; s }
58
+ send_data([length].pack("N"))
59
+ bindata.each { |b| send_data(b) }
60
+ logger.debug { "Sent #{length} byte packet containing #{records.length} records" }
61
+ end
62
+ end
63
+
64
+ class Operation
65
+ attr_reader :op, :opcode, :request, :response, :callback
66
+ def initialize(op,opcode,request,response,callback)
67
+ @op=op;@opcode=opcode
68
+ @request=request;@response=response
69
+ @callback=callback
70
+ end
71
+
72
+ def path
73
+ #Every request has a path!
74
+ #TODO - path may be chrooted!
75
+ request.path if request.respond_to?(:path)
76
+ end
77
+ end
78
+
79
+ class Packet < Operation
80
+ attr_reader :xid, :watch_type, :watcher
81
+
82
+ def initialize(xid,op,opcode,request,response,watch_type,watcher,callback)
83
+ super(op,opcode,request,response,callback)
84
+ @xid=xid;
85
+ @watch_type = watch_type; @watcher = watcher
86
+ end
87
+
88
+
89
+ def error(reason)
90
+ result(reason)[0..2] # don't need the watch
91
+ end
92
+
93
+ def result(rc)
94
+ error = nil
95
+ unless (Error::NONE === rc) then
96
+ error = Error.lookup(rc)
97
+ error = error.exception("ZooKeeper error for #{@op}(#{path}) ")
98
+ end
99
+ [ callback, error ,response, watch_type ]
100
+ end
101
+ end
102
+
103
+ # NoNode error is expected for exists
104
+ class ExistsPacket < Packet
105
+ def result(rc)
106
+ Error::NO_NODE === rc ? [ callback, nil, nil, :exists ] : super(rc)
107
+ end
108
+ end
109
+
110
+ # In the normal case the close packet will be last and will get
111
+ # cleared via disconnected() and :session_expired
112
+ class ClosePacket < Packet
113
+ def result(rc)
114
+ Error::SESSION_EXPIRED == rc ? [ callback, nil, nil, nil ] : super(rc)
115
+ end
116
+ end
117
+
118
+
119
+ # Returned by asynchronous calls
120
+ #
121
+ # @example
122
+ # op = zk.stat("\apath") { |stat| something_with_stat() }
123
+ #
124
+ # op.on_error do |err|
125
+ # case err
126
+ # when ZK::Error::SESSION_EXPIRED
127
+ # puts "Ignoring session expired"
128
+ # else
129
+ # raise err
130
+ # end
131
+ # end
132
+ #
133
+ # begin
134
+ # result_of_somthing_with_stat = op.value
135
+ # rescue StandardError => ex
136
+ # puts "Oops, my error handler raised an exception"
137
+ # end
138
+ #
139
+ #
140
+ class AsyncOp
141
+
142
+ attr_accessor :backtrace
143
+
144
+ # Provide an error callback.
145
+ # @param block the error callback as a block
146
+ # @yieldparam [StandardError] the error raised by the zookeeper operation OR by its callback
147
+ def errback(&block)
148
+ set_error_handler(block)
149
+ end
150
+
151
+ # @param block the error callback as a Proc
152
+ def errback=(block)
153
+ set_error_handler(block)
154
+ end
155
+
156
+ alias :on_error :errback
157
+
158
+ # Wait for the async op to finish and returns its value
159
+ # will raise an exception if
160
+ # the operation itself fails and there is no error handler
161
+ # the callback raises a StandardError and there is no error handler
162
+ # the error handler raises StandardError
163
+ def value();
164
+ wait_value()
165
+ rescue ZooKeeper::Error => ex
166
+ # Set the backtrace to the original caller, rather than the ZK event loop
167
+ ex.set_backtrace(@backtrace) if @backtrace
168
+ raise ex
169
+ end
170
+
171
+ private
172
+
173
+ def set_error_handler(blk)
174
+ raise NotImplementedError, ":wait_result to be privately implemented by binding"
175
+ end
176
+
177
+ def wait_value();
178
+ raise NotImplementedError, ":wait_result to be privately implemented by binding"
179
+ end
180
+ end
181
+ end
182
+