iproto 0.3.3 → 0.3.6

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.3.3'
8
- s.date = '2012-07-06'
7
+ s.version = '0.3.6'
8
+ s.date = '2012-07-18'
9
9
  s.rubyforge_project = 'iproto'
10
10
 
11
11
  s.summary = "Mail.Ru simple network protocol"
@@ -1,6 +1,9 @@
1
1
  module IProto
2
2
  module ConnectionAPI
3
3
  PACK = 'VVV'.freeze
4
+ DEFAULT_RECONNECT = 0.1
5
+ HEADER_SIZE = 12
6
+
4
7
  def next_request_id
5
8
  @next_request_id = ((@next_request_id ||= 0) + 1) & 0x7fffffff
6
9
  end
@@ -1,3 +1,4 @@
1
+ require 'fiber'
1
2
  class Fiber
2
3
  alias call resume
3
4
  end
data/lib/iproto/em.rb CHANGED
@@ -3,7 +3,7 @@ require 'fiber'
3
3
  require 'iproto/core-ext'
4
4
 
5
5
  module IProto
6
- module EM
6
+ class EMConnection < ::EM::Connection
7
7
  module FixedLengthProtocol
8
8
  def post_init
9
9
  raise "you should set @_needed_size" unless @_needed_size
@@ -28,36 +28,50 @@ module IProto
28
28
  end
29
29
  end
30
30
 
31
- class Connection < ::EM::Connection
32
31
  include IProto::ConnectionAPI
33
32
  include FixedLengthProtocol
34
- HEADER_SIZE = 12
33
+
34
+ attr_reader :host, :port
35
35
 
36
36
  def initialize(host, port, reconnect = true)
37
37
  @host = host
38
38
  @port = port
39
+ @reconnect_timeout = Numeric === reconnect ? reconnect : DEFAULT_RECONNECT
39
40
  @should_reconnect = !!reconnect
40
41
  @reconnect_timer = nil
41
- @connected = false
42
+ @connected = :init_waiting
42
43
  @waiting_requests = {}
43
44
  @waiting_for_connect = []
44
45
  init_protocol
46
+ @shutdown_hook = false
47
+ shutdown_hook
48
+ end
49
+
50
+ def connected?
51
+ @connected == true
52
+ end
53
+
54
+ def could_be_connected?
55
+ @connected && (@connected != :force || ::EM.reactor_running?)
45
56
  end
46
57
 
47
58
  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
- }
59
+ unless @shutdown_hook
60
+ ::EM.add_shutdown_hook {
61
+ @connected = @should_reconnect ? :force : false
62
+ if Integer === @reconnect_timer
63
+ ::EM.cancel_timer @reconnect_timer
64
+ end
65
+ @reconnect_timer = nil
66
+ @shutdown_hook = false
67
+ }
68
+ @shutdown_hook = true
69
+ end
55
70
  end
56
71
 
57
72
  def connection_completed
58
73
  @reconnect_timer = nil
59
74
  @connected = true
60
- shutdown_hook
61
75
  _perform_waiting_for_connect(true)
62
76
  end
63
77
 
@@ -70,15 +84,19 @@ module IProto
70
84
  def receive_chunk(chunk)
71
85
  if @_state == :receive_header
72
86
  @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)
87
+ if body_size > 0
88
+ @_needed_size = body_size
89
+ @_state = :receive_body
90
+ return
91
+ else
92
+ chunk = ''
93
+ end
81
94
  end
95
+ request = @waiting_requests.delete @request_id
96
+ raise IProto::UnexpectedResponse.new("For request id #{@request_id}") unless request
97
+ @_needed_size = HEADER_SIZE
98
+ @_state = :receive_header
99
+ do_response(request, chunk)
82
100
  end
83
101
 
84
102
  def do_response(request, data)
@@ -86,9 +104,11 @@ module IProto
86
104
  end
87
105
 
88
106
  def _setup_reconnect_timer(timeout)
89
- if @reconnect_timer.nil? || @reconnect_timer == :force
107
+ if @reconnect_timer.nil?
90
108
  @reconnect_timer = :waiting
91
- if @timeout == 0
109
+ shutdown_hook
110
+ if timeout == 0
111
+ @connected = :waiting
92
112
  reconnect @host, @port
93
113
  else
94
114
  @reconnect_timer = ::EM.add_timer(timeout) do
