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 +4 -0
- data/HISTORY.txt +9 -0
- data/README +2 -2
- data/Rakefile +4 -4
- data/examples/example_scaffold.rb +15 -0
- data/examples/ping.rb +1 -1
- data/examples/pong.rb +1 -1
- data/examples/tcp.rb +21 -0
- data/lib/at_fork.rb +37 -13
- data/lib/cod.rb +69 -3
- data/lib/cod/channel/abstract.rb +32 -0
- data/lib/cod/channel/base.rb +96 -33
- data/lib/cod/channel/beanstalk.rb +8 -9
- data/lib/cod/channel/pipe.rb +46 -46
- data/lib/cod/channel/tcp.rb +16 -0
- data/lib/cod/channel/tcpconnection.rb +73 -0
- data/lib/cod/channel/tcpserver.rb +84 -0
- data/lib/cod/connection/beanstalk.rb +28 -7
- data/lib/cod/context.rb +8 -55
- data/lib/cod/object_io.rb +3 -0
- data/lib/cod/objectio/connection.rb +0 -0
- data/lib/cod/objectio/reader.rb +129 -0
- data/lib/cod/objectio/serializer.rb +26 -0
- data/lib/cod/objectio/writer.rb +32 -0
- metadata +56 -51
data/Gemfile
CHANGED
data/HISTORY.txt
CHANGED
@@ -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.
|
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
|
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
|
-
|
33
|
-
pkg.
|
33
|
+
Gem::PackageTask.new(spec) do |pkg|
|
34
|
+
# pkg.need_tar = true
|
34
35
|
end
|
35
|
-
|
data/examples/ping.rb
CHANGED
data/examples/pong.rb
CHANGED
data/examples/tcp.rb
ADDED
@@ -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
|
data/lib/at_fork.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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.
|
38
|
+
Kernel.at_fork_parent.each(&:call)
|
21
39
|
|
22
40
|
fork_without_at_fork do
|
23
|
-
|
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
|
-
|
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
|
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
|
data/lib/cod/channel/base.rb
CHANGED
@@ -1,13 +1,49 @@
|
|
1
1
|
|
2
2
|
module Cod
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
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
|
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
|
-
#
|
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
|
-
|
52
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
66
|
-
Marshal.load(buffer)
|
67
|
-
end
|
118
|
+
private
|
68
119
|
|
69
|
-
#
|
70
|
-
#
|
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
|
-
|
73
|
-
|
74
|
-
|
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
|
-
#
|
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
|
80
|
-
|
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
|