cod 0.2.0 → 0.3.0
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.
- 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
|