cod 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -7,6 +7,10 @@ group :test do
7
7
  gem 'flexmock'
8
8
 
9
9
  gem 'guard'
10
+ gem 'guard-rspec'
11
+
12
+ gem 'sdoc'
13
+
10
14
  gem 'growl'
11
15
  gem 'rb-fsevent'
12
16
  end
@@ -1,4 +1,13 @@
1
1
 
2
+ == 0.3 / 20Jul2011
3
+
4
+ + Cod.tcpserver and Cod.tcp channels. Allows direct connection via
5
+ TCP/IP with the same semantics.
6
+
7
+ - You will now need to always provide a name for beanstalk channels. There
8
+ is currently no reliable way to generate unique names at the library
9
+ level.
10
+
2
11
  == 0.2 / 2011-04-27
3
12
 
4
13
  * Cod::Client now allows both synchronous (with answer) and asynchronous
data/README CHANGED
@@ -6,7 +6,7 @@ SYNOPSIS
6
6
 
7
7
  # Cod's basic elements are channels, unidirectional communication links.
8
8
  pipe = Cod.pipe
9
- beanstalk = Cod.beanstalk('localhost:11300')
9
+ beanstalk = Cod.beanstalk('localhost:11300', 'a_channel')
10
10
 
11
11
  # You can use those either directly:
12
12
  pipe.put :some_ruby_object # Process A
@@ -28,6 +28,6 @@ Becoming more useful by the day. Most things will work nicely already,
28
28
  although error handling is not production quality. Toy around with it now
29
29
  and give me feedback!
30
30
 
31
- At version 0.1 (unreleased)
31
+ At version 0.3
32
32
 
33
33
  (c) 2011 Kaspar Schiess
data/Rakefile CHANGED
@@ -1,7 +1,8 @@
1
+ require 'psych'
1
2
  require "rubygems"
2
3
  require "rake/rdoctask"
3
4
  require 'rspec/core/rake_task'
4
- require "rake/gempackagetask"
5
+ require 'rubygems/package_task'
5
6
 
6
7
  desc "Run all tests: Exhaustive."
7
8
  RSpec::Core::RakeTask.new
@@ -29,7 +30,6 @@ task :gem => :spec
29
30
  spec = eval(File.read('cod.gemspec'))
30
31
 
31
32
  desc "Generate the gem package."
32
- Rake::GemPackageTask.new(spec) do |pkg|
33
- pkg.gem_spec = spec
33
+ Gem::PackageTask.new(spec) do |pkg|
34
+ # pkg.need_tar = true
34
35
  end
35
-
@@ -0,0 +1,15 @@
1
+ def server
2
+ fork do
3
+ yield
4
+ end
5
+ end
6
+
7
+ def client
8
+ fork do
9
+ yield
10
+ end
11
+ end
12
+
13
+ def run
14
+ Process.waitall
15
+ end
@@ -1,7 +1,7 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
2
  require 'cod'
3
3
 
4
- pipe = Cod::Channel::Beanstalk.new('localhost:11300', 'pingpong')
4
+ pipe = Cod.beanstalk('localhost:11300', 'pingpong')
5
5
 
6
6
  loop do
7
7
  pipe.put Time.now
@@ -1,7 +1,7 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
2
  require 'cod'
3
3
 
4
- pipe = Cod::Channel::Beanstalk.new('localhost:11300', "pingpong")
4
+ pipe = Cod.beanstalk('localhost:11300', "pingpong")
5
5
 
6
6
  loop do
7
7
  puts "Received: "+pipe.get.inspect
@@ -0,0 +1,21 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+ $:.unshift File.expand_path(File.dirname(__FILE__))
3
+ require 'cod'
4
+
5
+ require 'example_scaffold'
6
+
7
+ server {
8
+ channel = Cod.tcpserver('127.0.0.1:5454')
9
+
10
+ client = channel.get
11
+ client.put 'heiho from server'
12
+ }
13
+
14
+ client {
15
+ channel = Cod.tcp('127.0.0.1:5454')
16
+
17
+ channel.put channel
18
+ puts channel.get
19
+ }
20
+
21
+ run
@@ -1,26 +1,50 @@
1
1
  # Extends the Kernel module with an at_fork method for installing at_fork
2
2
  # handlers.