@@ -99,22 +119,28 @@ module IProto
99
119
  end
100
120
 
101
121
  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]
122
+ if @connected == true
123
+ _do_send_request(request_type, body, request)
124
+ elsif could_be_connected?
125
+ @waiting_for_connect << [request_type, body, request]
126
+ if @connected == :force
107
127
  _setup_reconnect_timer(0)
108
128
  end
129
+ elsif ::EM.reactor_running?
130
+ EM.next_tick{
131
+ do_response(request, IProto::Disconnected.new("connection is closed"))
132
+ }
109
133
  else
110
- _do_send_request(request_type, body, request)
134
+ do_response(request, IProto::Disconnected.new("connection is closed"))
111
135
  end
112
136
  end
113
137
 
114
138
  def _perform_waiting_for_connect(real)
115
139
  if real
116
140
  @waiting_for_connect.each do |request_type, body, request|
141
+ ::EM.next_tick{
117
142
  _do_send_request(request_type, body, request)
143
+ }
118
144
  end
119
145
  else
120
146
  i = -1
@@ -138,13 +164,15 @@ module IProto
138
164
 
139
165
  def close_connection(*args)
140
166
  @should_reconnect = nil
141
- if @reconnect_timer
142
- ::EM.cancel_timer @reconnect_timer unless Symbol === @reconnect_timer
143
- @reconnect_timer = nil
167
+ if Integer === @reconnect_timer
168
+ ::EM.cancel_timer @reconnect_timer
144
169
  end
145
- if @connected
170
+ @reconnect_timer = nil
171
+
172
+ if @connected == true
146
173
  super(*args)
147
174
  end
175
+ @connected = false
148
176
  discard_requests
149
177
  end
150
178
 
@@ -158,16 +186,23 @@ module IProto
158
186
  end
159
187
 
160
188
  def unbind
189
+ prev_connected = @connected
190
+ @connected = false
161
191
  discard_requests
192
+ @connected = prev_connected
193
+
162
194
  case @should_reconnect
163
195
  when true
164
- @connected = false
165
- _setup_reconnect_timer(0.03) unless @reconnect_timer == :force
196
+ @reconnect_timer = nil
197
+ unless @connected == :force
198
+ @connected = false
199
+ _setup_reconnect_timer(@reconnect_timeout)
200
+ end
166
201
  when false
167
- if @connected
168
- raise IProto::Disconnected
169
- else
202
+ if @connected == :init_waiting
170
203
  raise IProto::CouldNotConnect
204
+ else
205
+ raise IProto::Disconnected
171
206
  end
172
207
  when nil
173
208
  # do nothing cause we explicitely disconnected
@@ -175,7 +210,7 @@ module IProto
175
210
  end
176
211
  end
177
212
 
178
- class FiberedConnection < Connection
213
+ class EMFiberedConnection < EMConnection
179
214
  def send_request(request_type, body)
180
215
  _send_request(request_type, body, Fiber.current)
181
216
  result = Fiber.yield
@@ -184,11 +219,9 @@ module IProto
184
219
  end
185
220
  end
186
221
 
187
- class CallbackConnection < Connection
222
+ class EMCallbackConnection < EMConnection
188
223
  def send_request(request_type, body, cb = nil, &block)
189
224
  _send_request(request_type, body, cb || block)
190
225
  end
191
226
  end
192
-
193
- end
194
227
  end
@@ -4,41 +4,63 @@ module IProto
4
4
  class TCPSocket
5
5
  include IProto::ConnectionAPI
6
6
  def initialize(host, port, reconnect = true)
7
- @host = host
8
- @port = port
9
- @reconnect = true
7
+ @addr = [host, port]
8
+ @reconnect_timeout = Numeric === reconnect ? reconnect : DEFAULT_RECONNECT
9
+ @reconnect = !!reconnect
10
+ @socket = nil
11
+ @reconnect_time = Time.now - 1
12
+ @retry = true
10
13
  end
11
14
 
12
15
  def close
13
16
  @reconnect = false
14
17
  if @socket
15
18
  @socket.close rescue nil
19
+ @socket = :disconnected
16
20
  end
17
21
  end
18
22
 
23
+ def connected?
24
+ @socket && @socket != :disconnected
25
+ end
26
+
27
+ def could_be_connected?
28
+ @socket ? @socket != :disconnected
29
+ : (@retry || @reconnect_time < Time.now)
30
+ end
31
+
19
32
  def socket
