cod 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,14 @@
1
+ = 0.4.4 / 7Mar2012
2
+
3
+ + Uses IO.select in the server as well, not polling for accept. This should
4
+ further reduce the CPU time consumed by servers.
5
+
6
+ ! Fixes a bug where server would hang forever with messages to return in
7
+ its queue.
8
+
9
+ + Switching to YARD for documentation, please be patient while the whole
10
+ gets a workover. This should be finished by 0.4.6 at the latest.
11
+
1
12
  = 0.4.3 / 28Feb2012
2
13
 
3
14
  ! Fixes a few bugs when using tcp channels (connection was not made or not
data/README CHANGED
@@ -34,7 +34,7 @@ Working transports include:
34
34
  * tcp (server and client)
35
35
  * beanstalk (connects to beanstalkd)
36
36
 
37
- At version 0.4.3
37
+ At version 0.4.4
38
38
 
39
39
  (c) 2011 Kaspar Schiess
40
40
 
@@ -1,11 +1,13 @@
1
1
  def server
2
2
  fork do
3
+ $0 = 'server'
3
4
  yield
4
5
  end
5
6
  end
6
7
 
7
8
  def client
8
9
  fork do
10
+ $0 = 'client'
9
11
  yield
10
12
  end
11
13
  end
@@ -1,3 +1,4 @@
1
1
  A demonstration of what it takes to use googles protocol buffers (protobuf) on
2
- the wire. You will need the ruby-protocol-buffers gem for this to work.
2
+ the wire. You will need the ruby-protocol-buffers gem and of course the protoc
3
+ compiler for this to work.
3
4
 
@@ -0,0 +1,38 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+ $:.unshift File.expand_path(File.dirname(__FILE__))
3
+ require 'cod'
4
+ require 'example_scaffold'
5
+
6
+ 2.times do
7
+ client {
8
+ chan = Cod.tcp('localhost:1234')
9
+ chan.put :get_client_id
10
+ p [:client_id, chan.get]
11
+ }
12
+ end
13
+
14
+ server {
15
+ server = Cod.tcp_server('localhost:1234')
16
+ client_id = 0
17
+
18
+ loop do
19
+ msg, chan = server.get_ext
20
+
21
+ p [:incoming, msg, chan.object_id.to_s(16)]
22
+ begin
23
+ case msg
24
+ when :get_client_id
25
+ client_id += 1
26
+ chan.put client_id
27
+ end
28
+
29
+ ensure
30
+ # Closes connections after one request.
31
+ chan.close
32
+ end
33
+
34
+ break if client_id >= 2
35
+ end
36
+ }
37
+
38
+ run
@@ -4,7 +4,7 @@ module Cod
4
4
  # answer. It solves problems related to timeouts, getting _your_ answer and
5
5
  # not any kind of answer, etc...
6
6
  #
7
- # Synopsis:
7
+ # == Synopsis
8
8
  # # On the server end:
9
9
  # service = server_channel.service
10
10
  # service.one { |request| :answer }
@@ -40,6 +40,8 @@ module Cod
40
40
  # Use Cod::Client to perform the service call. This will keep track of
41
41
  # messages sent and answers received and a couple of other things.
42
42
  #
43
+ # @yield [Object] request made by the client. The return value of the
44
+ # block will be returned to the client.
43
45
  #
44
46
  def one
45
47
  rq, answer_chan = @channel.get
@@ -25,12 +25,15 @@ module Cod
25
25
  @serializer = serializer
26
26
  @destination = destination
27
27
 
28
+ # TODO remove this soon
29
+ fail "Deprecated API" if destination.kind_of?(TCPSocket)
30
+
28
31
  # TcpClient handles two cases: Construction via an url (destination is a
29
32
  # string) and construction via a connection that has been
30
33
  # preestablished (destination is a socket):
31
- if destination.respond_to?(:read)
32
- # destination seems to be a socket, wrap it with Connection
33
- @connection = Connection.new(destination)
34
+ if destination.respond_to?(:established?)
35
+ # Should be a connection already.
36
+ @connection = destination
34
37
  else
35
38
  @connection = RobustConnection.new(destination)
36
39
  end
@@ -115,6 +118,8 @@ module Cod
115
118
  # the other end (the deserializing end). What the deserializing code does
116
119
  # with this is his problem.
117
120
  #
121
+ # @private
122
+ #
118
123
  OtherEnd = Struct.new(:destination) # :nodoc:
119
124
 
120
125
  def _dump(level) # :nodoc:
@@ -235,9 +240,12 @@ module Cod
235
240
  # the TcpServers clients: the tcp server manages the back channels, so
236
241
  # the created channel is lent its socket only.
237
242
  #
243
+ # @private
244
+ #
238
245
  class Connection # :nodoc:
239
- def initialize(socket)
240
- @socket = socket.dup
246
+ def initialize(socket, owner)
247
+ @owner = owner
248
+ @socket = socket
241
249
  end
242
250
  attr_reader :socket
243
251
  def try_connect
@@ -252,7 +260,7 @@ module Cod
252
260
  @socket.write(buffer)
253
261
  end
254
262
  def close
255
- @socket.close
263
+ @owner.request_close(socket)
256
264
  end
257
265
  end
258
266
  end
@@ -4,7 +4,7 @@ module Cod
4
4
  # A tcp server channel. Messages are read from any of the connected sockets
5
5
  # in a round robin fashion.
6
6
  #
7
- # Synopsis:
7
+ # == Synopsis
8
8
  # server = Cod.tcp_server('localhost:12345')
9
9
  # server.get # 'a message'
10
10
  # msg, chan = server.get_ext
@@ -13,13 +13,11 @@ module Cod
13
13
  # connected sockets, this is up to you to implement. Instead, you can use
14
14
  # one of two ways to obtain a channel for talking back to a specific client:
15
15
  #
16
- # Using #get_ext:
17
16
  # msg, chan = server.get_ext
18
- #
17
+ #
19
18
  # chan is a two way connected channel to the specific client that has opened
20
19
  # its communication with msg.
21
20
  #
22
- # Using plain #get:
23
21
  # # on the client:
24
22
  # client.put [client, :msg]
25
23
  # # on the server
@@ -37,10 +35,14 @@ module Cod
37
35
  @serializer = serializer
38
36
  end
39
37
 
40
- # Receives one object from the channel.
38
+ # Receives one object from the channel. This will receive one message from
39
+ # one of the connected clients in a round-robin fashion.
41
40
  #
42
- # Example:
41
+ # @example
43
42
  # channel.get # => object
43
+ #
44
+ # @param opts [Hash]
45
+ # @return [Object] message sent by one of the clients
44
46
  #
45
47
  def get(opts={})
46
48
  msg, socket = _get(opts)
@@ -54,14 +56,18 @@ module Cod
54
56
  # Using this method, the server can communicate back to its clients
55
57
  # individually instead of collectively.
56
58
  #
57
- # Example:
59
+ # @example Answering to the client that sent a specific message
58
60
  # msg, chan = server.get_ext
59
61
  # chan.put :answer
62
+ #
63
+ # @param opts [Hash]
64
+ # @return [Array<Object, TcpClient>] tuple of the message that was sent
65
+ # a channel back to the client that sent the message
60
66
  def get_ext(opts={})
61
67
  msg, socket = _get(opts)
62
68
  return [
63
69
  msg,
64
- TcpClient.new(socket, @serializer)]
70
+ produce_back_channel(socket)]
65
71
  end