3
-
3
+ #
4
+ # NOTE: at_fork handlers are executed in the thread that does the forking and
5
+ # that survives the process fork.
6
+ #
7
+ # Usage:
8
+ #
9
+ # at_fork do
10
+ # # Do something on fork (in the forking process)
11
+ # end
12
+ # at_fork(:child) { ... } # do something in the forked process
13
+ # at_fork(:parent) { ... } # do something in the forking process
14
+ #
4
15
  module Kernel
5
- class << self
6
- def at_fork_handler
7
- @at_fork_handler ||= proc {}
8
- end
9
- def at_fork_handler=(handler)
10
- @at_fork_handler = handler
11
- end
16
+ # Child at_fork handlers
17
+ #
18
+ def self.at_fork_child
19
+ @at_fork_child ||= []
12
20
  end
13
21
 
14
- def at_fork(&block)
15
- old_handler = Kernel.at_fork_handler
16
- Kernel.at_fork_handler = lambda { block.call(old_handler) }
22
+ # Parent at_fork handlers
23
+ #
24
+ def self.at_fork_parent
25
+ @at_fork_parent ||= []
26
+ end
27
+
28
+ def at_fork(type=:parent,&block)
29
+ raise ArgumentError, "Must provide a handler block." unless block
30
+
31
+ handler_array = (type == :child) ?
32
+ Kernel.at_fork_child : Kernel.at_fork_parent
33
+
34
+ handler_array << block
17
35
  end
18
36
 
19
37
  def fork_with_at_fork(&block)
20
- Kernel.at_fork_handler.call()
38
+ Kernel.at_fork_parent.each(&:call)
21
39
 
22
40
  fork_without_at_fork do
23
- Kernel.at_fork_handler = nil
41
+ # From this point on, operation is single threaded.
42
+
43
+ Kernel.at_fork_child.each(&:call)
44
+
45
+ Kernel.at_fork_parent.replace([])
46
+ Kernel.at_fork_child.replace([])
47
+
24
48
  block.call
25
49
  end
26
50
  end
data/lib/cod.rb CHANGED
@@ -1,5 +1,32 @@
1
1
  require 'at_fork'
2
2
 
3
+ # The core concept of Cod are 'channels'. (Cod::Channel::Base) You can create
4
+ # such channels on top of the various transport layers. Once you have such a
5
+ # channel, you #put messages into it and you #get messages out of it. Messages
6
+ # are retrieved in FIFO manner, making channels look like a communication pipe
7
+ # most of the time.
8
+ #
9
+ # Cod also brings a few abstractions layered on top of channels: You can use
10
+ # channels to present 'services' (Cod::Service) to the network: A service is a
11
+ # simple one or two way RPC call. (one way = asynchronous) You can also use
12
+ # channels to run a 'directory' (Cod::Directory) where processes subscribe to
13
+ # information using a filter. They then get information that matches their
14
+ # filter written to their inbound channel. (also called pub/sub)
15
+ #
16
+ # Cod channels are serializable whereever possible. If you want to tell
17
+ # somebody where to write his answers and/or questions to, send him the
18
+ # channel! This is really powerful and used extensively in constructing the
19
+ # higher order primitives.
20
+ #
21
+ # The goal of Cod is that you have to know only very few things about the
22
+ # network (the various transports) to be able to construct complex things. It
23
+ # handles reconnection and reliability for you. It also translates cryptic OS
24
+ # errors into plain text messages where it can't just handle them. This should
25
+ # give you a clear place to look at if things go wrong. Note that this can
26
+ # only be ever as good as the sum of situations Cod has been tested in.
27
+ # Contribute your observations and we'll come up with a way of dealing with
28
+ # most of the tricky stuff!
29
+ #
3
30
  module Cod
4
31
  # This gets raised in #create_reference when the identifier passed in is
5
32
  # either invalid (has never existed) or when it cannot be turned into an
@@ -8,24 +35,59 @@ module Cod
8
35
  #
9
36
  class InvalidIdentifier < StandardError; end
10
37
 
11
- def beanstalk(url, name=nil)
38
+ # Creates a beanstalkd based channel. Messages are written to a tube and
39
+ # read from it. This channel is read/write. Multiple readers will obtain
40
+ # messages in a round-robin fashion from the beanstalk server.
41
+ #
42
+ # Returns an instance of the Cod::Channel::Beanstalk class.
43
+ #
44
+ # Example:
45
+ # chan = Cod.beanstalk('localhost:11300', 'my_tube')
46
+ #
47
+ def beanstalk(url, name)
12
48
  context.beanstalk(url, name)
13
49
  end
14
50
  module_function :beanstalk
15
51
 
