cod 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,12 @@
1
+ = 0.4.3 / 28Feb2012
2
+
3
+ ! Fixes a few bugs when using tcp channels (connection was not made or not
4
+ closed properly).
5
+
6
+ + Cod::ConnectionLost should be raised when the connection goes down. This
7
+ allows simply reconnecting on error, see
8
+ examples/bs_ping_pong/{ping,pong}.rb on how to do this.
9
+
1
10
  = 0.4.2 / 15Feb2012
2
11
 
3
12
  ! Actively removes clients that have closed the sockets on their end
data/README CHANGED
@@ -23,11 +23,18 @@ SYNOPSIS
23
23
 
24
24
  STATUS
25
25
 
26
- Complete rewrite of the code: Did away with some of the complexities that
27
- stemmed from early design work. The functionality that is there is much like
28
- that we had before, but some things have been designed more logically.
26
+ Working library. Some rough edges and potential for growth. Have a look at the
27
+ Cod module to get started.
29
28
 
30
- At version 0.4.0
29
+ Working transports include:
30
+
31
+ * process (spawn, connecting to $stdout and $stdin)
32
+ * stdio (connects to $stdout and $stdin of current process)
33
+ * pipe (akin to IO.pipe)
34
+ * tcp (server and client)
35
+ * beanstalk (connects to beanstalkd)
36
+
37
+ At version 0.4.3
31
38
 
32
39
  (c) 2011 Kaspar Schiess
33
40
 
data/Rakefile CHANGED
@@ -2,6 +2,7 @@ require "rubygems"
2
2
  require "rdoc/task"
3
3
  require 'rspec/core/rake_task'
4
4
  require 'rubygems/package_task'
5
+ require 'rake/clean'
5
6
 
6
7
  desc "Run all tests: Exhaustive."
7
8
  RSpec::Core::RakeTask.new
@@ -15,22 +16,12 @@ task :stats do
15
16
  end
16
17
  end
17
18
 
18
- require 'sdoc'
19
-
20
- # Generate documentation
21
- RDoc::Task.new do |rdoc|
22
- rdoc.title = "cod - IPC made really simple."
23
- rdoc.options << '--line-numbers'
24
- rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
25
- rdoc.template = 'direct' # lighter template used on railsapi.com
26
- rdoc.main = "README"
27
- rdoc.rdoc_files.include("README", "lib/**/*.rb")
28
- rdoc.rdoc_dir = "rdoc"
19
+ require 'yard'
20
+ YARD::Rake::YardocTask.new do |t|
21
+ # t.files = ['lib/**/*.rb']
22
+ # t.options = ['--any', '--extra', '--opts'] # optional
29
23
  end
30
24
 
31
- desc 'Clear out RDoc'
32
- task :clean => [:clobber_rdoc, :clobber_package]
33
-
34
25
  # This task actually builds the gem.
35
26
  task :gem => :spec
36
27
  spec = eval(File.read('cod.gemspec'))
@@ -39,3 +30,6 @@ desc "Generate the gem package."
39
30
  Gem::PackageTask.new(spec) do |pkg|
40
31
  # pkg.need_tar = true
41
32
  end
33
+
34
+ CLEAN << 'pkg'
35
+ CLEAN << 'doc'
@@ -0,0 +1,15 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
2
+ require 'cod'
3
+
4
+ begin
5
+ pipe ||= Cod.beanstalk('pingpong', 'localhost:11300')
6
+
7
+ loop do
8
+ pipe.put Time.now
9
+ sleep 1
10
+ end
11
+ rescue Cod::ConnectionLost
12
+ pipe.close
13
+ pipe = nil
14
+ retry
15
+ end
@@ -0,0 +1,13 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
2
+ require 'cod'
3
+
4
+ begin
5
+ pipe ||= Cod.beanstalk("pingpong", 'localhost:11300')
6
+
7
+ loop do
8
+ puts "Received: "+pipe.get.inspect
9
+ end
10
+ rescue Cod::ConnectionLost
11
+ pipe.close; pipe = nil
12
+ retry
13
+ end
@@ -9,7 +9,7 @@ service_channel = Cod.pipe
9
9
  answer_channel = Cod.pipe
10
10
 
11
11
  child_pid = fork do
12
- service = Cod.service(service_channel)
12
+ service = service_channel.service()
13
13
  service.one { |call|
14
14
  puts "Service got called with #{call.inspect}"
15
15
  time = Time.now
@@ -17,7 +17,7 @@ child_pid = fork do
17
17
  time }
