cod 0.3.0 → 0.3.1
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 +2 -0
- data/HISTORY.txt +13 -0
- data/README +3 -3
- data/Rakefile +2 -2
- data/examples/presence_client.rb +15 -0
- data/examples/presence_server.rb +30 -0
- data/examples/pubsub/README +12 -0
- data/examples/pubsub/client.rb +13 -0
- data/examples/pubsub/directory.rb +13 -0
- data/lib/cod.rb +28 -15
- data/lib/cod/channel/base.rb +28 -5
- data/lib/cod/channel/beanstalk.rb +1 -1
- data/lib/cod/channel/pipe.rb +4 -2
- data/lib/cod/channel/tcpconnection.rb +18 -24
- data/lib/cod/channel/tcpserver.rb +24 -24
- data/lib/cod/connection/beanstalk.rb +1 -1
- data/lib/cod/directory.rb +67 -13
- data/lib/cod/directory/countdown.rb +31 -0
- data/lib/cod/directory/subscription.rb +43 -5
- data/lib/cod/object_io.rb +3 -0
- data/lib/cod/objectio/connection.rb +106 -0
- data/lib/cod/objectio/reader.rb +15 -46
- data/lib/cod/objectio/writer.rb +7 -12
- data/lib/cod/topic.rb +66 -5
- metadata +26 -14
- data/lib/at_fork.rb +0 -53
- data/lib/cod/channel/abstract.rb +0 -32
- data/lib/cod/context.rb +0 -25
data/Gemfile
CHANGED
data/HISTORY.txt
CHANGED
@@ -1,4 +1,17 @@
|
|
1
1
|
|
2
|
+
== 0.3.1 / ???
|
3
|
+
|
4
|
+
+ Large improvements in resilience to crashes, reconnects are now attempted
|
5
|
+
but can be improved further.
|
6
|
+
|
7
|
+
+ Both sides of an 1:n directory:topic structure can now crash without
|
8
|
+
permanent message loss.
|
9
|
+
|
10
|
+
+ tcp channels now have a #connected? method that indicates permanent
|
11
|
+
unrecoverable disconnection.
|
12
|
+
|
13
|
+
- at_fork extension is now permanently gone.
|
14
|
+
|
2
15
|
== 0.3 / 20Jul2011
|
3
16
|
|
4
17
|
+ Cod.tcpserver and Cod.tcp channels. Allows direct connection via
|
data/README
CHANGED
@@ -19,8 +19,8 @@ SYNOPSIS
|
|
19
19
|
service.one { |msg| :response } # Process A
|
20
20
|
client.call :ruby_object # => :response # Process B
|
21
21
|
|
22
|
-
# And more: Publish/Subscribe, easy construction of more advanced
|
23
|
-
# communication.
|
22
|
+
# And more: Publish/Subscribe, easy construction of more advanced
|
23
|
+
# distributed communication.
|
24
24
|
|
25
25
|
STATUS
|
26
26
|
|
@@ -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.3
|
31
|
+
At version 0.3.1
|
32
32
|
|
33
33
|
(c) 2011 Kaspar Schiess
|
data/Rakefile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'psych'
|
2
2
|
require "rubygems"
|
3
|
-
require "
|
3
|
+
require "rdoc/task"
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
require 'rubygems/package_task'
|
6
6
|
|
@@ -12,7 +12,7 @@ task :default => :spec
|
|
12
12
|
require 'sdoc'
|
13
13
|
|
14
14
|
# Generate documentation
|
15
|
-
|
15
|
+
RDoc::Task.new do |rdoc|
|
16
16
|
rdoc.title = "parslet - construction of parsers made easy"
|
17
17
|
rdoc.options << '--line-numbers'
|
18
18
|
rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
|
@@ -0,0 +1,15 @@
|
|
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
|
+
|
@@ -0,0 +1,30 @@
|
|
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
This is an example of PUB/SUB style messaging. You should run one directory
|
3
|
+
and any number of clients. All clients should receive timestamps from the directory, once every second.
|
4
|
+
|
5
|
+
Experiments that can be made using this setup:
|
6
|
+
|
7
|
+
1) Disconnect a client, restart it. It should start receiving updates
|
8
|
+
immediately.
|
9
|
+
|
10
|
+
2) Disconnect the directory, restart it. It should start redistributing
|
11
|
+
updates to all clients within 5 seconds.
|
12
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
|
2
|
+
require 'cod'
|
3
|
+
|
4
|
+
channels = Struct.new(:directory, :answers).new(
|
5
|
+
Cod.beanstalk('localhost:11300', 'directory'),
|
6
|
+
Cod.beanstalk('localhost:11300', 'directory.'+Cod.uuid))
|
7
|
+
|
8
|
+
topic = Cod::Topic.new('', channels.directory, channels.answers, :renew => 5)
|
9
|
+
|
10
|
+
loop do
|
11
|
+
puts topic.get
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
|
2
|
+
require 'cod'
|
3
|
+
|
4
|
+
channels = Struct.new(:directory).new(
|
5
|
+
Cod.beanstalk('localhost:11300', 'directory'))
|
6
|
+
|
7
|
+
directory = Cod::Directory.new(channels.directory)
|
8
|
+
|
9
|
+
loop do
|
10
|
+
directory.publish '', Time.now
|
11
|
+
sleep 1
|
12
|
+
end
|
13
|
+
|
data/lib/cod.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'uuid'
|
2
2
|
|
3
3
|
# The core concept of Cod are 'channels'. (Cod::Channel::Base) You can create
|
4
4
|
# such channels on top of the various transport layers. Once you have such a
|
@@ -45,7 +45,9 @@ module Cod
|
|
45
45
|
# chan = Cod.beanstalk('localhost:11300', 'my_tube')
|
46
46
|
#
|
47
47
|
def beanstalk(url, name)
|
48
|
-
|
48
|
+
Cod::Channel::Beanstalk.new(
|
49
|
+
Connection::Beanstalk.new(url),
|
50
|
+
name)
|
49
51
|
end
|
50
52
|
module_function :beanstalk
|
51
53
|
|
@@ -66,31 +68,43 @@ module Cod
|
|
66
68
|
# chan = Cod.pipe
|
67
69
|
#
|
68
70
|
def pipe(name=nil)
|
69
|
-
|
71
|
+
Cod::Channel::Pipe.new(name)
|
70
72
|
end
|
71
73
|
module_function :pipe
|
72
74
|
|
75
|
+
# Creates a tcp connection to the destination and returns a channel for it.
|
76
|
+
#
|
73
77
|
def tcp(destination)
|
74
|
-
|
78
|
+
Cod::Channel::TCPConnection.new(destination)
|
75
79
|
end
|
76
80
|
module_function :tcp
|
77
81
|
|
82
|
+
# Creates a tcp listener on bind_to and returns a channel for it.
|
83
|
+
#
|
78
84
|
def tcpserver(bind_to)
|
79
|
-
|
85
|
+
Cod::Channel::TCPServer.new(bind_to)
|
80
86
|
end
|
81
87
|
module_function :tcpserver
|
82
88
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
# For testing mainly
|
89
|
+
# Returns a UUID that should be unique for this machine (based on MAC), this
|
90
|
+
# Thread and Process. Even after a fork.
|
91
|
+
#
|
92
|
+
# This is used to create identity on the network. Internal method.
|
89
93
|
#
|
90
|
-
def
|
91
|
-
|
94
|
+
def uuid
|
95
|
+
uuid_generator.generate
|
96
|
+
end
|
97
|
+
def uuid_generator
|
98
|
+
pid, generator = Thread.current[:_cod_uuid_generator]
|
99
|
+
|
100
|
+
if pid && Process.pid == pid
|
101
|
+
return generator
|
102
|
+
end
|
103
|
+
|
104
|
+
pid, generator = Thread.current[:_cod_uuid_generator] = [Process.pid, UUID.new]
|
105
|
+
return generator
|
92
106
|
end
|
93
|
-
module_function :
|
107
|
+
module_function :uuid, :uuid_generator
|
94
108
|
end
|
95
109
|
|
96
110
|
module Cod::Connection; end
|
@@ -105,7 +119,6 @@ require 'cod/channel/beanstalk'
|
|
105
119
|
require 'cod/channel/tcpconnection'
|
106
120
|
require 'cod/channel/tcpserver'
|
107
121
|
|
108
|
-
require 'cod/context'
|
109
122
|
require 'cod/client'
|
110
123
|
|
111
124
|
require 'cod/service'
|
data/lib/cod/channel/base.rb
CHANGED
@@ -40,6 +40,11 @@ module Cod
|
|
40
40
|
# channel.close
|
41
41
|
#
|
42
42
|
class Channel::Base
|
43
|
+
def initialize(reader, writer)
|
44
|
+
@reader = reader
|
45
|
+
@writer = writer
|
46
|
+
end
|
47
|
+
|
43
48
|
# Writes a Ruby object (the 'message') to the channel. This object will
|
44
49
|
# be queued in the channel and become available for #get in a FIFO manner.
|
45
50
|
#
|
@@ -51,7 +56,8 @@ module Cod
|
|
51
56
|
# chan.put :symbol
|
52
57
|
#
|
53
58
|
def put(message)
|
54
|
-
|
59
|
+
# TODO Errno::EPIPE raised after a while when the receiver goes away.
|
60
|
+
@writer.put(message)
|
55
61
|
end
|
56
62
|
|
57
63
|
# Reads a Ruby object (a message) from the channel. Some channels may not
|
@@ -60,17 +66,30 @@ module Cod
|
|
60
66
|
# <code>:timeout</code> :: Time to wait before throwing Cod::Channel::TimeoutError.
|
61
67
|
#
|
62
68
|
def get(opts={})
|
63
|
-
|
69
|
+
@reader.get(opts)
|
64
70
|
end
|
65
71
|
|
66
72
|
# Returns true if there are messages waiting in the channel.
|
67
73
|
#
|
68
74
|
def waiting?
|
75
|
+
# TODO EOFError is thrown when the other end has gone away
|
76
|
+
@reader.waiting?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns true if the channel is connected, and false if all hope must be
|
80
|
+
# given up of reconnecting this channel.
|
81
|
+
#
|
82
|
+
def connected?
|
69
83
|
not_implemented
|
70
84
|
end
|
71
85
|
|
86
|
+
# Closes reader and writer.
|
87
|
+
#
|
72
88
|
def close
|
73
|
-
|
89
|
+
@reader.close if @reader
|
90
|
+
@writer.close if @writer
|
91
|
+
|
92
|
+
@reader = @writer = nil
|
74
93
|
end
|
75
94
|
|
76
95
|
# Returns the Identifier class below the current channel class. This is
|
@@ -154,9 +173,13 @@ module Cod
|
|
154
173
|
end
|
155
174
|
|
156
175
|
def not_implemented
|
157
|
-
|
176
|
+
trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}} # blatantly stolen from dependencies.rb in activesupport
|
177
|
+
exception = NotImplementedError.new(
|
158
178
|
"You called a method in Cod::Channel::Base. Missing implementation in "+
|
159
|
-
"the subclass!"
|
179
|
+
"the subclass #{self.class.name}!")
|
180
|
+
exception.set_backtrace trace
|
181
|
+
|
182
|
+
raise exception
|
160
183
|
end
|
161
184
|
end
|
162
185
|
end
|
data/lib/cod/channel/pipe.rb
CHANGED
@@ -79,8 +79,10 @@ module Cod
|
|
79
79
|
private
|
80
80
|
def init_in_and_out
|
81
81
|
serializer = ObjectIO::Serializer.new
|
82
|
-
|
83
|
-
|
82
|
+
read_pool = ObjectIO::Connection::Single.new { fds.r }
|
83
|
+
write_pool = ObjectIO::Connection::Single.new { fds.w }
|
84
|
+
@in = ObjectIO::Reader.new(serializer, read_pool)
|
85
|
+
@out = ObjectIO::Writer.new(serializer, write_pool) { fds.w }
|
84
86
|
end
|
85
87
|
|
86
88
|
def close_write
|
@@ -12,6 +12,8 @@ module Cod
|
|
12
12
|
#
|
13
13
|
attr_reader :destination
|
14
14
|
|
15
|
+
attr_reader :connection_pool
|
16
|
+
|
15
17
|
def initialize(destination_or_connection)
|
16
18
|
if destination_or_connection.respond_to?(:to_str)
|
17
19
|
@destination = split_uri(destination_or_connection)
|
@@ -20,23 +22,18 @@ module Cod
|
|
20
22
|
@destination = nil
|
21
23
|
@connection = destination_or_connection
|
22
24
|
end
|
23
|
-
|
25
|
+
|
24
26
|
serializer = ObjectIO::Serializer.new
|
25
|
-
@
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@writer.put(message)
|
31
|
-
end
|
32
|
-
|
33
|
-
def get(opts={})
|
34
|
-
@reader.get(opts)
|
27
|
+
@connection_pool = ObjectIO::Connection::Single.new { connect }
|
28
|
+
|
29
|
+
super(
|
30
|
+
ObjectIO::Reader.new(serializer, @connection_pool),
|
31
|
+
ObjectIO::Writer.new(serializer, @connection_pool))
|
35
32
|
end
|
36
|
-
|
37
|
-
def
|
38
|
-
|
39
|
-
|
33
|
+
|
34
|
+
def connected?
|
35
|
+
waiting?
|
36
|
+
connection_pool.size > 0
|
40
37
|
end
|
41
38
|
|
42
39
|
def identifier
|
@@ -44,15 +41,12 @@ module Cod
|
|
44
41
|
end
|
45
42
|
|
46
43
|
private
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# The other end doesn't exist as of this moment. Have the caller retry
|
54
|
-
# later on.
|
55
|
-
nil
|
44
|
+
def connect
|
45
|
+
if destination
|
46
|
+
TCPSocket.new(*destination)
|
47
|
+
else
|
48
|
+
@connection
|
49
|
+
end
|
56
50
|
end
|
57
51
|
end
|
58
52
|
|
@@ -20,37 +20,33 @@ module Cod
|
|
20
20
|
def initialize(bind_to)
|
21
21
|
@bind_to = split_uri(bind_to)
|
22
22
|
@server = TCPServer.new(*@bind_to)
|
23
|
-
|
23
|
+
|
24
|
+
connection_pool = ObjectIO::Connection::Pool.new { accept_connections(server) }
|
24
25
|
serializer = ObjectIO::Serializer.new(self)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def get(opts={})
|
31
|
-
# Read a message from the wire and transform all contained objects.
|
32
|
-
@reader.get(opts)
|
26
|
+
|
27
|
+
super(
|
28
|
+
ObjectIO::Reader.new(serializer, connection_pool),
|
29
|
+
nil)
|
33
30
|
end
|
34
|
-
|
31
|
+
|
32
|
+
# Sending a message to the server side of a socket is not supported.
|
33
|
+
# Transmit the client side channel instance to be able to write back to it
|
34
|
+
# on the server side. This overwrites #put in Base for the sole purpose of
|
35
|
+
# raising a better error.
|
36
|
+
#
|
35
37
|
def put(message)
|
36
38
|
communication_error "You cannot write to the server directly, transmit a "
|
37
39
|
"channel to the server instead."
|
38
40
|
end
|
39
41
|
|
40
|
-
def waiting?
|
41
|
-
@reader.waiting?
|
42
|
-
end
|
43
|
-
|
44
42
|
def close
|
45
|
-
|
43
|
+
super
|
44
|
+
|
46
45
|
server.close if server
|
47
|
-
|
48
46
|
@server = nil
|
49
|
-
@reader = nil
|
50
47
|
end
|
51
48
|
|
52
49
|
def transform(socket, obj)
|
53
|
-
# p [:tcp_server_deserialize, obj]
|
54
50
|
if obj.kind_of?(Channel::TCPConnection::Identifier)
|
55
51
|
# We've been sent 'a' tcp channel. Assume that it's our own client end
|
56
52
|
# that we've been sent and turn it into a channel that communicates
|
@@ -72,13 +68,17 @@ module Cod
|
|
72
68
|
def accept_connections(server)
|
73
69
|
connections = []
|
74
70
|
loop do
|
75
|
-
|
76
|
-
|
71
|
+
# Try connecting more sockets.
|
72
|
+
begin
|
73
|
+
connections << server.accept_nonblock
|
74
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
|
75
|
+
# Means that no more connects are pending. Ignore, since this is exactly
|
76
|
+
# one of the termination conditions for this method.
|
77
|
+
return connections
|
78
|
+
end
|
77
79
|
end
|
78
|
-
|
79
|
-
|
80
|
-
# one of the termination conditions for this method.
|
81
|
-
return connections
|
80
|
+
|
81
|
+
fail "NOTREACHED: return should be from loop."
|
82
82
|
end
|
83
83
|
end
|
84
84
|
end
|
data/lib/cod/directory.rb
CHANGED
@@ -8,19 +8,40 @@ module Cod
|
|
8
8
|
#
|
9
9
|
attr_reader :channel
|
10
10
|
|
11
|
+
# Subscriptions this directory handles.
|
12
|
+
#
|
13
|
+
attr_reader :subscriptions
|
14
|
+
|
11
15
|
def initialize(channel)
|
12
16
|
@channel = channel
|
13
|
-
@subscriptions =
|
17
|
+
@subscriptions = Set.new
|
14
18
|
end
|
15
19
|
|
16
|
-
# Sends the message to all subscribers that listen to this topic.
|
20
|
+
# Sends the message to all subscribers that listen to this topic. Returns
|
21
|
+
# the number of subscribers this message has been sent to.
|
17
22
|
#
|
18
23
|
def publish(topic, message)
|
19
|
-
|
20
|
-
|
24
|
+
process_control_messages
|
25
|
+
|
26
|
+
n = 0
|
27
|
+
failed_subscriptions = []
|
21
28
|
for subscription in @subscriptions
|
22
|
-
|
29
|
+
begin
|
30
|
+
if subscription === topic
|
31
|
+
subscription.put message
|
32
|
+
n += 1
|
33
|
+
end
|
34
|
+
# TODO reenable this with more specific exception list.
|
35
|
+
rescue Cod::Channel::DirectionError
|
36
|
+
# Writing message failed; remove the subscription.
|
37
|
+
failed_subscriptions << subscription
|
38
|
+
end
|
23
39
|
end
|
40
|
+
|
41
|
+
process_control_messages
|
42
|
+
|
43
|
+
remove_subscriptions { |sub| failed_subscriptions.include?(sub) }
|
44
|
+
return n
|
24
45
|
end
|
25
46
|
|
26
47
|
# Closes all resources used by the directory.
|
@@ -28,17 +49,50 @@ module Cod
|
|
28
49
|
def close
|
29
50
|
channel.close
|
30
51
|
end
|
52
|
+
|
53
|
+
# Internal use: Subscribe a new topic to this directory.
|
54
|
+
#
|
55
|
+
def subscribe(subscription, status=:new)
|
56
|
+
if status == :new && subscriptions.include?(subscription)
|
57
|
+
raise "UUID collision? I already have a subscription for #{subscription.identifier}."
|
58
|
+
end
|
59
|
+
|
60
|
+
@subscriptions << subscription
|
61
|
+
end
|
31
62
|
|
32
|
-
|
33
|
-
|
34
|
-
|
63
|
+
# Internal method to process messages that are inbound on the directory
|
64
|
+
# control channel.
|
65
|
+
#
|
66
|
+
def process_control_messages(now = Time.now)
|
67
|
+
# Handle incoming messages on channel
|
35
68
|
while channel.waiting?
|
36
|
-
|
69
|
+
cmd, *rest = channel.get(timeout: 0.1)
|
70
|
+
case cmd
|
71
|
+
when :subscribe
|
72
|
+
subscription, status = *rest
|
73
|
+
subscribe subscription, status
|
74
|
+
when :ping
|
75
|
+
ping_id = rest.first
|
76
|
+
subscriptions.
|
77
|
+
find { |sub| sub.identifier == ping_id }.
|
78
|
+
ping
|
79
|
+
else
|
80
|
+
warn "Unknown command received: #{cmd.inspect} (#{rest.inspect})"
|
81
|
+
end
|
37
82
|
end
|
83
|
+
|
84
|
+
# Remove all stale subscriptions
|
85
|
+
remove_subscriptions { |sub| sub.stale?(now) }
|
86
|
+
rescue ArgumentError
|
87
|
+
# Probably we could not create a duplicate of a serialized channel.
|
88
|
+
# Ignore this round of subscriptions.
|
38
89
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
90
|
+
|
91
|
+
private
|
92
|
+
def remove_subscriptions(&block)
|
93
|
+
@subscriptions.delete_if(&block)
|
42
94
|
end
|
43
95
|
end
|
44
|
-
end
|
96
|
+
end
|
97
|
+
|
98
|
+
require 'cod/directory/countdown'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Cod::Directory
|
2
|
+
class Countdown
|
3
|
+
attr_reader :run_time
|
4
|
+
|
5
|
+
def initialize(run_time=30*60, now = Time.now)
|
6
|
+
@run_time = run_time
|
7
|
+
start(now); stop
|
8
|
+
end
|
9
|
+
|
10
|
+
def elapsed?(now = Time.now)
|
11
|
+
if running?
|
12
|
+
return (now - @started_at) > @run_time
|
13
|
+
else
|
14
|
+
return (@stopped_at - @started_at) > @run_time
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def running?
|
19
|
+
@started_at && !@stopped_at
|
20
|
+
end
|
21
|
+
|
22
|
+
def start(now = Time.now)
|
23
|
+
@started_at = now
|
24
|
+
@stopped_at = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop(now = Time.now)
|
28
|
+
@stopped_at = now
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,21 +1,59 @@
|
|
1
|
-
|
2
|
-
# Represents a subscription to a directory.
|
1
|
+
class Cod::Directory
|
2
|
+
# Represents a subscription to a directory. The subscription is what links
|
3
|
+
# the topic to the directory. It carries the topic id as identifier; this is
|
4
|
+
# what gives the subscription identity. If two subscriptions in a system
|
5
|
+
# have the same identifier, they link to the same topic instance and should
|
6
|
+
# not be sent the same message twice.
|
3
7
|
#
|
4
|
-
class
|
8
|
+
class Subscription
|
5
9
|
attr_reader :matcher
|
6
10
|
attr_reader :channel
|
11
|
+
attr_reader :countdown
|
7
12
|
|
8
|
-
def initialize(matcher, channel)
|
13
|
+
def initialize(matcher, channel, topic_id)
|
9
14
|
@matcher = matcher
|
10
15
|
@channel = channel
|
16
|
+
@countdown = Countdown.new
|
17
|
+
@identifier = topic_id
|
11
18
|
end
|
12
19
|
|
13
20
|
def ===(other)
|
14
21
|
matcher === other
|
15
22
|
end
|
16
23
|
|
24
|
+
def identifier
|
25
|
+
@identifier
|
26
|
+
end
|
27
|
+
def eql?(other)
|
28
|
+
hash == other.hash &&
|
29
|
+
identifier == other.identifier
|
30
|
+
end
|
31
|
+
alias == eql?
|
32
|
+
def hash
|
33
|
+
identifier.hash
|
34
|
+
end
|
35
|
+
|
17
36
|
def put(msg)
|
18
|
-
|
37
|
+
countdown.start
|
38
|
+
|
39
|
+
# Envelope the message to send this subscription id along
|
40
|
+
channel.put [identifier, msg]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Is this subscription stale? Staleness is determined by an internal
|
44
|
+
# countdown since last #put operation.
|
45
|
+
#
|
46
|
+
def stale?(now=Time.now)
|
47
|
+
countdown.running? && countdown.elapsed?(now)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Tells the subscription that the other end has sent back a ping. This
|
51
|
+
# always marks the subscription alive.
|
52
|
+
#
|
53
|
+
def ping(now= Time.now)
|
54
|
+
# Stop the countdown where it is. If it has elapsed?, the subscription
|
55
|
+
# will be marked as stale?
|
56
|
+
countdown.stop(now)
|
19
57
|
end
|
20
58
|
end
|
21
59
|
end
|
data/lib/cod/object_io.rb
CHANGED
@@ -0,0 +1,106 @@
|
|
1
|
+
module Cod::ObjectIO::Connection
|
2
|
+
class Pool
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :connect_action
|
6
|
+
attr_reader :connections
|
7
|
+
|
8
|
+
# Example:
|
9
|
+
# Connection::Single.new { TCPSocket.new('...') }
|
10
|
+
# Connection::Pool.new { socket.accept }
|
11
|
+
#
|
12
|
+
def initialize(&connect_action)
|
13
|
+
raise ArgumentError unless connect_action
|
14
|
+
|
15
|
+
@connect_action = connect_action
|
16
|
+
@connections = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def size
|
20
|
+
@connections.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def report_failed(connection)
|
24
|
+
@connections.delete_if { |e| e == connection }
|
25
|
+
end
|
26
|
+
|
27
|
+
def accept
|
28
|
+
@connections += call_connect
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
@connections.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Closes all connections in this pool.
|
36
|
+
#
|
37
|
+
def close
|
38
|
+
self.each do |connection|
|
39
|
+
begin
|
40
|
+
connection.close
|
41
|
+
rescue IOError
|
42
|
+
# Maybe someone else that shares this connection closed it already?
|
43
|
+
# DO NOTHING
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
private
|
48
|
+
# Calls the connect_action block and normalizes the result to be either
|
49
|
+
# an array or nil.
|
50
|
+
#
|
51
|
+
def call_connect
|
52
|
+
result = connect_action[]
|
53
|
+
if result
|
54
|
+
return [result].flatten
|
55
|
+
end
|
56
|
+
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Implements a single connection that is retried on failure for a number
|
62
|
+
# of times.
|
63
|
+
#
|
64
|
+
class Single < Pool
|
65
|
+
MAX_FAILURES = 10
|
66
|
+
|
67
|
+
def initialize(&connect_action)
|
68
|
+
super
|
69
|
+
|
70
|
+
@connected = false
|
71
|
+
@failures = 0
|
72
|
+
end
|
73
|
+
|
74
|
+
def report_failed(connection)
|
75
|
+
super
|
76
|
+
|
77
|
+
@failures += 1
|
78
|
+
@connected = false
|
79
|
+
end
|
80
|
+
|
81
|
+
def accept
|
82
|
+
return if @connected
|
83
|
+
|
84
|
+
# How many times have we reconnected? Maybe just give up.
|
85
|
+
permanent_connection_error if @failures >= MAX_FAILURES
|
86
|
+
|
87
|
+
# Try and make a new connection: Returns it on success.
|
88
|
+
new_connection = call_connect
|
89
|
+
if new_connection
|
90
|
+
@connected = true
|
91
|
+
@connections += new_connection
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
# No new connection could be made. Count this as a failure.
|
96
|
+
@failures += 1
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def permanent_connection_error
|
102
|
+
raise Cod::Channel::CommunicationError,
|
103
|
+
"Permanent connection failure: Giving up."
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/cod/objectio/reader.rb
CHANGED
@@ -3,55 +3,25 @@ module Cod::ObjectIO
|
|
3
3
|
#
|
4
4
|
class Reader
|
5
5
|
attr_reader :waiting_messages
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :pool
|
7
7
|
|
8
8
|
# Initializes an object reader that reads from one or several IO objects.
|
9
|
-
# You can either pass the io object in the constructor (io) or you can
|
10
|
-
# provide the instance with a block that is called each time a read is
|
11
|
-
# attempted. The block should return an array of IO objects to also read
|
12
|
-
# from.
|
13
9
|
#
|
14
10
|
# Example:
|
15
|
-
#
|
11
|
+
# connection = Connection::Pool.new { make_new_connection }
|
12
|
+
# reader = Reader.new(serializer, connection)
|
16
13
|
#
|
17
|
-
def initialize(serializer,
|
14
|
+
def initialize(serializer, conn_pool)
|
18
15
|
@serializer = serializer
|
19
16
|
@waiting_messages = []
|
20
|
-
@
|
21
|
-
@registered_ios = Set.new
|
22
|
-
|
23
|
-
register io if io
|
17
|
+
@pool = conn_pool
|
24
18
|
end
|
25
19
|
|
26
|
-
# Called before each attempt to read from the wire. This should return
|
27
|
-
# the IO objects that need to be considered when reading.
|
28
|
-
#
|
29
|
-
def establish
|
30
|
-
sockets = @establish_block && @establish_block.call(@io) ||
|
31
|
-
nil
|
32
|
-
|
33
|
-
[sockets].flatten
|
34
|
-
end
|
35
|
-
|
36
|
-
def register(ios)
|
37
|
-
return unless ios
|
38
|
-
ios.each do |io|
|
39
|
-
registered_ios << io
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def unregister(ios)
|
44
|
-
ios.each do |io|
|
45
|
-
registered_ios.delete(io)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
20
|
def get(opts={})
|
50
21
|
return waiting_messages.shift if queued?
|
51
22
|
|
52
23
|
start_time = Time.now
|
53
24
|
loop do
|
54
|
-
# p [:looping, opts]
|
55
25
|
read_from_wire opts
|
56
26
|
|
57
27
|
# Early return in case we have a message waiting
|
@@ -69,6 +39,8 @@ module Cod::ObjectIO
|
|
69
39
|
def waiting?
|
70
40
|
read_from_wire
|
71
41
|
queued?
|
42
|
+
rescue Cod::Channel::CommunicationError
|
43
|
+
queued?
|
72
44
|
end
|
73
45
|
|
74
46
|
def queued?
|
@@ -76,7 +48,7 @@ module Cod::ObjectIO
|
|
76
48
|
end
|
77
49
|
|
78
50
|
def close
|
79
|
-
@
|
51
|
+
@pool.close
|
80
52
|
end
|
81
53
|
|
82
54
|
private
|
@@ -85,13 +57,10 @@ module Cod::ObjectIO
|
|
85
57
|
#
|
86
58
|
def read_from_wire(opts={})
|
87
59
|
# Establish new connections and register them
|
88
|
-
|
60
|
+
@pool.accept
|
89
61
|
|
90
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
# Read all ready sockets
|
94
|
-
process_nonblock(ready_read) if ready_read
|
62
|
+
# Process all waiting data
|
63
|
+
process_nonblock(@pool.connections)
|
95
64
|
end
|
96
65
|
|
97
66
|
# Reads all data waiting in each io in the ios array.
|
@@ -111,11 +80,11 @@ module Cod::ObjectIO
|
|
111
80
|
while not sio.eof?
|
112
81
|
waiting_messages << deserialize(io, sio)
|
113
82
|
end
|
83
|
+
rescue Errno::EAGAIN
|
84
|
+
# read failed because there was no data. This is expected.
|
85
|
+
return
|
114
86
|
rescue EOFError
|
115
|
-
|
116
|
-
# We will need to reconnect this. If possible.
|
117
|
-
registered_ios.delete(io)
|
118
|
-
raise
|
87
|
+
@pool.report_failed(io)
|
119
88
|
end
|
120
89
|
|
121
90
|
# Deserializes a message (in message format, string) into the object that
|
data/lib/cod/objectio/writer.rb
CHANGED
@@ -2,29 +2,24 @@ module Cod::ObjectIO
|
|
2
2
|
# Writes objects to an IO stream.
|
3
3
|
#
|
4
4
|
class Writer
|
5
|
-
def initialize(serializer,
|
5
|
+
def initialize(serializer, pool)
|
6
6
|
@serializer = serializer
|
7
|
-
@
|
8
|
-
@reconnect_block = block
|
7
|
+
@pool = pool
|
9
8
|
end
|
10
9
|
|
11
10
|
def put(message)
|
12
|
-
|
11
|
+
@pool.accept
|
13
12
|
|
14
|
-
@
|
13
|
+
@pool.each do |connection|
|
14
|
+
connection.write(serialize(message))
|
15
|
+
end
|
15
16
|
end
|
16
17
|
|
17
18
|
def close
|
18
|
-
@
|
19
|
+
@pool.close
|
19
20
|
end
|
20
21
|
|
21
22
|
private
|
22
|
-
def attempt_reconnect
|
23
|
-
if @reconnect_block
|
24
|
-
@io = @reconnect_block[]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
23
|
def serialize(message)
|
29
24
|
@serializer.serialize(message)
|
30
25
|
end
|
data/lib/cod/topic.rb
CHANGED
@@ -4,9 +4,33 @@ module Cod
|
|
4
4
|
class Topic
|
5
5
|
attr_reader :answers, :directory
|
6
6
|
attr_reader :match_expr
|
7
|
-
|
7
|
+
attr_reader :identifier
|
8
|
+
attr_reader :subscription
|
9
|
+
attr_reader :renew_countdown
|
10
|
+
|
11
|
+
# Creates a topic that subscribes to a part of a directory. The match_expr
|
12
|
+
# decides which messages get forwarded to this topic, it limits the
|
13
|
+
# topic to a subset of the messages in the directory.
|
14
|
+
#
|
15
|
+
# Parameters:
|
16
|
+
# match_expr :: Topic to subscribe
|
17
|
+
# directory_channel :: Directory channel
|
18
|
+
# answer_channel :: Where the messages for this topic get sent
|
19
|
+
# opts :: See below
|
20
|
+
#
|
21
|
+
# Available options are:
|
22
|
+
# :renew :: Renew the subscription every n seconds.
|
23
|
+
#
|
24
|
+
def initialize(match_expr, directory_channel, answer_channel, opts={})
|
8
25
|
@directory, @answers = directory_channel, answer_channel
|
9
26
|
@match_expr = match_expr
|
27
|
+
@identifier = Cod.uuid
|
28
|
+
@subscription = Directory::Subscription.new(
|
29
|
+
match_expr, answers, @identifier)
|
30
|
+
|
31
|
+
# Default is to renew subscriptions every 30 minutes
|
32
|
+
@renew_countdown = Directory::Countdown.new(opts[:renew] || 30*60)
|
33
|
+
renew_countdown.start
|
10
34
|
|
11
35
|
subscribe
|
12
36
|
end
|
@@ -14,14 +38,51 @@ module Cod
|
|
14
38
|
# Subscribes this topic to the directory's messages. This gets called upon
|
15
39
|
# initialization and must not be called again.
|
16
40
|
#
|
17
|
-
def subscribe
|
18
|
-
directory.put
|
41
|
+
def subscribe(status=:new)
|
42
|
+
directory.put [
|
43
|
+
:subscribe, subscription, status]
|
44
|
+
|
45
|
+
# Start counting down to next subscription renewal
|
46
|
+
renew_countdown.start
|
47
|
+
end
|
48
|
+
def renew_subscription
|
49
|
+
subscribe(:refresh)
|
50
|
+
end
|
51
|
+
def renewal_needed?
|
52
|
+
renew_countdown.elapsed?
|
19
53
|
end
|
20
54
|
|
21
55
|
# Reads the next message from the directory that matches this topic.
|
22
56
|
#
|
23
|
-
def get
|
24
|
-
|
57
|
+
def get(opts={})
|
58
|
+
# Read one message from the channel
|
59
|
+
subscription_id, message = next_message(opts)
|
60
|
+
# Answer back with a ping (so the directory knows we're still there)
|
61
|
+
directory.put [:ping, subscription_id]
|
62
|
+
|
63
|
+
return message
|
64
|
+
end
|
65
|
+
def next_message(opts)
|
66
|
+
if t=opts[:timeout]
|
67
|
+
timeout_at = Time.now + t
|
68
|
+
timeout = [t, renew_countdown.run_time].min
|
69
|
+
else
|
70
|
+
timeout_at = nil
|
71
|
+
timeout = renew_countdown.run_time
|
72
|
+
end
|
73
|
+
|
74
|
+
loop do
|
75
|
+
renew_subscription if renewal_needed?
|
76
|
+
|
77
|
+
begin
|
78
|
+
return answers.get(opts.merge(:timeout => timeout))
|
79
|
+
rescue Cod::Channel::TimeoutError
|
80
|
+
raise if timeout_at && Time.now > timeout_at
|
81
|
+
# DO NOTHING
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
fail "NOT REACHED"
|
25
86
|
end
|
26
87
|
|
27
88
|
# Closes all resources used by the topic.
|
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.3.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-07-20 00:00:00.
|
13
|
-
default_executable:
|
12
|
+
date: 2011-07-20 00:00:00.000000000Z
|
14
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: uuid
|
16
|
+
requirement: &70169583690420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70169583690420
|
15
25
|
- !ruby/object:Gem::Dependency
|
16
26
|
name: rspec
|
17
|
-
requirement: &
|
27
|
+
requirement: &70169583689920 !ruby/object:Gem::Requirement
|
18
28
|
none: false
|
19
29
|
requirements:
|
20
30
|
- - ! '>='
|
@@ -22,10 +32,10 @@ dependencies:
|
|
22
32
|
version: '0'
|
23
33
|
type: :development
|
24
34
|
prerelease: false
|
25
|
-
version_requirements: *
|
35
|
+
version_requirements: *70169583689920
|
26
36
|
- !ruby/object:Gem::Dependency
|
27
37
|
name: flexmock
|
28
|
-
requirement: &
|
38
|
+
requirement: &70169583689340 !ruby/object:Gem::Requirement
|
29
39
|
none: false
|
30
40
|
requirements:
|
31
41
|
- - ! '>='
|
@@ -33,10 +43,10 @@ dependencies:
|
|
33
43
|
version: '0'
|
34
44
|
type: :development
|
35
45
|
prerelease: false
|
36
|
-
version_requirements: *
|
46
|
+
version_requirements: *70169583689340
|
37
47
|
- !ruby/object:Gem::Dependency
|
38
48
|
name: sdoc
|
39
|
-
requirement: &
|
49
|
+
requirement: &70169583688800 !ruby/object:Gem::Requirement
|
40
50
|
none: false
|
41
51
|
requirements:
|
42
52
|
- - ! '>='
|
@@ -44,7 +54,7 @@ dependencies:
|
|
44
54
|
version: '0'
|
45
55
|
type: :development
|
46
56
|
prerelease: false
|
47
|
-
version_requirements: *
|
57
|
+
version_requirements: *70169583688800
|
48
58
|
description:
|
49
59
|
email: kaspar.schiess@absurd.li
|
50
60
|
executables: []
|
@@ -57,8 +67,6 @@ files:
|
|
57
67
|
- LICENSE
|
58
68
|
- Rakefile
|
59
69
|
- README
|
60
|
-
- lib/at_fork.rb
|
61
|
-
- lib/cod/channel/abstract.rb
|
62
70
|
- lib/cod/channel/base.rb
|
63
71
|
- lib/cod/channel/beanstalk.rb
|
64
72
|
- lib/cod/channel/pipe.rb
|
@@ -68,7 +76,7 @@ files:
|
|
68
76
|
- lib/cod/channel.rb
|
69
77
|
- lib/cod/client.rb
|
70
78
|
- lib/cod/connection/beanstalk.rb
|
71
|
-
- lib/cod/
|
79
|
+
- lib/cod/directory/countdown.rb
|
72
80
|
- lib/cod/directory/subscription.rb
|
73
81
|
- lib/cod/directory.rb
|
74
82
|
- lib/cod/object_io.rb
|
@@ -83,10 +91,14 @@ files:
|
|
83
91
|
- examples/master_child.rb
|
84
92
|
- examples/ping.rb
|
85
93
|
- examples/pong.rb
|
94
|
+
- examples/presence_client.rb
|
95
|
+
- examples/presence_server.rb
|
96
|
+
- examples/pubsub/client.rb
|
97
|
+
- examples/pubsub/directory.rb
|
98
|
+
- examples/pubsub/README
|
86
99
|
- examples/service.rb
|
87
100
|
- examples/service_directory.rb
|
88
101
|
- examples/tcp.rb
|
89
|
-
has_rdoc: true
|
90
102
|
homepage: http://kschiess.github.com/cod
|
91
103
|
licenses: []
|
92
104
|
post_install_message:
|
@@ -109,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
121
|
version: '0'
|
110
122
|
requirements: []
|
111
123
|
rubyforge_project:
|
112
|
-
rubygems_version: 1.6
|
124
|
+
rubygems_version: 1.8.6
|
113
125
|
signing_key:
|
114
126
|
specification_version: 3
|
115
127
|
summary: Really simple IPC.
|
data/lib/at_fork.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
# Extends the Kernel module with an at_fork method for installing at_fork
|
2
|
-
# handlers.
|
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
|
-
#
|
15
|
-
module Kernel
|
16
|
-
# Child at_fork handlers
|
17
|
-
#
|
18
|
-
def self.at_fork_child
|
19
|
-
@at_fork_child ||= []
|
20
|
-
end
|
21
|
-
|
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
|
35
|
-
end
|
36
|
-
|
37
|
-
def fork_with_at_fork(&block)
|
38
|
-
Kernel.at_fork_parent.each(&:call)
|
39
|
-
|
40
|
-
fork_without_at_fork do
|
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
|
-
|
48
|
-
block.call
|
49
|
-
end
|
50
|
-
end
|
51
|
-
alias fork_without_at_fork fork
|
52
|
-
alias fork fork_with_at_fork
|
53
|
-
end
|
data/lib/cod/channel/abstract.rb
DELETED
@@ -1,32 +0,0 @@
|
|
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/context.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'weakref'
|
2
|
-
|
3
|
-
module Cod
|
4
|
-
# Context will allow to produce channels retaining some state. Until now,
|
5
|
-
# this hasn't been neccessary.
|
6
|
-
#
|
7
|
-
class Context
|
8
|
-
def pipe(name=nil)
|
9
|
-
Cod::Channel::Pipe.new(name)
|
10
|
-
end
|
11
|
-
|
12
|
-
def beanstalk(url, name=nil)
|
13
|
-
Cod::Channel::Beanstalk.new(
|
14
|
-
Connection::Beanstalk.new(url), name)
|
15
|
-
end
|
16
|
-
|
17
|
-
def tcp(destination)
|
18
|
-
Cod::Channel::TCPConnection.new(destination)
|
19
|
-
end
|
20
|
-
|
21
|
-
def tcpserver(bind_to)
|
22
|
-
Cod::Channel::TCPServer.new(bind_to)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|