66
72
 
67
73
  # Closes the channel.
@@ -89,37 +95,65 @@ module Cod
89
95
  def service
90
96
  Service.new(self)
91
97
  end
92
- # NOTE: It is really more convenient to just construct a Cod.tcp_client
98
+
99
+ # @note It is really more convenient to just construct a Cod.tcp_client
93
100
  # and ask that for a client object. In the case of TCP, this is enough.
94
101
  #
95
102
  def client(answers_to)
96
103
  Service::Client.new(answers_to, answers_to)
97
104
  end
98
105
 
106
+ # ------------------------------------------------------- connection owner
107
+
108
+ # Notifies the TcpServer that one of its connections needs to be closed.
109
+ # This can be triggered by using #get_ext to obtain a handle to connections
110
+ # and then calling #close on that connection.
111
+ #
112
+ # @param socket [TCPSocket] the socket that needs to be closed
113
+ # @return [void]
114
+ #
115
+ def request_close(socket)
116
+ @client_sockets.delete(socket)
117
+ socket.close
118
+ end
119
+
99
120
  private
100
121
  def _get(opts)
101
122
  loop do
102
- # Check if there are pending connects
103
- accept_new_connections
123
+ # Return a buffered message if there is one left.
124
+ return @messages.shift unless @messages.empty?
104
125
 