18
18
  end
19
19
 
20
- client = Cod.client(service_channel, answer_channel)
20
+ client = service_channel.client(answer_channel)
21
21
  puts "Calling service..."
22
22
  answer = client.call('42')
23
23
 
data/lib/cod.rb CHANGED
@@ -1,8 +1,10 @@
1
- # The core concept of Cod are 'channels'. (Cod::Channel::Base) You can create
2
- # such channels on top of the various transport layers. Once you have such a
3
- # channel, you #put messages into it and you #get messages out of it. Messages
4
- # are retrieved in FIFO manner, making channels look like a communication pipe
5
- # most of the time.
1
+ require 'stringio'
2
+
3
+ # The core concept of Cod are 'channels'. (see {Cod::Channel::Base}) You can
4
+ # create such channels on top of the various transport layers. Once you have
5
+ # such a channel, you #put messages into it and you #get messages out of it.
6
+ # Messages are retrieved in FIFO manner, making channels look like a
7
+ # communication pipe most of the time.
6
8
  #
7
9
  # Cod also brings a few abstractions layered on top of channels: You can use
8
10
  # channels to present 'services' (Cod::Service) to the network: A service is a
@@ -13,6 +15,12 @@
13
15
  # channel! This is really powerful and used extensively in constructing the
14
16
  # higher order primitives.
15
17
  #
18
+ # All Cod channels have a serializer. If you don't specify your own
19
+ # serializer, they will use Marshal.dump and Marshal.load. (see
20
+ # {Cod::SimpleSerializer}) This allows to send Ruby objects and not just
21
+ # strings by default. If you want to, you can of course go back to very strict
22
+ # wire formats, see {Cod::ProtocolBuffersSerializer} for an example of that.
23
+ #
16
24
  # The goal of Cod is that you have to know only very few things about the
17
25
  # network (the various transports) to be able to construct complex things. It
18
26
  # handles reconnection and reliability for you. It also translates cryptic OS
@@ -22,18 +30,24 @@
22
30
  # Contribute your observations and we'll come up with a way of dealing with
23
31
  # most of the tricky stuff!
24
32
  #
33
+ # @see Cod::Channel
34
+ #
25
35
  module Cod
26
36
  # Creates a pipe connection that is visible to this process and its
27
- # children. (see Cod::Pipe)
37
+ # children.
38
+ #
39
+ # @see Cod::Pipe
28
40
  #
29
41
  def pipe(serializer=nil, pipe_pair=nil)
30
42
  Cod::Pipe.new(serializer)
31
43
  end
32
44
  module_function :pipe
33
45
 
34
- # Creates two channels based on Cod.pipe (unidirectional IO.pipe) and links
46
+ # Creates two channels based on {Cod.pipe} (unidirectional IO.pipe) and links
35
47
  # things up so that you communication is bidirectional. Writes go to
36
- # #out and reads come from #in.
48
+ # #out and reads come from #in.
49
+ #
50
+ # @see Cod::BidirPipe
37
51
  #
38
52
  def bidir_pipe(serializer=nil, pipe_pair=nil)
39
53
  Cod::BidirPipe.new(serializer, pipe_pair)
@@ -41,7 +55,8 @@ module Cod
41
55
  module_function :bidir_pipe
42
56
 
43
57
  # Creates a tcp connection to the destination and returns a channel for it.
44
- # (see Cod::TcpClient)
58
+ #
59
+ # @see Cod::TcpClient
45
60
  #
46
61
  def tcp(destination, serializer=nil)
