iproto 0.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/iproto.gemspec CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'iproto'
7
- s.version = '0.2'
8
- s.date = '2012-01-27'
7
+ s.version = '0.3.3'
8
+ s.date = '2012-07-06'
9
9
  s.rubyforge_project = 'iproto'
10
10
 
11
11
  s.summary = "Mail.Ru simple network protocol"
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  iproto.gemspec
29
29
  lib/iproto.rb
30
30
  lib/iproto/connection_api.rb
31
+ lib/iproto/core-ext.rb
31
32
  lib/iproto/em.rb
32
33
  lib/iproto/tcp_socket.rb
33
34
  ]
@@ -1,16 +1,12 @@
1
1
  module IProto
2
2
  module ConnectionAPI
3
+ PACK = 'VVV'.freeze
3
4
  def next_request_id
4
- @next_request_id ||= 0
5
- @next_request_id += 1
6
- if @next_request_id > 0xffffffff
7
- @next_request_id = 0
8
- end
9
- @next_request_id
5
+ @next_request_id = ((@next_request_id ||= 0) + 1) & 0x7fffffff
10
6
  end
11
7
 
12
8
  def send_request(request_id, data)
13
9
  # for override
14
10
  end
15
11
  end
16
- end
12
+ end
@@ -0,0 +1,3 @@
1
+ class Fiber
2
+ alias call resume
3
+ end
data/lib/iproto/em.rb CHANGED
@@ -1,118 +1,194 @@
1
1
  require 'eventmachine'
2
2
  require 'fiber'
3
+ require 'iproto/core-ext'
4
+
3
5
  module IProto
4
6
  module EM
5
- module FixedHeaderProtocol
6
- def self.included(base)
7
- base.extend ClassMethods
7
+ module FixedLengthProtocol
8
+ def post_init
9
+ raise "you should set @_needed_size" unless @_needed_size
8
10
  end
9
11
 
10
- module ClassMethods
11
- def header_size(size = nil)
12
- if size
13
- @_header_size = size
14
- else
15
- @_header_size
16
- end
17
- end
18
- end
19
-
20
- attr_accessor :header, :body
21
-
22
12
  def receive_data(data)
23
13
  @buffer ||= ''
24
14
  offset = 0
25
- while (chunk = data[offset, _needed_size - @buffer.size]).size > 0 || _needed_size == 0
26
- @buffer += chunk
15
+ while (chunk = data[offset, _needed_size - @buffer.size]).size > 0
16
+ @buffer << chunk
27
17
  offset += chunk.size
28
18
  if @buffer.size == _needed_size
29
- case _state
30
- when :receive_header
31
- @_state = :receive_body
32
- receive_header @buffer
33
- when :receive_body
34
- @_state = :receive_header
35
- receive_body @buffer
36
- end
19
+ chunk = @buffer
37
20
  @buffer = ''
21
+ receive_chunk chunk
38
22
  end
39
23
  end
40
24
  end
41
25
 
42
- def receive_header(header)
26
+ def receive_chunk(chunk)
43
27
  # for override
44
28
  end
45
-
46
- def body_size
47
- # for override
48
- end
49
-
50
- def receive_body(body)
51
- # for override
52
- end
53
-
54
- def _needed_size
55
- case _state
56
- when :receive_header
57
- self.class.header_size
58
- when :receive_body
59
- body_size
60
- end
61
- end
62
-
63
- def _state
64
- @_state ||= :receive_header
65
- end
66
29
  end
67
30
 
68
31
  class Connection < ::EM::Connection
69
32
  include IProto::ConnectionAPI
70
- include FixedHeaderProtocol
33
+ include FixedLengthProtocol
34
+ HEADER_SIZE = 12
35
+
36
+ def initialize(host, port, reconnect = true)
37
+ @host = host
38
+ @port = port
39
+ @should_reconnect = !!reconnect
40
+ @reconnect_timer = nil
41
+ @connected = false
42
+ @waiting_requests = {}
43
+ @waiting_for_connect = []
44
+ init_protocol
45
+ end
71
46
 
72
- header_size 12
47
+ def shutdown_hook
48
+ ::EM.add_shutdown_hook {
49
+ @connected = false
50
+ if @reconnect_timer && !(Symbol === @reconnect_timer)
51
+ ::EM.cancel_timer @reconnect_timer
52
+ end
53
+ @reconnect_timer = @should_reconnect ? :force : nil
54
+ }
55
+ end
73
56
 
74
57
  def connection_completed
58
+ @reconnect_timer = nil
75
59
  @connected = true
60
+ shutdown_hook
61
+ _perform_waiting_for_connect(true)
76
62
  end
77
63
 
78
- # begin FixedHeaderAndBody API
79
- def body_size
80
- @body_size
64
+ attr_reader :_needed_size
65
+ def init_protocol
66
+ @_needed_size = HEADER_SIZE
67
+ @_state = :receive_header
81
68
  end
