iproto 0.3.3 → 0.3.6

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.
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