52
+ # Creates a IO.pipe based channel. Messages are written to one end of the
53
+ # pipe and come out on the other end. This channel can have only one reader,
54
+ # but of course multiple writers. Also, once you either write or read from
55
+ # such a channel, it will not be available for the other operation anymore.
56
+ #
57
+ # A common trick is to #dup the channel before using it to either read or
58
+ # write, so that the copy can still be used for both operations.
59
+ #
60
+ # Note that Cod.pipe channels are usable from process childs (#fork) as
61
+ # well. As such, they are ideally suited for process control.
62
+ #
63
+ # Returns an instance of the Cod::Channel::Pipe class.
64
+ #
65
+ # Example:
66
+ # chan = Cod.pipe
67
+ #
16
68
  def pipe(name=nil)
17
69
  context.pipe(name)
18
70
  end
19
71
  module_function :pipe
20
72
 
21
- def context
73
+ def tcp(destination)
74
+ context.tcp(destination)
75
+ end
76
+ module_function :tcp
77
+
78
+ def tcpserver(bind_to)
79
+ context.tcpserver(bind_to)
80
+ end
81
+ module_function :tcpserver
82
+
83
+ def context # :nodoc:
22
84
  @convenience_context ||= Context.new
23
85
  end
24
86
  module_function :context
25
87
 
26
88
  # For testing mainly
27
89
  #
28
- def reset
90
+ def reset # :nodoc:
29
91
  @convenience_context = nil
30
92
  end
31
93
  module_function :reset
@@ -34,10 +96,14 @@ end
34
96
  module Cod::Connection; end
35
97
  require 'cod/connection/beanstalk'
36
98
 
99
+ require 'cod/object_io'
100
+
37
101
  require 'cod/channel'
38
102
  require 'cod/channel/base'
39
103
  require 'cod/channel/pipe'
40
104
  require 'cod/channel/beanstalk'
105
+ require 'cod/channel/tcpconnection'
106
+ require 'cod/channel/tcpserver'
41
107
 
42
108
  require 'cod/context'
43
109
  require 'cod/client'
@@ -0,0 +1,32 @@
1
+ module Cod
2
+ # This is mostly documentation: Use it as a template for new channels.
3
+ class Channel::Abstract < Channel::Base
4
+ def initialize(destination)
5
+ not_implemented
6
+ end
7
+
8
+ def initialize_copy(from)
9
+ not_implemented
10
+ end
11
+
12
+ def put(message)
13
+ not_implemented
14
+ end
15
+
16
+ def get(opts={})
17
+ not_implemented
18
+ end
19
+
20
+ def waiting?
21
+ not_implemented
22
+ end
23
+
24
+ def close
25
+ not_implemented
26
+ end
27
+
28
+ def identifier
29
+ not_implemented
30
+ end
31
+ end
32
+ end
@@ -1,13 +1,49 @@
1
1
 
2
2
  module Cod
3
- # TODO document write/read semantics
4
- # TODO document dup behaviour
5
- # TODO document object serialisation
3
+ # Channels are the communication primitives you've always wanted (secretly).
4
+ # They are simple to use, abstract most of the OS away and allow direct
5
+ # communication using language objects, not just strings. (Although you can
6
+ # fall back to just strings if you want to)
7
+ #
8
+ # == Construction
9
+ #
10
+ # The simplest way to obtain a new channel is through the Module Cod. It
11
+ # provides the following channel types:
12
+ #
13
+ # Cod.beanstalk(url, name) :: connects to beanstalkd daemon
14
+ # Cod.pipe :: abstracts IO.pipe
15
+ # Cod.tcp(url) :: connects to a tcp socket somewhere
16
+ # Cod.tcpserver(url) :: the counterpart to Cod.tcp
17
+ #
18
+ # Please refer to the documentation of these methods for more information.
19
+ #
20
+ # == Basic Interaction
21
+ #
22
+ # You can #put messages (Ruby objects) into a channel on one side:
23
+ #
24
+ # channel.put("some message")
25
+ #
26
+ # and #get messages on the other side:
27
+ #
28
+ # channel.get # => "some message"
29
+ #
30
+ # == Querying
31
+ #
32
+ # Are there messages waiting to be read from the channel? Use #waiting?:
33
+ #
34
+ # channel.waiting? # => true or false
35
+ #
36
+ # == Cleaning up
37
+ #
38
+ # Be sure to #close the channel once you're done with it.
39
+ #
40
+ # channel.close
41
+ #
6
42
  class Channel::Base
7
43
  # Writes a Ruby object (the 'message') to the channel. This object will