82
69
 
83
- def receive_header(header)
84
- @type, @body_size, @request_id = header.unpack('L3')
70
+ def receive_chunk(chunk)
71
+ if @_state == :receive_header
72
+ @type, body_size, @request_id = chunk.unpack(PACK)
73
+ @_needed_size = body_size
74
+ @_state = :receive_body
75
+ else
76
+ request = @waiting_requests.delete @request_id
77
+ raise IProto::UnexpectedResponse.new("For request id #{@request_id}") unless request
78
+ @_needed_size = HEADER_SIZE
79
+ @_state = :receive_header
80
+ do_response(request, chunk)
81
+ end
85
82
  end
86
83
 
87
- def receive_body(data)
88
- fiber = waiting_requests.delete @request_id
89
- raise IProto::UnexpectedResponse.new("For request id #{@request_id}") unless fiber
90
- fiber.resume data
84
+ def do_response(request, data)
85
+ request.call data
91
86
  end
92
- # end FixedHeaderAndBody API
93
87
 
94
- # begin ConnectionAPI
95
- def send_request(request_type, body)
96
- request_id = next_request_id
97
- send_data [request_type, body.size, request_id].pack('LLL') + body
98
- f = Fiber.current
99
- waiting_requests[request_id] = f
100
- Fiber.yield
88
+ def _setup_reconnect_timer(timeout)
89
+ if @reconnect_timer.nil? || @reconnect_timer == :force
90
+ @reconnect_timer = :waiting
91
+ if @timeout == 0
92
+ reconnect @host, @port
93
+ else
94
+ @reconnect_timer = ::EM.add_timer(timeout) do
95
+ reconnect @host, @port
96
+ end
97
+ end
98
+ end
101
99
  end
102
- # end
103
100
 
104
- def waiting_requests
105
- @waiting_requests ||= {}
101
+ def _send_request(request_type, body, request)
102
+ unless @connected
103
+ unless @should_reconnect
104
+ raise IProto::Disconnected.new("connection is closed") if @should_reconnect.nil?
105
+ else
106
+ @waiting_for_connect << [request_type, body, request]
107
+ _setup_reconnect_timer(0)
108
+ end
109
+ else
110
+ _do_send_request(request_type, body, request)
111
+ end
112
+ end
113
+
114
+ def _perform_waiting_for_connect(real)
115
+ if real
116
+ @waiting_for_connect.each do |request_type, body, request|
117
+ _do_send_request(request_type, body, request)
118
+ end
119
+ else
120
+ i = -1
121
+ @waiting_for_connect.each do |request_type, body, request|
122
+ @waiting_requests[i] = request
123
+ i -= 1
124
+ end
125
+ end
126
+ @waiting_for_connect.clear
127
+ end
128
+
129
+ def _do_send_request(request_type, body, request)
130
+ while @waiting_requests.include?(request_id = next_request_id); end
131
+ send_data [request_type, body.size, request_id].pack(PACK) + body
132
+ @waiting_requests[request_id] = request
133
+ end
134
+
135
+ def close
136
+ close_connection(false)
106
137
  end
107
138
 
108
139
  def close_connection(*args)
109
- super(*args)
140
+ @should_reconnect = nil
141
+ if @reconnect_timer
142
+ ::EM.cancel_timer @reconnect_timer unless Symbol === @reconnect_timer
143
+ @reconnect_timer = nil
144
+ end
145
+ if @connected
146
+ super(*args)
147
+ end
148
+ discard_requests
149
+ end
150
+
151
+ def discard_requests
152
+ exc = IProto::Disconnected.new("discarded cause of disconnect")
153
+ _perform_waiting_for_connect(false)
154
+ @waiting_requests.keys.each do |req|
155
+ request = @waiting_requests.delete req
156
+ do_response request, exc
157
+ end
110
158
  end
111
159
 
112
160
  def unbind
113
- raise IProto::CouldNotConnect.new unless @connected
161
+ discard_requests
162
+ case @should_reconnect
163
+ when true
164
+ @connected = false
165
+ _setup_reconnect_timer(0.03) unless @reconnect_timer == :force
166
+ when false
167
+ if @connected
168
+ raise IProto::Disconnected
169
+ else
170
+ raise IProto::CouldNotConnect
171
+ end
172
+ when nil
173
+ # do nothing cause we explicitely disconnected
174
+ end
175
+ end
176
+ end
177
+
178
+ class FiberedConnection < Connection
179
+ def send_request(request_type, body)
180
+ _send_request(request_type, body, Fiber.current)
181
+ result = Fiber.yield
182
+ raise result if Exception === result
183
+ result
184
+ end
185
+ end
186
+
187
+ class CallbackConnection < Connection
188
+ def send_request(request_type, body, cb = nil, &block)
189
+ _send_request(request_type, body, cb || block)
114
190
  end
