cod 0.4.3 → 0.4.4

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