iproto 0.2 → 0.3.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.
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