20
- @socket ||= ::TCPSocket.new(@host, @port)
33
+ if (sock = @socket)
34
+ sock != :disconnected ? sock : raise(Disconnected, "disconnected earlier")
35
+ else
36
+ sock = @socket = ::TCPSocket.new(*@addr)
37
+ @retry = true
38
+ end
39
+ sock
21
40
  rescue Errno::ECONNREFUSED => e
41
+ @socket = :disconnected unless @reconnect
22
42
  raise CouldNotConnect, e
23
43
  end
24
44
 
45
+ class Retry < RuntimeError; end
46
+
25
47
  # begin ConnectionAPI
26
48
  def send_request(request_type, body)
27
- request_id = next_request_id
28
- r = socket.send ([request_type, body.bytesize, request_id].pack(PACK) << body), 0
29
- response_size = recv_header request_id
30
- recv_response response_size
31
- rescue Errno::EPIPE => e
32
- @socket = nil if @reconnect
33
- raise Disconnected, e
49
+ begin
50
+ request_id = next_request_id
51
+ r = socket.send ([request_type, body.bytesize, request_id].pack(PACK) << body), 0
52
+ response_size = recv_header request_id
53
+ recv_response response_size
54
+ rescue Errno::EPIPE, Retry => e
55
+ _raise_disconnected(e, !@retry)
56
+ @retry = false
57
+ retry
58
+ end
34
59
  end
35
60
  # end ConnectionAPI
36
61
 
37
62
  def recv_header(request_id)
38
- header = socket.read(12) or begin
39
- @socket = nil if @reconnect
40
- raise Disconnected, 'disconnected while read'
41
- end
63
+ header = socket.read(HEADER_SIZE) or _raise_disconnected('disconnected while read', @retry ? :retry : true)
42
64
  type, response_size, recv_request_id = header.unpack(PACK)
43
65
  unless request_id == recv_request_id
44
66
  raise UnexpectedResponse.new("Waiting response for request_id #{request_id}, but received for #{recv_request_id}")
@@ -47,9 +69,21 @@ module IProto
47
69
  end
48
70
 
49
71
  def recv_response(response_size)
50
- socket.read(response_size) or begin
51
- @socket = nil if @reconnect
52
- raise Disconnected, 'disconnected while read'
72
+ socket.read(response_size) or _raise_disconnected('disconnected while read', 2)
73
+ end
74
+
75
+ def _raise_disconnected(message, _raise = true)
76
+ if @reconnect
77
+ @socket = nil
78
+ @reconnect_time = Time.now + @reconnect_timeout
79
+ else
80
+ @socket = :disconnected
81
+ end
82
+ case _raise
83
+ when true
84
+ raise Disconnected, message
85
+ when :retry
86
+ raise Retry
53
87
  end
54
88
  end
55
89
  end
data/lib/iproto.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  module IProto
2
- VERSION = '0.3.3'
2
+ VERSION = '0.3.6'
3
3
  class IProtoError < StandardError; end
4
- class CouldNotConnect < IProtoError; end
4
+ class ConnectionError < IProtoError; end
5
+ class CouldNotConnect < ConnectionError; end
6
+ class Disconnected < ConnectionError; end
5
7
  class UnexpectedResponse < IProtoError; end
6
- class Disconnected < IProtoError; end
7
8
 
8
9
  require 'iproto/connection_api'
9
10
 
@@ -14,10 +15,10 @@ module IProto
14
15
  case type
15
16
  when :em
16
17
  require 'iproto/em'
17
- ::EM.connect host, port, IProto::EM::FiberedConnection, host, port, reconnect
18
+ ::EM.connect host, port, IProto::EMFiberedConnection, host, port, reconnect
18
19
  when :em_callback
19
20
  require 'iproto/em'
20
- ::EM.connect host, port, IProto::EM::CallbackConnection, host, port, reconnect
21
+ ::EM.connect host, port, IProto::EMCallbackConnection, host, port, reconnect
21
22
  when :block
22
23
  require 'iproto/tcp_socket'
23
24
  IProto::TCPSocket.new(host, port, reconnect)
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.3.3
4
+ version: 0.3.6
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-07-06 00:00:00.000000000 Z
12
+ date: 2012-07-18 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Mail.Ru simple network protocol
15
15
  email: ceo@prepor.ru