115
191
  end
116
192
 
117
193
  end
118
- end
194
+ end
@@ -1,27 +1,56 @@
1
1
  require 'socket'
2
2
  module IProto
3
3
  # TODO: timeouts
4
- class TCPSocket < ::TCPSocket
4
+ class TCPSocket
5
5
  include IProto::ConnectionAPI
6
+ def initialize(host, port, reconnect = true)
7
+ @host = host
8
+ @port = port
9
+ @reconnect = true
10
+ end
11
+
12
+ def close
13
+ @reconnect = false
14
+ if @socket
15
+ @socket.close rescue nil
16
+ end
17
+ end
18
+
19
+ def socket
20
+ @socket ||= ::TCPSocket.new(@host, @port)
21
+ rescue Errno::ECONNREFUSED => e
22
+ raise CouldNotConnect, e
23
+ end
6
24
 
7
25
  # begin ConnectionAPI
8
26
  def send_request(request_type, body)
9
27
  request_id = next_request_id
10
- r = send [request_type, body.bytesize, request_id].pack('LLL') + body, 0
28
+ r = socket.send ([request_type, body.bytesize, request_id].pack(PACK) << body), 0
11
29
  response_size = recv_header request_id
12
30
  recv_response response_size
31
+ rescue Errno::EPIPE => e
32
+ @socket = nil if @reconnect
33
+ raise Disconnected, e
13
34
  end
14
35
  # end ConnectionAPI
15
36
 
16
37
  def recv_header(request_id)
17
- header = read(12)
18
- type, response_size, recv_request_id = header.unpack('L3')
19
- raise UnexpectedResponse.new("Waiting response for request_id #{request_id}, but received for #{recv_request_id}") unless request_id == recv_request_id
38
+ header = socket.read(12) or begin
39
+ @socket = nil if @reconnect
40
+ raise Disconnected, 'disconnected while read'
41
+ end
42
+ type, response_size, recv_request_id = header.unpack(PACK)
43
+ unless request_id == recv_request_id
44
+ raise UnexpectedResponse.new("Waiting response for request_id #{request_id}, but received for #{recv_request_id}")
45
+ end
20
46
  response_size
21
47
  end
22
48
 
23
49
  def recv_response(response_size)
24
- read(response_size)
50
+ socket.read(response_size) or begin
51
+ @socket = nil if @reconnect
52
+ raise Disconnected, 'disconnected while read'
53
+ end
25
54
  end
26
55
  end
27
- end
56
+ end
data/lib/iproto.rb CHANGED
@@ -1,24 +1,28 @@
1
1
  module IProto
2
- VERSION = '0.2'
2
+ VERSION = '0.3.3'
3
3
  class IProtoError < StandardError; end
4
4
  class CouldNotConnect < IProtoError; end
5
5
  class UnexpectedResponse < IProtoError; end
6
+ class Disconnected < IProtoError; end
6
7
 
7
8
  require 'iproto/connection_api'
8
9
 
9
10
  # types:
10
11
  # :em
11
12
  # :block
12
- def self.get_connection(host, port, type = :block)
13
+ def self.get_connection(host, port, type = :block, reconnect = true)
13
14
  case type
14
15
  when :em
15
16
  require 'iproto/em'
16
- ::EM.connect host, port, IProto::EM::Connection
17
+ ::EM.connect host, port, IProto::EM::FiberedConnection, host, port, reconnect
18
+ when :em_callback
19
+ require 'iproto/em'
20
+ ::EM.connect host, port, IProto::EM::CallbackConnection, host, port, reconnect
17
21
  when :block
18
22
  require 'iproto/tcp_socket'
19
- IProto::TCPSocket.new(host, port)
23
+ IProto::TCPSocket.new(host, port, reconnect)
20
24
  else
21
25
  raise "Undefined type #{type}"
22
26
  end
23
27
  end
24
- end
28
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iproto
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.2'
4
+ version: 0.3.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-27 00:00:00.000000000Z
12
+ date: 2012-07-06 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Mail.Ru simple network protocol
15
15
  email: ceo@prepor.ru
@@ -25,6 +25,7 @@ files:
25
25
  - iproto.gemspec
26
26
  - lib/iproto.rb
27
27
  - lib/iproto/connection_api.rb
28
+ - lib/iproto/core-ext.rb
28
29
  - lib/iproto/em.rb
29
30
  - lib/iproto/tcp_socket.rb
30
31
  homepage: http://github.com/mailru/iproto-ruby
@@ -48,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
49
  version: '0'
49
50
  requirements: []
50
51
  rubyforge_project: iproto
51
- rubygems_version: 1.8.10
52
+ rubygems_version: 1.8.24
52
53
  signing_key:
53
54
  specification_version: 2
54
55
  summary: Mail.Ru simple network protocol