8
44
  # be queued in the channel and become available for #get in a FIFO manner.
9
45
  #
10
- # Issuing a #put also closes the channel instance for subsequent #get's.
46
+ # Issuing a #put may also close the channel instance for subsequent #get's.
11
47
  #
12
48
  # Example:
13
49
  # chan.put 'test'
@@ -21,7 +57,7 @@ module Cod
21
57
  # Reads a Ruby object (a message) from the channel. Some channels may not
22
58
  # allow reading after you've written to it once. Options that work:
23
59
  #
24
- # :timeout :: Time to wait before throwing a Cod::Channel::TimeoutError.
60
+ # <code>:timeout</code> :: Time to wait before throwing Cod::Channel::TimeoutError.
25
61
  #
26
62
  def get(opts={})
27
63
  not_implemented
@@ -37,50 +73,77 @@ module Cod
37
73
  not_implemented
38
74
  end
39
75
 
40
- def identifier
41
- not_implemented
42
- end
43
-
44
76
  # Returns the Identifier class below the current channel class. This is
45
77
  # a helper function that should only be used by subclasses.
46
78
  #
47
- def identifier_class
79
+ def identifier_class # :nodoc:
48
80
  self.class.const_get(:Identifier)
49
81
  end
50
-
51
- def marshal_dump
52
- identifier
82
+
83
+ # Something to put into the data stream that is transmitted through a
84
+ # channel that allows reconstitution of the channel at the other end.
85
+ # The invariant is this:
86
+ #
87
+ # # channel1 and channel2 are abstract channels that illustrate my
88
+ # # meaning
89
+ # channel1.put channel2
90
+ # channel2a = channel1.get
91
+ # channel2a.put 'foo'
92
+ # channel2.get # => 'foo'
93
+ #
94
+ # Note that this should also work if channel1 and channel2 are the same.
95
+ #
96
+ def identifier # :nodoc:
97
+ identifier_class.new(self)
53
98
  end
54
99
 
55
- def marshal_load(identifier)
56
- temp = identifier.resolve
57
- initialize_copy(temp)
100
+ # ------------------------------------------------------------ marshalling
101
+
102
+ # Makes sure that we don't marshal this object, but the memento object
103
+ # returned by identifier.
104
+ #
105
+ def _dump(d) # :nodoc:
106
+ wire_data = to_wire_data
107
+ Marshal.dump(wire_data)
58
108
  end
59
-
60
- private
61
- def serialize(message)
62
- Marshal.dump(message)
109
+
110
+ # Loads the object from string. This doesn't always return the same kind
111
+ # of object that was serialized.
112
+ #
113
+ def self._load(string) # :nodoc:
114
+ wire_data = Marshal.load(string)
115
+ from_wire_data(wire_data)
63
116
  end
64
117
 
65
- def deserialize(buffer)
66
- Marshal.load(buffer)
67
- end
118
+ private
68
119
 
69
- # Turns the object into a buffer (simple transport layer that prefixes a
70
- # size)
120
+ # Returns the objects that need to be transmitted in order to reconstruct
121
+ # this object after transmission through the wire.
122
+ #
123
+ # If you're using a serialisation method other than the Ruby built in
124
+ # one, use this to obtain something in lieu of a channel that can be sent
125
+ # through the wire and reinterpreted at the other end.
71
126
  #
72
- def transport_pack(message)
73
- serialized = serialize(message)
74
- buffer = [serialized.size].pack('l') + serialized
127
+ # Example:
128
+ #
129
+ # # this should work:
130
+ # obj = channel.to_wire_data
131
+ # channel_equiv = Cod::Channel::Base.from_wire_data(obj)
132
+ #
133
+ def to_wire_data
134
+ identifier
75
135
  end
76
136
 
77
- # Slices one message from the front of buffer
137
+ # Using an object previously returned by #to_wire_data, reconstitute the
138
+ # original channel or something that is alike it. What you send to this
139
+ # second channel (#put) you should be able to #get from this copy returned
140
+ # here.
78
141
  #
79
- def transport_unpack(buffer)
80
- size = buffer.slice!(0...4).unpack('l').first
81
- serialized = buffer.slice!(0...size)
82
- deserialize(serialized)
142
+ def self.from_wire_data(obj)
143
+ obj.resolve
83
144
  end
145
+
146
+ # ---------------------------------------------------------- error raising
84
147
 
85
148
  def direction_error(msg)
86
149
  raise Cod::Channel::DirectionError, msg