105
- # shuffle the socket list around, so we don't always read from the
106
- # same client.
126
+ # Shuffle the socket list around, so we don't always read from the
127
+ # same client first.
107
128
  socket_list = round_robin(@client_sockets)
108
-
109
- # select for readiness
110
- rr, rw, re = IO.select(socket_list, nil, nil, 0.1)
129
+
130
+ # Append the server socket to be able to react to new connections
131
+ # that are made.
132
+ socket_list << @socket
133
+
134
+ # Sleep until either a new connection is made or data is available on
135
+ # one of the old connections.
136
+ rr, _, _ = IO.select(socket_list, nil, nil)
111
137
  next unless rr
112
138
 
113
- rr.each do |io|
114
- if io.eof?
115
- @client_sockets.delete(io)
116
- io.close
117
- else
118
- consume_pending io, opts
119
- end
139
+ # Accept new connections
140
+ if rr.include?(@socket)
141
+ accept_new_connections
142
+ rr.delete(@socket)
120
143
  end
121
144
 
122
- return @messages.shift unless @messages.empty?
145
+ handle_socket_events(rr, opts)
146
+ end
147
+ end
148
+
149
+ def handle_socket_events(sockets, opts)
150
+ sockets.each do |io|
151
+ if io.eof?
152
+ @client_sockets.delete(io)
153
+ io.close
154
+ else
155
+ consume_pending io, opts
156
+ end
123
157
  end
124
158
  end
125
159
 
@@ -130,14 +164,14 @@ module Cod
130
164
  io]
131
165
 
132
166
  # More messages from this socket?
133
- return unless IO.select([io], nil, nil, 0.01)
167
+ return unless IO.select([io], nil, nil, 0.0001)
134
168
  end
135
169
  end
136
170
 
137
171
  def deserialize(io)
138
172
  @serializer.de(io) { |obj|
139
173
  obj.kind_of?(TcpClient::OtherEnd) ?
140
- TcpClient.new(io, @serializer) :
174
+ produce_back_channel(io) :
141
175
  obj
142
176
  }
143
177
  end
@@ -161,5 +195,11 @@ module Cod
161
195
  rescue Errno::EAGAIN
162
196
  # This means that there are no sockets to accept. Continue.
163
197
  end
198
+
199
+ def produce_back_channel(socket)
200
+ TcpClient.new(
201
+ TcpClient::Connection.new(socket, self),
202
+ @serializer)
203
+ end
164
204
  end
165
205
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cod
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-28 00:00:00.000000000 Z
12
+ date: 2012-03-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70357075424760 !ruby/object:Gem::Requirement
16
+ requirement: &70125556634640 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70357075424760
24
+ version_requirements: *70125556634640
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70357075424320 !ruby/object:Gem::Requirement
27
+ requirement: &70125556634180 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70357075424320
35
+ version_requirements: *70125556634180
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: flexmock
38
- requirement: &70357075423900 !ruby/object:Gem::Requirement
38
+ requirement: &70125556633740 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70357075423900
46
+ version_requirements: *70125556633740
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: yard
49
- requirement: &70357075423480 !ruby/object:Gem::Requirement
49
+ requirement: &70125556633260 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70357075423480
57
+ version_requirements: *70125556633260
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: guard
60
- requirement: &70357075423060 !ruby/object:Gem::Requirement
60
+ requirement: &70125556632740 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70357075423060
68
+ version_requirements: *70125556632740
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: growl
71
- requirement: &70357075422640 !ruby/object:Gem::Requirement
71
+ requirement: &70125556632280 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70357075422640
79
+ version_requirements: *70125556632280
80
80
  description:
81
81
  email: kaspar.schiess@absurd.li
82
82
  executables: []
@@ -84,7 +84,6 @@ extensions: []
84
84
  extra_rdoc_files:
85
85
  - README
86
86
  files:
87
- - Gemfile
88
87
  - HISTORY.txt
89
88
  - LICENSE
90
89
  - Rakefile
@@ -114,16 +113,11 @@ files:
114
113
  - examples/master_child.rb
115
114
  - examples/netcat/README
116
115
  - examples/netcat/server.rb
117
- - examples/presence/client.rb
118
- - examples/presence/server.rb
119
116
  - examples/protocol-buffers/master_child.rb
120
117
  - examples/protocol-buffers/README
121
- - examples/queue/client.rb
122
- - examples/queue/queue.rb
123
- - examples/queue/README
124
- - examples/queue/send.rb
125
118
  - examples/service.rb