47
62
  Cod::TcpClient.new(
@@ -50,16 +65,20 @@ module Cod
50
65
  end
51
66
  module_function :tcp
52
67
 
53
- # Creates a tcp listener on bind_to and returns a channel for it. (see
54
- # Cod::TcpServer)
68
+ # Creates a tcp listener on bind_to and returns a channel for it.
69
+ #
70
+ # @see Cod::TcpServer
55
71
  #
56
- def tcp_server(bind_to)
57
- Cod::TcpServer.new(bind_to)
72
+ def tcp_server(bind_to, serializer=nil)
73
+ Cod::TcpServer.new(
74
+ bind_to,
75
+ serializer || SimpleSerializer.new)
58
76
  end
59
77
  module_function :tcp_server
60
78
 
61
- # Creates a channel based on the beanstalkd messaging queue. (see
62
- # Cod::Beanstalk::Channel)
79
+ # Creates a channel based on the beanstalkd messaging queue.
80
+ #
81
+ # @see Cod::Beanstalk::Channel
63
82
  #
64
83
  def beanstalk(tube_name, server=nil)
65
84
  Cod::Beanstalk::Channel.new(tube_name, server||'localhost:11300')
@@ -71,6 +90,9 @@ module Cod
71
90
  #
72
91
  # Example:
73
92
  # pid, channel = Cod.process('cat')
93
+ #
94
+ # @see Cod::Process
95
+ #
74
96
  def process(command, serializer=nil)
75
97
  Cod::Process.new(command, serializer)
76
98
  end
@@ -80,6 +102,8 @@ module Cod
80
102
  # pipes #put method will print to stdout, and the #get method will read from
81
103
  # stdin.
82
104
  #
105
+ # @see Cod::Pipe
106
+ #
83
107
  def stdio(serializer=nil)
84
108
  Cod::Pipe.new(serializer, [$stdin, $stdout])
85
109
  end
@@ -102,6 +126,14 @@ module Cod
102
126
  super("This channel is read only, attempted write operation.")
103
127
  end
104
128
  end
129
+
130
+ # Indicates that a standing connection was lost and must be reconnected.
131
+ #
132
+ class ConnectionLost < StandardError
133
+ def initialize
134
+ super "Connection lost."
135
+ end
136
+ end
105
137
  end
106
138
 
107
139
  require 'cod/select_group'
@@ -1,14 +1,19 @@
1
1
  module Cod::Beanstalk
2
2
 
3
- # NOTE: Beanstalk channels cannot currently be used in Cod.select. This is
4
- # due to limitations inherent in the beanstalkd protocol. We'll probably
5
- # try to get a patch into beanstalkd to change this.
3
+ # A channel based on a beanstalkd tube. A {#put} will insert messages into
4
+ # the tube, and a {#get} will fetch the next message that is pending on the
5
+ # tube.
6
6
  #
7
- # NOTE: If you embed a beanstalk channel into one of your messages, you will
8
- # get a channel that connects to the same server and the same tube on the
9
- # other end. This behaviour is useful for Cod::Service.
7
+ # @note Beanstalk channels cannot currently be used in Cod.select. This is
8
+ # due to limitations inherent in the beanstalkd protocol. We'll probably
9
+ # try to get a patch into beanstalkd to change this.
10
+ #
11
+ # @note If you embed a beanstalk channel into one of your messages, you will
12
+ # get a channel that connects to the same server and the same tube on the
13
+ # other end. This behaviour is useful for {Cod::Service}.
10
14
  #
11
15
  class Channel < Cod::Channel
16
+ # All messages get inserted into the beanstalk queue as this priority.
12
17
  JOB_PRIORITY = 0
13
18
 
14
19
  # Which tube this channel is connected to
@@ -55,6 +60,7 @@ module Cod::Beanstalk
55
60
  @transport.close
56
61
  end
57
62
 
63
+ # @private
58
64
  def to_read_fds
59
65
  fail "Cod.select not supported with beanstalkd channels.\n"+
60
66
  "To support this, we will have to extend the beanstalkd protocol."
@@ -69,6 +75,40 @@ module Cod::Beanstalk
69
75
  end
70
76
 
71
77
  # -------------------------------------------------------- queue interface
78
+
79
+ # Like {#get}, read next message from the channel but reserve the right
80
+ # to put it back. This uses beanstalkds flow control features to be able
81
+ # to control message flow in the case of exceptions and the like.
82
+ #
83
+ # If the block given to this message raises an exception, the message
84
+ # is released unless a control command has been given. This means that
85
+ # other workers on the same tube will get the chance to see the message.
86
+ #
87
+ # If the block is exited without specifying a fate for the message, it
88
+ # is deleted from the tube.
89
+ #
90
+ # @yield [Object, Cod::Beanstalk::Channel::Control]
91
+ # @return the blocks return value
92
+ #
93
+ # @example All the flow control that beanstalkd allows
94
+ # channel.try_get { |msg, ctl|
95
+ # if msg == 1
96
+ # ctl.release # don't handle messages of type 1
97
+ # else
98
+ # ctl.bury # for example
99
+ # end
100
+ # }
101
+ #
102
+ # @example Exceptions release the message
103
+ # # Will release the message and allow other connected channels to
104
+ # # #get it.
105
+ # channel.try_get { |msg, ctl|
106
+ # fail "No such message handler"
107
+ # }
108
+ #
109
+ #
110
+ # @see Cod::Beanstalk::Channel::Control
111
+ #
72
112
  def try_get
73
113
  fail "No block given to #try_get" unless block_given?
74
114
 
@@ -124,25 +164,31 @@ module Cod::Beanstalk
124
164
  end
125
165
 
126
166
  # ---------------------------------------------------------- serialization
167
+ # @private
127
168
  def _dump(level) # :nodoc:
128
169
  Marshal.dump(
129
170
  [@tube_name, @server_url])
130
171
  end
172
+ # @private
131
173
  def self._load(str) # :nodoc:
132
174
  tube_name, server_url = Marshal.load(str)
133
175
  Cod.beanstalk(tube_name, server_url)
134
176
  end
135
177
 
136
178
  # ----------------------------------------------------- beanstalk commands
137
- def bs_delete(msg_id) # :nodoc:
179
+ # @private
180
+ def bs_delete(msg_id)
138
181
  bs_command([:delete, msg_id], :deleted)
139
182
  end
140
- def bs_release(msg_id) # :nodoc:
183
+ # @private
184
+ def bs_release(msg_id)
141
185
  bs_command([:release, msg_id, JOB_PRIORITY, 0], :released)
142
186
  end
143
- def bs_release_with_delay(msg_id, seconds) # :nodoc:
187
+ # @private
188
+ def bs_release_with_delay(msg_id, seconds)
144
189
  bs_command([:release, msg_id, JOB_PRIORITY, seconds], :released)
145
190
  end
191
+ # @private
146
192
  def bs_bury(msg_id)
147
193
  # NOTE: Why I need to assign a priority when burying I fail to
148
194
  # understand. Like a priority for rapture?
@@ -36,6 +36,8 @@ module Cod::Beanstalk
36
36
 
37
37
  def de(io)
38
38
  str = io.gets("\r\n")
39
+ raise Cod::ConnectionLost unless str
40
+
39
41
  raw = str.split
40
42
 
41
43
  cmd = convert_cmd(raw.first)
@@ -1,4 +1,14 @@
1
1
  module Cod::Beanstalk
2
+ # Beanstalk services specialize for the beanstalk channels in that they
3
+ # support a second service block argument, the control. Using this argument,
4
+ # your service can refuse to accept a request or bury it for debugging
5
+ # inspection.
6
+ #
7
+ # @example Additional block argument
8
+ # service.one { |request, control|
9
+ # control.retry_in(1) # release message with delay
10
+ # }
11
+ #
2
12
  class Service < Cod::Service
3
13
  def one(&block)
4
14
  @channel.try_get { |(rq, answer_chan), control|
@@ -21,6 +31,12 @@ module Cod::Beanstalk
21
31
  @channel_control = channel_control
22
32
  end
23
33
 
34
+ # Releases the request and instructs the beanstalkd server to hand it
35
+ # to us again in seconds seconds.
36
+ #
37
+ # @param [Fixnum] seconds
38
+ # @return [void]
39
+ #
24
40
  def retry_in(seconds)
25
41
  fail ArgumentError,
26
42
  "#retry_in accepts only an integer number of seconds." \
@@ -28,16 +44,36 @@ module Cod::Beanstalk
28
44
 
29
45
  @channel_control.release_with_delay(seconds)
30
46
  end
47
+
48
+ # Releases the request for immediate consumption by someone else.
49
+ #
50
+ # @return [void]
51
+ #
31
52
  def retry
32
53
  @channel_control.release
33
54
  end
55
+
56
+ # Buries the message for later inspection. (see beanstalkd manual)
57
+ #
58
+ # @return [void]
59
+ #
34
60
  def bury
35
61
  @channel_control.bury
36
62
  end
63
+
64
+ # Deletes the request. This is how you accept a request.
65
+ #
66
+ # @return [void]
67
+ #
37
68
  def delete
38
69
  @channel_control.delete
39
70
  end
40
-
71
+
72
+ # Returns true if a flow control command has already been given as answer
73
+ # to the current request. Multiple flow control commands are not allowed.
74
+ #
75
+ # @return [Boolean]
76
+ #
41
77
  def command_issued?
42
78
  @channel_control.command_given?
43
79
  end
@@ -1,33 +1,39 @@
1
1
  module Cod
2
- # Channels transport ruby objects from one end to the other. The
2
+ # Channels transport Ruby objects from one end to the other. The
3
3
  # communication setup varies a bit depending on the transport used for the
4
4
  # channel, but the interface you interact with doesn't vary. You can #put
5
5
  # messages into a channel and you then #get them out of it.
6
6
  #
7
- # Synopsis:
7
+ # = Synopsis
8
+ #
8
9
  # channel.put [:a, :ruby, :object]
9
10
  # channel.get # => [:a, :ruby, :object]
10
11
  #
11
12
  # By default, channels will serialize the messages you give them using
12
13
  # Marshal.dump and Marshal.load. You can change this by passing your own
13
- # serializer to the channel upon construction; see SimpleSerializer for a
14
+ # serializer to the channel upon construction; see {SimpleSerializer} for a
14
15
  # description of the interface such a serializer needs to implement.
15
16
  #
16
- # This class (Cod::Channel) is the abstract superclass of all Cod channels.
17
- # It doesn't have a transport by its own, but implements the whole interface
18
- # for documentation purposes.
17
+ # This class is the abstract superclass of all Cod channels. It doesn't have
18
+ # a transport by its own, but implements the whole interface for
19
+ # documentation purposes.
19
20
  #
20
21
  class Channel
21
22
  # Obtains one message from the channel. If the channel is empty, but
22
23
  # theoretically able to receive more messages, blocks forever. But if the
23
24
  # channel is somehow broken, an exception is raised.
24
25
  #
26
+ # @return [Object] the next message waiting in the channel
27
+ #
25
28
  def get
26
29
  abstract_method_error
27
30
  end
28
31
 
29
32
  # Puts one message into a channel.
30
33
  #
34
+ # @param msg [Object] any Ruby object that should be sent as message
35
+ # @return [void]
36
+ #
31
37
  def put(msg)
32
38
  abstract_method_error
33
39
  end
@@ -35,6 +41,9 @@ module Cod
35
41
  # Interact with a channel by first writing msg to it, then reading back
36
42
  # the other ends answer.
37
43
  #
44
+ # @param msg [Object] any Ruby object that should be sent as message
45
+ # @return [Object] the answer from the other end.
46
+ #
38
47
  def interact(msg)
39
48
  put msg
40
49
  get
@@ -42,6 +51,8 @@ module Cod
42
51
 
43
52
  # Produces a service that has this channel as communication point.
44
53
  #
54
+ # @return [Cod::Service]
55
+ #
45
56
  def service
46
57
  abstract_method_error
47
58
  end
@@ -49,6 +60,9 @@ module Cod
49
60
  # Produces a service client that connects to this channel and receives
50
61
  # service answers to the channel indicated by answers_to.
51
62
  #
63
+ # @param answers_to [Cod::Channel] where to send the answers
64
+ # @return [Cod::Service::Client]
65
+ #
52
66
  def client(answers_to)
53
67
  abstract_method_error
54
68
  end
@@ -6,11 +6,11 @@ module Cod
6
6
  #
7
7
  # Synopsis:
8
8
  # # On the server end:
9
- # service = Cod.service(central_location)
9
+ # service = server_channel.service
10
10
  # service.one { |request| :answer }
11
11
  #
12
12
  # # On the client end:
13
- # service = Cod.client(central_location, answer_here)
13
+ # service = answer_channel.client(server_channel)
14
14
  #
15
15
  # # asynchronous, no answer
16
16
  # service.notify [:a, :request] # => nil
@@ -4,10 +4,25 @@ module Cod
4
4
  # format serializers.
5
5
  #
6
6
  class SimpleSerializer
7
+ # Serializes obj into a format that can be transmitted via the wire. In
8
+ # this implementation, it will use Marshal.dump to turn the obj into a
9
+ # string.
10
+ #
11
+ # @param obj [Object] to dump
12
+ # @return [String] transmitted over the wire
13
+ #
7
14
  def en(obj)
8
15
  Marshal.dump(obj)
9
16
  end
10
17
 
18
+ # Reads as many bytes as needed from io to reconstruct one message. Turns
19
+ # the message back into a Ruby object according to the rules of the serializer.
20
+ # In this implementation, it will use Marshal.load to turn the object
21
+ # from a String to a Ruby Object.
22
+ #
23
+ # @param io [IO] to read one message from
24
+ # @return [Object] that has been deserialized
25
+ #
11
26
  def de(io)
12
27
  if block_given?
13
28
  Marshal.load(io, Proc.new)
@@ -4,7 +4,23 @@ module Cod
4
4
  # Acts as a channel that connects to a tcp listening socket on the other
5
5
  # end.
6
6
  #
7
+ # Connection negotiation has three phases, as follows:
8
+ # 1) Connection is establishing. Sent messages are buffered and really sent
9
+ # down the wire once the connection stands. Reading from the channel
10
+ # will block the client forever.
11
+ #
12
+ # 2) Connection is established: Sending and receiving are immediate and
13
+ # no buffering is done.
14
+ #
15
+ # 3) Connection is down because of an interruption or exception. Sending and
16
+ # receiving messages no longer works, instead a ConnectionLost error is
17
+ # raised.
18
+ #
7
19
  class TcpClient < Channel
20
+ # Constructs a tcp client channel. destination may either be a socket,
21
+ # in which case phase 1) of connection negotiation is skipped, or a string
22
+ # that contains an 'address:port' part.
23
+ #
8
24
  def initialize(destination, serializer)
9
25
  @serializer = serializer
10
26
  @destination = destination
@@ -23,10 +39,7 @@ module Cod
23
39
 
24
40
  # The predicate for allowing sends: Is the connection up?
25
41
  @work_queue.predicate {
26
- # NOTE This will not be called unless we have some messages to send,
27
- # so no useless connections are made
28
- @connection.try_connect
29
- @connection.established?
42
+ cached_connection_established?
30
43
  }
31
44
  end
32
45
 
@@ -35,12 +48,12 @@ module Cod
35
48
  # reconnection attempts.
36
49
  #
37
50
  def close
38
- @work_queue.shutdown
51
+ @work_queue.shutdown if @work_queue
39
52
  @connection.close
40
53
  end
41
54
 
42
- # Sends an object to the other end of the channel, if it is connected.
43
- # If it is not connected, objects sent will queue up and once the internal
55
+ # Sends an object to the other end of the channel, if it is connected. If
56
+ # it is not connected, objects sent will queue up and once the internal
44
57
  # storage reaches the high watermark, they will be dropped silently.
45
58
  #
46
59
  # Example:
@@ -55,6 +68,12 @@ module Cod
55
68
  send(obj)
56
69
  }
57
70
 
71
+ @work_queue.exclusive {
72
+ # If we're connected, shut down the worker thread, do all the work
73
+ # here.
74
+ check_connection_state
75
+ }
76
+
58
77
  @work_queue.try_work
59
78
  end
60
79
 
@@ -62,8 +81,16 @@ module Cod
62
81
  # Options include:
63
82
  #
64
83
  def get(opts={})
65
- @connection.try_connect
84
+ while @work_queue.size > 0
85
+ @work_queue.try_work
86
+ end
87
+
88
+ check_connection_state
89
+
66
90
  @connection.read(@serializer)
91
+ rescue Errno::ECONNRESET, EOFError
92
+ # Connection reset by peer
93
+ raise ConnectionLost
67
94
  end
68
95
 
69
96
  # --------------------------------------------------------- service/client
@@ -100,9 +127,39 @@ module Cod
100
127
  OtherEnd.new(params)
101
128
  end
102
129
  private
130
+ # Checks to see in which of the three connection phases we're in. If we're
131
+ # past 1), shuts down the background worker thread.
132
+ #
133
+ def check_connection_state
134
+ # Has the connection been established in the meantime? If yes, shut
135
+ # down the work queues thread, all work will be done in this thread
136
+ # from now on.
137
+ if cached_connection_established?
138
+ @work_queue.shutdown
139
+ end
140
+ end
141
+
142
+ # Returns false until the connection has been established once, at which
143
+ # point it always returns true.
144
+ #
145
+ def cached_connection_established?
146
+ @cached_connection_established ||= begin
147
+ # NOTE This will not be called unless we have some messages to send,
148
+ # so no useless connections are made
149
+ @connection.try_connect
150
+ @connection.established?
151
+ end
152
+ end
153
+
154
+ # Actual sending of messages. This is invoked from either the main thread
155
+ # or the worker thread, but never both at the same time.
156
+ #
103
157
  def send(msg)
104
158
  @connection.write(
105
159
  @serializer.en(msg))
160
+ rescue Exception => e
161
+ warn e
162
+ raise
106
163
  end
107
164
 
108
165
  # A connection that can be down. This allows elegant handling of
@@ -29,12 +29,12 @@ module Cod
29
29
  # as part of the message you send.
30
30
  #
31
31
  class TcpServer
32
- def initialize(bind_to)
32
+ def initialize(bind_to, serializer)
33
33
  @socket = TCPServer.new(*bind_to.split(':'))
34
34
  @client_sockets = []
35
35
  @round_robin_index = 0
36
36
  @messages = Array.new
37
- @serializer = SimpleSerializer.new
37
+ @serializer = serializer
38
38
  end
39
39
 
40
40
  # Receives one object from the channel.
@@ -27,6 +27,7 @@ module Cod
27
27
  @try_work_exclusive_section = ExclusiveSection.new
28
28
 
29
29
  @thread = Thread.start(&method(:thread_main))
30
+ @thread.priority = Thread.current.priority - 10
30
31
  end
31
32
 
32
33
  # The internal thread that is used to work on scheduled items in the
@@ -37,13 +38,26 @@ module Cod
37
38
  @try_work_exclusive_section.enter {
38
39
  # NOTE if predicate is nil or not set, no work will be accomplished.
39
40
  # This is the way I need it.
40
- while !@queue.empty? && @predicate && @predicate.call
41
+ while !@queue.empty? && predicate?
41
42
  wi = @queue.shift
42
43
  wi.call
43
44
  end
44
45
  }
45
46
  end
46
47
 
48
+ # Allows to perform other work mutually exclusive with the work performed
49
+ # in the queue. (see ExclusiveSection)
50
+ #
51
+ def exclusive
52
+ @try_work_exclusive_section.enter { yield }
53
+ end
54
+
55
+ # Evaluates the predicate. If the predicate is not set, this returns nil.
56
+ #
57
+ def predicate?
58
+ @predicate && @predicate.call
59
+ end
60
+
47
61
  # Before any kind of work is attempted, this predicate must evaluate to
48
62
  # true. It is tested repeatedly.
49
63
  #
@@ -65,8 +79,11 @@ module Cod
65
79
  # Shuts down the queue properly, without waiting for work to be completed.
66
80
  #
67
81
  def shutdown
82
+ return unless @thread
83
+
68
84
  @shutdown_requested = true
69
85
  @thread.join
86
+ @thread = nil
70
87
  end
71
88
 
72
89
  # Returns the size of the queue.
@@ -86,8 +103,8 @@ module Cod
86
103
  Thread.current.abort_on_exception = true
87
104
 
88
105
  loop do
89
- sleep 0.01
90
-
106
+ Thread.pass
107
+
91
108
  try_work
92
109
 
93
110
  # Signal the outside world that we've been around this loop once.
@@ -1,3 +1,8 @@
1
+
2
+ # Proxies tcp connections on a machine from one port to the next. This is used
3
+ # for debugging and writing specifications for cod. This is not officially
4
+ # part of cod's API.
5
+ #
1
6
  class TCPProxy
2
7
  attr_reader :connections
3
8
 
@@ -17,6 +22,8 @@ class TCPProxy
17
22
  @thread = Thread.start(&method(:thread_main))
18
23
  end
19
24
 
25
+ # Closes the proxy, disrupting every connection made to it.
26
+ #
20
27
  def close
21
28
  @shutdown = true
22
29
 
@@ -28,16 +35,27 @@ class TCPProxy
28
35
  @connections.each do |connection|
29
36
  connection.close
30
37
  end
31
- @ins.close
38
+ @ins.close if @ins
32
39
  end
33
40
 
41
+ # Disallows new connections.
42
+ #
34
43
  def block
35
44
  @accept_new = false
45
+ @ins.close
46
+ @ins = nil
36
47
  end
48
+
49
+ # Allows new connections to be made. This is the default, so you only need
50
+ # this to reenable connections after a {#block}.
51
+ #
37
52
  def allow
53
+ @ins = TCPServer.new(@host, @from_port)
38
54
  @accept_new = true
39
55
  end
40
56
 
57
+ # Drops all established connections.
58
+ #
41
59
  def drop_all
42
60
  # Copy the connections and then empty the collection
43
61
  connections = @connections_m.synchronize {
@@ -51,6 +69,8 @@ class TCPProxy
51
69
 
52
70
  # Inside the background thread ----------------------------------------
53
71
 
72
+ # Internal thread that pumps messages from and to ports.
73
+ # @private
54
74
  def thread_main
55
75
  loop do
56
76
  accept_connections if @accept_new
@@ -67,6 +87,7 @@ class TCPProxy
67
87
  raise
68
88
  end
69
89
 
90
+ # @private
70
91
  class Connection
71
92
  def initialize(in_sock, out_sock)
72
93
  @m = Mutex.new
@@ -96,10 +117,10 @@ class TCPProxy
96
117
  buf = socket.read_nonblock(16*1024)
97
118
 
98
119
  if socket == @in_sock
99
- puts "--> #{buf.size}"
120
+ puts "--> #{buf.size}" if $DEBUG
100
121
  @out_sock.write(buf)
101
122
  else
102
- puts "<-- #{buf.size}"
123
+ puts "<-- #{buf.size}" if $DEBUG
103
124
  @in_sock.write(buf)
104
125
  end
105
126
  end
@@ -111,6 +132,7 @@ class TCPProxy
111
132
  end
112
133
  end
113
134
 
135
+ # @private
114
136
  def accept_connections
115
137
  loop do
116
138
  in_sock = @ins.accept_nonblock
@@ -123,10 +145,21 @@ class TCPProxy
123
145
  # No more connections pending, stop accepting new connections
124
146
  end
125
147
 
148
+ # @private
126
149
  def forward_data
127
150
  connections = @connections_m.synchronize { @connections.dup }
151
+ remove_list = []
128
152
  connections.each do |conn|
129
- conn.pump_synchronized
153
+ begin
154
+ conn.pump_synchronized
155
+ rescue EOFError
156
+ # Socket was closed, remove from the collection.
157
+ remove_list << conn
158
+ end
130
159
  end
160
+
161
+ @connections_m.synchronize {
162
+ @connections -= remove_list
163
+ }
131
164
  end
132
165
  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.2
4
+ version: 0.4.3
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-15 00:00:00.000000000 Z
12
+ date: 2012-02-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70132592401200 !ruby/object:Gem::Requirement
16
+ requirement: &70357075424760 !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: *70132592401200
24
+ version_requirements: *70357075424760
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70132592400460 !ruby/object:Gem::Requirement
27
+ requirement: &70357075424320 !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: *70132592400460
35
+ version_requirements: *70357075424320
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: flexmock
38
- requirement: &70132592399720 !ruby/object:Gem::Requirement
38
+ requirement: &70357075423900 !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: *70132592399720
46
+ version_requirements: *70357075423900
47
47
  - !ruby/object:Gem::Dependency
48
- name: sdoc
49
- requirement: &70132592399020 !ruby/object:Gem::Requirement
48
+ name: yard
49
+ requirement: &70357075423480 !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: *70132592399020
57
+ version_requirements: *70357075423480
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: guard
60
- requirement: &70132592398400 !ruby/object:Gem::Requirement
60
+ requirement: &70357075423060 !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: *70132592398400
68
+ version_requirements: *70357075423060
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: growl
71
- requirement: &70132592397740 !ruby/object:Gem::Requirement
71
+ requirement: &70357075422640 !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: *70132592397740
79
+ version_requirements: *70357075422640
80
80
  description:
81
81
  email: kaspar.schiess@absurd.li
82
82
  executables: []
@@ -108,12 +108,12 @@ files:
108
108
  - lib/cod/work_queue.rb
109
109
  - lib/cod.rb
110
110
  - lib/tcp_proxy.rb
111
+ - examples/bs_ping_pong/ping.rb
112
+ - examples/bs_ping_pong/pong.rb
111
113
  - examples/example_scaffold.rb
112
114
  - examples/master_child.rb
113
115
  - examples/netcat/README
114
116
  - examples/netcat/server.rb
115
- - examples/ping_pong/ping.rb
116
- - examples/ping_pong/pong.rb
117
117
  - examples/presence/client.rb
118
118
  - examples/presence/server.rb
119
119
  - examples/protocol-buffers/master_child.rb
@@ -140,7 +140,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
140
  version: '0'
141
141
  segments:
142
142
  - 0
143
- hash: -1652399252235665508
143
+ hash: -4089674409401429686
144
144
  required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  none: false
146
146
  requirements:
@@ -149,11 +149,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
149
  version: '0'
150
150
  segments:
151
151
  - 0
152
- hash: -1652399252235665508
152
+ hash: -4089674409401429686
153
153
  requirements: []
154
154
  rubyforge_project:
155
- rubygems_version: 1.8.10
155
+ rubygems_version: 1.8.16
156
156
  signing_key:
157
157
  specification_version: 3
158
- summary: Really simple IPC.
158
+ summary: Really simple IPC. Pipes, TCP sockets, beanstalkd, ...
159
159
  test_files: []
@@ -1,9 +0,0 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
2
- require 'cod'
3
-
4
- pipe = Cod.beanstalk('localhost:11300', 'pingpong')
5
-
6
- loop do
7
- pipe.put Time.now
8
- sleep 1
9
- end
@@ -1,9 +0,0 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
2
- require 'cod'
3
-
4
- pipe = Cod.beanstalk('localhost:11300', "pingpong")
5
-
6
- loop do
7
- puts "Received: "+pipe.get.inspect
8
- end
9
-