thrift_client-mavericks 0.8.4
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.
- checksums.yaml +15 -0
- data/lib/thrift_client.rb +34 -0
- data/lib/thrift_client/abstract_thrift_client.rb +221 -0
- data/lib/thrift_client/connection.rb +4 -0
- data/lib/thrift_client/connection/base.rb +25 -0
- data/lib/thrift_client/connection/factory.rb +13 -0
- data/lib/thrift_client/connection/http.rb +29 -0
- data/lib/thrift_client/connection/socket.rb +29 -0
- data/lib/thrift_client/event_machine.rb +145 -0
- data/lib/thrift_client/server.rb +108 -0
- data/lib/thrift_client/simple.rb +272 -0
- data/lib/thrift_client/thrift.rb +40 -0
- data/test/greeter/greeter.rb +121 -0
- data/test/greeter/server.rb +44 -0
- data/test/multiple_working_servers_test.rb +112 -0
- data/test/simple_test.rb +136 -0
- data/test/test_helper.rb +12 -0
- data/test/thrift_client_http_test.rb +46 -0
- data/test/thrift_client_test.rb +282 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NDRjMjcxZmEyY2ZmYmMyYWNiYTFlZTI3NjcxMDg4MGE4NzUxM2FlNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmM3ZTQ2ZjQ3OGI5ZDkwNDFlODkxZTZlNzJkOWM2YmQyNTQ0YWFiZQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MzA4OTk3ODdiYWM3MzFlNWQwYTMxYzE2NjEyOTk1OWIwMTc2NGEzN2U4MzBh
|
10
|
+
YTlmNDJhOTY2YWU3NmQ1NTQ3Y2U3ZWU0ZDA0ZTE3NWRjNGZlMGRiNTU0YmVj
|
11
|
+
Y2MyN2UwYTFhZGVkZTdlZTg0Njg1NTMyMzI5OTVjMDM5ZjI4ZmM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YTEyNzk2OTk5NmRjZTRhMWRiNzdkZmQ4NzQ3ZTUzNDgwNjNiMDM2MTBkN2Nl
|
14
|
+
ZGFjNjA0ZWE0ODRiNjY0NzMzYWJmMzY4YzgzZDM3NWI3ZTIzODZkMzU0ZDk2
|
15
|
+
NGNlMGY1ZmJlN2M2YTJmZmVlZmIxYWI2YTBhMmVjM2UwM2FiMGU=
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thrift'
|
2
|
+
require 'thrift_client/thrift'
|
3
|
+
require 'thrift_client/server'
|
4
|
+
require 'thrift_client/abstract_thrift_client'
|
5
|
+
|
6
|
+
class ThriftClient < AbstractThriftClient
|
7
|
+
class NoServersAvailable < StandardError; end
|
8
|
+
|
9
|
+
=begin rdoc
|
10
|
+
Create a new ThriftClient instance. Accepts an internal Thrift client class (such as CassandraRb::Client), a list of servers with ports, and optional parameters.
|
11
|
+
|
12
|
+
Valid optional parameters are:
|
13
|
+
|
14
|
+
<tt>:protocol</tt>:: Which Thrift protocol to use. Defaults to <tt>Thrift::BinaryProtocol</tt>.
|
15
|
+
<tt>:protocol_extra_params</tt>:: An array of additional parameters to pass to the protocol initialization call. Defaults to <tt>[]</tt>.
|
16
|
+
<tt>:transport</tt>:: Which Thrift transport to use. Defaults to <tt>Thrift::Socket</tt>.
|
17
|
+
<tt>:transport_wrapper</tt>:: Which Thrift transport wrapper to use. Defaults to <tt>Thrift::FramedTransport</tt>.
|
18
|
+
<tt>:exception_classes</tt>:: Which exceptions to catch and retry when sending a request. Defaults to <tt>[IOError, Thrift::Exception, Thrift::ApplicationException, Thrift::TransportException, NoServersAvailable]</tt>
|
19
|
+
<tt>:exception_class_overrides</tt>:: For specifying children of classes in exception_classes for which you don't want to retry or reconnect.
|
20
|
+
<tt>:raise</tt>:: Whether to reraise errors if no responsive servers are found. Defaults to <tt>true</tt>.
|
21
|
+
<tt>:retries</tt>:: How many times to retry a request. Defaults to 0.
|
22
|
+
<tt>:server_retry_period</tt>:: How many seconds to wait before trying to reconnect to a dead server. Defaults to <tt>1</tt>. Set to <tt>nil</tt> to disable.
|
23
|
+
<tt>:server_max_requests</tt>:: How many requests to perform before moving on to the next server in the pool, regardless of error status. Defaults to <tt>nil</tt> (no limit).
|
24
|
+
<tt>:timeout</tt>:: Specify the default timeout in seconds. Defaults to <tt>1</tt>.
|
25
|
+
<tt>:connect_timeout</tt>:: Specify the connection timeout in seconds. Defaults to <tt>0.1</tt>.
|
26
|
+
<tt>:timeout_overrides</tt>:: Specify additional timeouts on a per-method basis, in seconds. Only works with <tt>Thrift::BufferedTransport</tt>.
|
27
|
+
<tt>:cached_connections</tt>:: Cache connections between requests. Trades connect() costs for open sockets. Defaults to <tt>false</tt>.
|
28
|
+
<tt>:defaults</tt>:: Specify default values to return on a per-method basis, if <tt>:raise</tt> is set to false.
|
29
|
+
=end rdoc
|
30
|
+
|
31
|
+
def initialize(client_class, servers, options = {})
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
class AbstractThriftClient
|
2
|
+
include ThriftHelpers
|
3
|
+
|
4
|
+
DISCONNECT_ERRORS = [
|
5
|
+
IOError,
|
6
|
+
Thrift::Exception,
|
7
|
+
Thrift::ApplicationException,
|
8
|
+
Thrift::TransportException
|
9
|
+
]
|
10
|
+
|
11
|
+
DEFAULT_WRAPPED_ERRORS = [
|
12
|
+
Thrift::ApplicationException,
|
13
|
+
Thrift::TransportException,
|
14
|
+
]
|
15
|
+
|
16
|
+
DEFAULTS = {
|
17
|
+
:protocol => Thrift::BinaryProtocol,
|
18
|
+
:protocol_extra_params => [],
|
19
|
+
:transport => Thrift::Socket,
|
20
|
+
:transport_wrapper => Thrift::FramedTransport,
|
21
|
+
:raise => true,
|
22
|
+
:defaults => {},
|
23
|
+
:exception_classes => DISCONNECT_ERRORS,
|
24
|
+
:exception_class_overrides => [],
|
25
|
+
:retries => 0,
|
26
|
+
:server_retry_period => 1,
|
27
|
+
:server_max_requests => nil,
|
28
|
+
:retry_overrides => {},
|
29
|
+
:wrapped_exception_classes => DEFAULT_WRAPPED_ERRORS,
|
30
|
+
:connect_timeout => 0.1,
|
31
|
+
:timeout => 1,
|
32
|
+
:timeout_overrides => {},
|
33
|
+
:cached_connections => false
|
34
|
+
}
|
35
|
+
|
36
|
+
attr_reader :last_client, :client, :client_class, :current_server, :server_list, :options, :client_methods
|
37
|
+
|
38
|
+
def initialize(client_class, servers, options = {})
|
39
|
+
@options = DEFAULTS.merge(options)
|
40
|
+
@options[:server_retry_period] ||= 0
|
41
|
+
|
42
|
+
@client_class = client_class
|
43
|
+
@server_list = Array(servers).collect do |s|
|
44
|
+
Server.new(s, @client_class, @options)
|
45
|
+
end.sort_by { rand }
|
46
|
+
|
47
|
+
@current_server = @server_list.first
|
48
|
+
|
49
|
+
@callbacks = {}
|
50
|
+
@client_methods = []
|
51
|
+
@client_class.instance_methods.each do |method_name|
|
52
|
+
if method_name != 'send_message' && method_name =~ /^send_(.*)$/
|
53
|
+
instance_eval("def #{$1}(*args); handled_proxy(:'#{$1}', *args); end", __FILE__, __LINE__)
|
54
|
+
@client_methods << $1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
@request_count = 0
|
58
|
+
@options[:wrapped_exception_classes].each do |exception_klass|
|
59
|
+
name = exception_klass.to_s.split('::').last
|
60
|
+
begin
|
61
|
+
@client_class.const_get(name)
|
62
|
+
rescue NameError
|
63
|
+
@client_class.const_set(name, Class.new(exception_klass))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Adds a callback that will be invoked at a certain time. The valid callback types are:
|
69
|
+
# :post_connect - should accept a single AbstractThriftClient argument, which is the client object to
|
70
|
+
# which the callback was added. Called after a connection to the remote thrift server
|
71
|
+
# is established.
|
72
|
+
# :before_method - should accept a single method name argument. Called before a method is invoked on the
|
73
|
+
# thrift server.
|
74
|
+
# :on_exception - should accept 2 args: an Exception instance and a method name. Called right before the
|
75
|
+
# exception is raised.
|
76
|
+
def add_callback(callback_type, &block)
|
77
|
+
case callback_type
|
78
|
+
when :post_connect, :before_method, :on_exception
|
79
|
+
@callbacks[callback_type] ||= []
|
80
|
+
@callbacks[callback_type].push(block)
|
81
|
+
# Allow chaining
|
82
|
+
return self
|
83
|
+
else
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def inspect
|
89
|
+
"<#{self.class}(#{client_class}) @current_server=#{@current_server}>"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Force the client to connect to the server. Not necessary to be
|
93
|
+
# called as the connection will be made on the first RPC method
|
94
|
+
# call.
|
95
|
+
def connect!(method = nil)
|
96
|
+
start_time ||= Time.now
|
97
|
+
@current_server = next_live_server
|
98
|
+
@client = @current_server.client
|
99
|
+
@last_client = @client
|
100
|
+
do_callbacks(:post_connect, self)
|
101
|
+
rescue IOError, Thrift::TransportException
|
102
|
+
disconnect!(true)
|
103
|
+
timeout = timeout(method)
|
104
|
+
if timeout && Time.now - start_time > timeout
|
105
|
+
no_servers_available!
|
106
|
+
else
|
107
|
+
retry
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def disconnect!(error = false)
|
112
|
+
if @current_server
|
113
|
+
@current_server.mark_down!(@options[:server_retry_period]) if error
|
114
|
+
@current_server.close
|
115
|
+
end
|
116
|
+
|
117
|
+
@client = nil
|
118
|
+
@current_server = nil
|
119
|
+
@request_count = 0
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Calls all callbacks of the specified type with the given args
|
125
|
+
def do_callbacks(callback_type_sym, *args)
|
126
|
+
return unless @callbacks[callback_type_sym]
|
127
|
+
@callbacks[callback_type_sym].each do |callback|
|
128
|
+
callback.call(*args)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def next_live_server
|
133
|
+
@server_index ||= 0
|
134
|
+
@server_list.length.times do |i|
|
135
|
+
cur = (1 + @server_index + i) % @server_list.length
|
136
|
+
if @server_list[cur].up?
|
137
|
+
@server_index = cur
|
138
|
+
return @server_list[cur]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
no_servers_available!
|
142
|
+
end
|
143
|
+
|
144
|
+
def ensure_socket_alignment
|
145
|
+
incomplete = true
|
146
|
+
result = yield
|
147
|
+
incomplete = false
|
148
|
+
result
|
149
|
+
# Thrift exceptions get read off the wire. We can consider them complete requests
|
150
|
+
rescue Thrift::Exception => e
|
151
|
+
incomplete = false
|
152
|
+
raise e
|
153
|
+
ensure
|
154
|
+
disconnect! if incomplete
|
155
|
+
end
|
156
|
+
|
157
|
+
def handled_proxy(method_name, *args)
|
158
|
+
begin
|
159
|
+
connect!(method_name.to_sym) unless @client
|
160
|
+
if has_timeouts?
|
161
|
+
@client.timeout = timeout(method_name.to_sym)
|
162
|
+
end
|
163
|
+
@request_count += 1
|
164
|
+
do_callbacks(:before_method, method_name)
|
165
|
+
ensure_socket_alignment { @client.send(method_name, *args) }
|
166
|
+
rescue *@options[:exception_class_overrides] => e
|
167
|
+
raise_or_default(e, method_name)
|
168
|
+
rescue *@options[:exception_classes] => e
|
169
|
+
disconnect!(true)
|
170
|
+
tries ||= (@options[:retry_overrides][method_name.to_sym] || @options[:retries]) + 1
|
171
|
+
tries -= 1
|
172
|
+
if tries > 0
|
173
|
+
retry
|
174
|
+
else
|
175
|
+
raise_or_default(e, method_name)
|
176
|
+
end
|
177
|
+
rescue Exception => e
|
178
|
+
raise_or_default(e, method_name)
|
179
|
+
ensure
|
180
|
+
disconnect! if @options[:server_max_requests] && @request_count >= @options[:server_max_requests]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def raise_or_default(e, method_name)
|
185
|
+
if @options[:raise]
|
186
|
+
raise_wrapped_error(e, method_name)
|
187
|
+
else
|
188
|
+
@options[:defaults][method_name.to_sym]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def raise_wrapped_error(e, method_name)
|
193
|
+
do_callbacks(:on_exception, e, method_name)
|
194
|
+
if @options[:wrapped_exception_classes].include?(e.class)
|
195
|
+
raise @client_class.const_get(e.class.to_s.split('::').last), e.message, e.backtrace
|
196
|
+
else
|
197
|
+
raise e
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def has_timeouts?
|
202
|
+
@has_timeouts ||= @options[:timeout_overrides].any? && transport_can_timeout?
|
203
|
+
end
|
204
|
+
|
205
|
+
def timeout(method = nil)
|
206
|
+
@options[:timeout_overrides][method] || @options[:timeout]
|
207
|
+
end
|
208
|
+
|
209
|
+
def transport_can_timeout?
|
210
|
+
if (@options[:transport_wrapper] || @options[:transport]).method_defined?(:timeout=)
|
211
|
+
true
|
212
|
+
else
|
213
|
+
warn "ThriftClient: Timeout overrides have no effect with with transport type #{(@options[:transport_wrapper] || @options[:transport])}"
|
214
|
+
false
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def no_servers_available!
|
219
|
+
raise ThriftClient::NoServersAvailable, "No live servers in #{@server_list.inspect}."
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ThriftHelpers
|
2
|
+
module Connection
|
3
|
+
class Base
|
4
|
+
attr_accessor :transport, :server
|
5
|
+
|
6
|
+
def initialize(transport, transport_wrapper, server, timeout)
|
7
|
+
@transport = transport
|
8
|
+
@transport_wrapper = transport_wrapper
|
9
|
+
@server = server
|
10
|
+
@timeout = timeout
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect!
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def open?
|
18
|
+
@transport.open?
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ThriftHelpers
|
2
|
+
module Connection
|
3
|
+
class Factory
|
4
|
+
def self.create(transport, transport_wrapper, server, timeout)
|
5
|
+
if transport == Thrift::HTTPClientTransport
|
6
|
+
Connection::HTTP.new(transport, transport_wrapper, server, timeout)
|
7
|
+
else
|
8
|
+
Connection::Socket.new(transport, transport_wrapper, server, timeout)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ThriftHelpers
|
2
|
+
module Connection
|
3
|
+
class HTTP < Base
|
4
|
+
def initialize(*args)
|
5
|
+
super *args
|
6
|
+
|
7
|
+
uri = parse_server(@server)
|
8
|
+
@transport = Thrift::HTTPClientTransport.new(@server)
|
9
|
+
end
|
10
|
+
|
11
|
+
def connect!
|
12
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
13
|
+
http.use_ssl = uri.scheme == "https"
|
14
|
+
http.get(uri.path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def open?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def parse_server(server)
|
23
|
+
uri = URI.parse(server)
|
24
|
+
raise ArgumentError, 'Servers must start with http' unless uri.scheme =~ /^http/
|
25
|
+
uri
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ThriftHelpers
|
2
|
+
module Connection
|
3
|
+
class Socket < Base
|
4
|
+
def initialize(*args)
|
5
|
+
super *args
|
6
|
+
|
7
|
+
host, port = parse_server(@server)
|
8
|
+
@transport = @transport.new(host, port.to_i, @timeout)
|
9
|
+
@transport = @transport_wrapper.new(@transport) if @transport_wrapper
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
@transport.close
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect!
|
17
|
+
@transport.open
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_server(server)
|
23
|
+
host, port = server.to_s.split(":")
|
24
|
+
raise ArgumentError, 'Servers must be in the form "host:port"' unless host and port
|
25
|
+
[host, port]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
raise RuntimeError, "The eventmachine transport requires Ruby 1.9.x" if RUBY_VERSION < '1.9.0'
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'fiber'
|
5
|
+
|
6
|
+
# EventMachine-ready Thrift connection
|
7
|
+
# Should not be used with a transport wrapper since it already performs buffering in Ruby.
|
8
|
+
module Thrift
|
9
|
+
class EventMachineTransport < BaseTransport
|
10
|
+
def initialize(host, port=9090, timeout=5)
|
11
|
+
@host, @port, @timeout = host, port, timeout
|
12
|
+
@connection = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def open?
|
16
|
+
@connection && @connection.connected?
|
17
|
+
end
|
18
|
+
|
19
|
+
def open
|
20
|
+
fiber = Fiber.current
|
21
|
+
@connection = EventMachineConnection.connect(@host, @port, @timeout)
|
22
|
+
@connection.callback do
|
23
|
+
fiber.resume
|
24
|
+
end
|
25
|
+
@connection.errback do
|
26
|
+
fiber.resume
|
27
|
+
end
|
28
|
+
Fiber.yield
|
29
|
+
|
30
|
+
raise Thrift::TransportException, "Unable to connect to #{@host}:#{@port}" unless @connection.connected?
|
31
|
+
@connection
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
@connection.close
|
36
|
+
end
|
37
|
+
|
38
|
+
def read(sz)
|
39
|
+
@connection.blocking_read(sz)
|
40
|
+
end
|
41
|
+
|
42
|
+
def write(buf)
|
43
|
+
@connection.send_data(buf)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module EventMachineConnection
|
48
|
+
GARBAGE_BUFFER_SIZE = 4096 # 4kB
|
49
|
+
|
50
|
+
include EM::Deferrable
|
51
|
+
|
52
|
+
def self.connect(host='localhost', port=9090, timeout=5, &block)
|
53
|
+
EM.connect(host, port, self, host, port) do |conn|
|
54
|
+
conn.pending_connect_timeout = timeout
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def trap
|
59
|
+
begin
|
60
|
+
yield
|
61
|
+
rescue Exception => ex
|
62
|
+
puts ex.message
|
63
|
+
puts ex.backtrace.join("\n")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(host, port=9090)
|
68
|
+
@host, @port = host, port
|
69
|
+
@index = 0
|
70
|
+
@disconnected = 'not connected'
|
71
|
+
@buf = ''
|
72
|
+
end
|
73
|
+
|
74
|
+
def close
|
75
|
+
trap do
|
76
|
+
@disconnected = 'closed'
|
77
|
+
close_connection(true)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def blocking_read(size)
|
82
|
+
raise IOError, "lost connection to #{@host}:#{@port}: #{@disconnected}" if @disconnected
|
83
|
+
if can_read?(size)
|
84
|
+
yank(size)
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Unexpected state" if @size or @callback
|
87
|
+
|
88
|
+
fiber = Fiber.current
|
89
|
+
@size = size
|
90
|
+
@callback = proc { |data|
|
91
|
+
fiber.resume(data)
|
92
|
+
}
|
93
|
+
Fiber.yield
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def receive_data(data)
|
98
|
+
trap do
|
99
|
+
(@buf) << data
|
100
|
+
|
101
|
+
if @callback and can_read?(@size)
|
102
|
+
callback = @callback
|
103
|
+
data = yank(@size)
|
104
|
+
@callback = @size = nil
|
105
|
+
callback.call(data)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def connected?
|
111
|
+
!@disconnected
|
112
|
+
end
|
113
|
+
|
114
|
+
def connection_completed
|
115
|
+
@disconnected = nil
|
116
|
+
succeed
|
117
|
+
end
|
118
|
+
|
119
|
+
def unbind
|
120
|
+
if !@disconnected
|
121
|
+
@disconnected = 'unbound'
|
122
|
+
else
|
123
|
+
fail
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def can_read?(size)
|
128
|
+
@buf.size >= @index + size
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def yank(len)
|
134
|
+
data = @buf.slice(@index, len)
|
135
|
+
@index += len
|
136
|
+
@index = @buf.size if @index > @buf.size
|
137
|
+
if @index >= GARBAGE_BUFFER_SIZE
|
138
|
+
@buf = @buf.slice(@index..-1)
|
139
|
+
@index = 0
|
140
|
+
end
|
141
|
+
data
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|