126
119
  - examples/tcp.rb
120
+ - examples/tcp_get_ext.rb
127
121
  homepage: http://kschiess.github.com/cod
128
122
  licenses: []
129
123
  post_install_message:
@@ -140,7 +134,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
134
  version: '0'
141
135
  segments:
142
136
  - 0
143
- hash: -4089674409401429686
137
+ hash: -763744089584274604
144
138
  required_rubygems_version: !ruby/object:Gem::Requirement
145
139
  none: false
146
140
  requirements:
@@ -149,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
143
  version: '0'
150
144
  segments:
151
145
  - 0
152
- hash: -4089674409401429686
146
+ hash: -763744089584274604
153
147
  requirements: []
154
148
  rubyforge_project:
155
149
  rubygems_version: 1.8.16
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gemspec
@@ -1,15 +0,0 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
- require 'cod'
3
-
4
-
5
- raise unless ARGV.first
6
-
7
- client = Cod.tcp('localhost:12345')
8
- client.put [client, ARGV.first]
9
-
10
- puts "Waiting..."
11
- $stdin.gets
12
- client.close
13
- $stdin.gets
14
-
15
-
@@ -1,30 +0,0 @@
1
-
2
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
3
- require 'cod'
4
-
5
-
6
- present = {}
7
- server = Cod.tcpserver('localhost:12345')
8
-
9
- loop do
10
- # Process connection requests
11
- while server.waiting?
12
- connection, attributes = server.get
13
-
14
- present[connection] = attributes
15
- end
16
-
17
- # Check if all connections are alive
18
- remove = []
19
- present.each do |conn, attrs|
20
- if conn.connected?
21
- puts "Alive: #{attrs.inspect}"
22
- else
23
- puts "Dead: #{attrs.inspect}"
24
- remove << conn
25
- end
26
- end
27
-
28
- remove.each { |conn| present.delete(conn) }
29
- sleep 1
30
- end
@@ -1,9 +0,0 @@
1
- A small queue like example.
2
-
3
- run ruby send.rb N to send N messages through queue.rb to client.rb.
4
- Client will print statistics (calls per second) every 100 messages.
5
-
6
- Problems:
7
- - queue.rb seems to always consume 100% CPU
8
- - number of messages per second is way low
9
- - some exceptions are still not handled correctly
@@ -1,31 +0,0 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
2
- require 'cod'
3
-
4
- queue = Cod.tcp('localhost:12345')
5
-
6
- queue.put [:join, queue]
7
-
8
- class CallCounter
9
- attr_reader :n
10
- def initialize
11
- @n = 0
12
- @last_lap = [0, Time.now]
13
- end
14
- def inc
15
- @n += 1
16
- end
17
- def calls_per_sec
18
- ln, ll = @last_lap
19
- @last_lap = [@n, Time.now]
20
-
21
- (@n - ln) / (Time.now - ll)
22
- end
23
- end
24
-
25
- cc = CallCounter.new
26
- loop do
27
- wi = queue.get
28
-
29
- cc.inc
30
- puts cc.calls_per_sec if cc.n%100==0
31
- end
@@ -1,51 +0,0 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
2
- require 'cod'
3
-
4
- class Queue
5
- def initialize(url)
6
- @url = url
7
- connect
8
- end
9
-
10
- def connect
11
- @clients = []
12
- @server = Cod.tcpserver(@url)
13
- end
14
-
15
- def run
16
- loop do
17
- handle_commands
18
-
19
- check_connections
20
- end
21
- end
22
-
23
- def handle_commands
24
- while @server.waiting?
25
- cmd, *rest = @server.get
26
-
27
- dispatch_command cmd, rest
28
- end
29
- end
30
-
31
- def dispatch_command(cmd, rest)
32
- self.send("cmd_#{cmd}", *rest)
33
- end
34
-
35
- def cmd_join(connection)
36
- puts "Join at #{Time.now}."
37
- @clients << connection
38
- end
39
-
40
- def cmd_work_item
41
- @clients.each do |client|
42
- client.put :work_item
43
- end
44
- end
45
-
46
- def check_connections
47
- @clients.keep_if { |client| client.connected? }
48
- end
49
- end
50
-
51
- Queue.new('localhost:12345').run
@@ -1,9 +0,0 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
2
- require 'cod'
3
-
4
- queue = Cod.tcp('localhost:12345')
5
-
6
- n = Integer(ARGV.first)
7
- n.times do
8
- queue.put :work_item
9
- end