cod 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/HISTORY.txt +5 -1
- data/README +12 -13
- data/Rakefile +7 -1
- data/examples/{ping.rb → ping_pong/ping.rb} +1 -1
- data/examples/{pong.rb → ping_pong/pong.rb} +1 -1
- data/examples/{presence_client.rb → presence/client.rb} +0 -0
- data/examples/{presence_server.rb → presence/server.rb} +0 -0
- data/examples/queue/README +9 -0
- data/examples/queue/client.rb +31 -0
- data/examples/queue/queue.rb +51 -0
- data/examples/queue/send.rb +9 -0
- data/examples/service.rb +2 -2
- data/examples/tcp.rb +1 -1
- data/lib/cod.rb +47 -82
- data/lib/cod/beanstalk.rb +7 -0
- data/lib/cod/beanstalk/channel.rb +170 -0
- data/lib/cod/beanstalk/serializer.rb +80 -0
- data/lib/cod/beanstalk/service.rb +53 -0
- data/lib/cod/channel.rb +54 -12
- data/lib/cod/pipe.rb +188 -0
- data/lib/cod/select.rb +47 -0
- data/lib/cod/select_group.rb +87 -0
- data/lib/cod/service.rb +55 -42
- data/lib/cod/simple_serializer.rb +19 -0
- data/lib/cod/tcp_client.rb +202 -0
- data/lib/cod/tcp_server.rb +124 -0
- data/lib/cod/work_queue.rb +129 -0
- metadata +31 -45
- data/examples/pubsub/README +0 -12
- data/examples/pubsub/client.rb +0 -13
- data/examples/pubsub/directory.rb +0 -13
- data/examples/service_directory.rb +0 -32
- data/lib/cod/channel/base.rb +0 -185
- data/lib/cod/channel/beanstalk.rb +0 -69
- data/lib/cod/channel/pipe.rb +0 -137
- data/lib/cod/channel/tcp.rb +0 -16
- data/lib/cod/channel/tcpconnection.rb +0 -67
- data/lib/cod/channel/tcpserver.rb +0 -84
- data/lib/cod/client.rb +0 -81
- data/lib/cod/connection/beanstalk.rb +0 -77
- data/lib/cod/directory.rb +0 -98
- data/lib/cod/directory/countdown.rb +0 -31
- data/lib/cod/directory/subscription.rb +0 -59
- data/lib/cod/object_io.rb +0 -6
- data/lib/cod/objectio/connection.rb +0 -106
- data/lib/cod/objectio/reader.rb +0 -98
- data/lib/cod/objectio/serializer.rb +0 -26
- data/lib/cod/objectio/writer.rb +0 -27
- data/lib/cod/topic.rb +0 -95
@@ -1,69 +0,0 @@
|
|
1
|
-
|
2
|
-
begin
|
3
|
-
require 'beanstalk-client'
|
4
|
-
rescue LoadError
|
5
|
-
fail "You should install the gem 'beanstalk-client' to use Cod::Channel::Beanstalk."
|
6
|
-
end
|
7
|
-
|
8
|
-
module Cod
|
9
|
-
# Also see Channel::Base for more documentation.
|
10
|
-
#
|
11
|
-
class Channel::Beanstalk < Channel::Base
|
12
|
-
NONBLOCK_TIMEOUT = 0.01
|
13
|
-
|
14
|
-
# Connection instance that is in use for this channel.
|
15
|
-
attr_reader :connection
|
16
|
-
|
17
|
-
# Name of the queue on the beanstalk server
|
18
|
-
attr_reader :tube_name
|
19
|
-
|
20
|
-
def initialize(connection, name)
|
21
|
-
@connection = connection
|
22
|
-
@tube_name = name.freeze
|
23
|
-
@serializer = ObjectIO::Serializer.new
|
24
|
-
end
|
25
|
-
|
26
|
-
def initialize_copy(from)
|
27
|
-
@connection = from.connection.dup
|
28
|
-
@tube_name = from.tube_name
|
29
|
-
end
|
30
|
-
|
31
|
-
def put(message)
|
32
|
-
buffer = @serializer.serialize(message)
|
33
|
-
connection.put(tube_name, buffer)
|
34
|
-
end
|
35
|
-
|
36
|
-
def waiting?
|
37
|
-
connection.waiting?(tube_name)
|
38
|
-
end
|
39
|
-
|
40
|
-
def get(opts={})
|
41
|
-
message = connection.get(tube_name,
|
42
|
-
:timeout => opts[:timeout])
|
43
|
-
return @serializer.deserialize(nil, message)
|
44
|
-
rescue Beanstalk::TimedOut
|
45
|
-
raise Channel::TimeoutError, "No messages waiting in #{tube_name}."
|
46
|
-
end
|
47
|
-
|
48
|
-
def close
|
49
|
-
connection.close
|
50
|
-
end
|
51
|
-
|
52
|
-
def identifier
|
53
|
-
identifier_class.new(connection.url, tube_name)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
class Channel::Beanstalk::Identifier
|
58
|
-
def initialize(url, tube_name)
|
59
|
-
@url, @tube_name = url, tube_name
|
60
|
-
end
|
61
|
-
|
62
|
-
def resolve(ctxt=nil)
|
63
|
-
raise NotImplementedError, "Explicit context not yet implemented." \
|
64
|
-
if ctxt
|
65
|
-
|
66
|
-
Cod.beanstalk(@url, @tube_name)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/cod/channel/pipe.rb
DELETED
@@ -1,137 +0,0 @@
|
|
1
|
-
module Cod
|
2
|
-
# A channel that uses IO.pipe as its transport mechanism. This means that
|
3
|
-
# you can only communicate within a single process hierarchy using this,
|
4
|
-
# since the file descriptors are not visible to the outside world.
|
5
|
-
#
|
6
|
-
# Also see Channel::Base for more documentation.
|
7
|
-
#
|
8
|
-
class Channel::Pipe < Channel::Base
|
9
|
-
# A tuple storing the read and the write end of a IO.pipe.
|
10
|
-
#
|
11
|
-
Fds = Struct.new(:r, :w)
|
12
|
-
|
13
|
-
attr_reader :fds
|
14
|
-
|
15
|
-
def initialize(name=nil)
|
16
|
-
@fds = Fds.new(*IO.pipe)
|
17
|
-
|
18
|
-
init_in_and_out
|
19
|
-
end
|
20
|
-
|
21
|
-
def initialize_copy(old)
|
22
|
-
old_fds = old.fds
|
23
|
-
|
24
|
-
raise ArgumentError,
|
25
|
-
"Dupping a pipe channel only makes sense if it is still unused." \
|
26
|
-
unless old_fds.r && old_fds.w
|
27
|
-
|
28
|
-
@fds = Fds.new(
|
29
|
-
old_fds.r.dup,
|
30
|
-
old_fds.w.dup)
|
31
|
-
|
32
|
-
init_in_and_out
|
33
|
-
end
|
34
|
-
|
35
|
-
def put(message)
|
36
|
-
close_read
|
37
|
-
|
38
|
-
unless fds.w
|
39
|
-
direction_error 'Cannot put data to pipe. Already closed that end?'
|
40
|
-
end
|
41
|
-
|
42
|
-
@out.put(message)
|
43
|
-
rescue Errno::EPIPE
|
44
|
-
direction_error "You should #dup before writing; Looks like no other copy exists currently."
|
45
|
-
end
|
46
|
-
|
47
|
-
def waiting?
|
48
|
-
@in.waiting?
|
49
|
-
rescue EOFError
|
50
|
-
# We've just hit end of file in the pipe. That means that all write
|
51
|
-
# ends have been closed.
|
52
|
-
@in.queued?
|
53
|
-
end
|
54
|
-
|
55
|
-
def get(opts={})
|
56
|
-
close_write
|
57
|
-
|
58
|
-
unless fds.r
|
59
|
-
direction_error 'Cannot get data from pipe. Already closed that end?'
|
60
|
-
end
|
61
|
-
|
62
|
-
@in.get(opts)
|
63
|
-
rescue EOFError
|
64
|
-
# We've just hit end of file in the pipe. That means that all write
|
65
|
-
# ends have been closed.
|
66
|
-
communication_error "All write ends for this pipe have been closed. "+
|
67
|
-
"Further #get's would block forever." \
|
68
|
-
unless @in.queued?
|
69
|
-
|
70
|
-
# rescue Errno::EPIPE
|
71
|
-
# direction_error 'Cannot get data from pipe. Already closed that end?'
|
72
|
-
end
|
73
|
-
|
74
|
-
def close
|
75
|
-
close_write
|
76
|
-
close_read
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
def init_in_and_out
|
81
|
-
serializer = ObjectIO::Serializer.new
|
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 }
|
86
|
-
end
|
87
|
-
|
88
|
-
def close_write
|
89
|
-
return unless fds.w
|
90
|
-
fds.w.close
|
91
|
-
|
92
|
-
fds.w = nil
|
93
|
-
@out = nil
|
94
|
-
end
|
95
|
-
|
96
|
-
def close_read
|
97
|
-
return unless fds.r
|
98
|
-
fds.r.close
|
99
|
-
|
100
|
-
fds.r = nil
|
101
|
-
@in = nil
|
102
|
-
end
|
103
|
-
|
104
|
-
# # Tries hard to empty the pipe and to store incoming messages in
|
105
|
-
# # @waiting_messages.
|
106
|
-
# #
|
107
|
-
# def process_inbound_nonblock
|
108
|
-
# buffer = fds.r.read_nonblock(1024*1024*1024)
|
109
|
-
#
|
110
|
-
# while buffer.size > 0
|
111
|
-
# @waiting_messages << transport_unpack(buffer)
|
112
|
-
# end
|
113
|
-
rescue EOFError
|
114
|
-
# We've just hit end of file in the pipe. That means that all write
|
115
|
-
# ends have been closed.
|
116
|
-
communication_error "All write ends for this pipe have been closed. "+
|
117
|
-
"Further #get's would block forever." \
|
118
|
-
unless queued?
|
119
|
-
# rescue Errno::EAGAIN
|
120
|
-
# # Catch and ignore this: fds.r is not ready and read would block.
|
121
|
-
# end
|
122
|
-
end
|
123
|
-
|
124
|
-
class Channel::Pipe::Identifier
|
125
|
-
def initialize(channel)
|
126
|
-
@objid = channel.object_id
|
127
|
-
end
|
128
|
-
|
129
|
-
def resolve
|
130
|
-
ObjectSpace._id2ref(@objid).dup
|
131
|
-
rescue RangeError
|
132
|
-
raise Cod::InvalidIdentifier,
|
133
|
-
"Could not reference channel. Either it was garbage collected "+
|
134
|
-
"or it never existed in this process."
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
data/lib/cod/channel/tcp.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
|
3
|
-
module Cod
|
4
|
-
module Channel::TCP
|
5
|
-
def split_uri(uri)
|
6
|
-
colon = uri.index(':')
|
7
|
-
raise ArgumentError,
|
8
|
-
"TCP points must include a port number, #{uri.inspect} does not." \
|
9
|
-
unless colon
|
10
|
-
|
11
|
-
[
|
12
|
-
colon == 0 ? nil : uri[0...colon],
|
13
|
-
Integer(uri[colon+1..-1])]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
require 'cod/channel/tcp'
|
2
|
-
|
3
|
-
module Cod
|
4
|
-
# A channel based on a tcp connection.
|
5
|
-
#
|
6
|
-
# Also see Channel::Base for more documentation.
|
7
|
-
#
|
8
|
-
class Channel::TCPConnection < Channel::Base
|
9
|
-
include Channel::TCP
|
10
|
-
|
11
|
-
# A <host, port> tuple: The target of this connection.
|
12
|
-
#
|
13
|
-
attr_reader :destination
|
14
|
-
|
15
|
-
attr_reader :connection_pool
|
16
|
-
|
17
|
-
def initialize(destination_or_connection)
|
18
|
-
if destination_or_connection.respond_to?(:to_str)
|
19
|
-
@destination = split_uri(destination_or_connection)
|
20
|
-
@connection = nil
|
21
|
-
else
|
22
|
-
@destination = nil
|
23
|
-
@connection = destination_or_connection
|
24
|
-
end
|
25
|
-
|
26
|
-
serializer = ObjectIO::Serializer.new
|
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))
|
32
|
-
end
|
33
|
-
|
34
|
-
def connected?
|
35
|
-
waiting?
|
36
|
-
connection_pool.size > 0
|
37
|
-
end
|
38
|
-
|
39
|
-
def identifier
|
40
|
-
Identifier.new(destination)
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
def connect
|
45
|
-
if destination
|
46
|
-
TCPSocket.new(*destination)
|
47
|
-
else
|
48
|
-
@connection
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
class Channel::TCPConnection::Identifier
|
54
|
-
def initialize(destination)
|
55
|
-
@destination = destination
|
56
|
-
end
|
57
|
-
|
58
|
-
def resolve
|
59
|
-
# If we've been sent to our own server end, assume the role of the
|
60
|
-
# socket on that side. This is achieved by inserting the self into
|
61
|
-
# the stream of deserialized objects and having the transformer
|
62
|
-
# (attached to the serializer, see ObjectIO::Serializer) transform
|
63
|
-
# it into a valid connection object.
|
64
|
-
self
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'cod/channel/tcp'
|
2
|
-
|
3
|
-
module Cod
|
4
|
-
# A channel based on a passive tcp server (listening).
|
5
|
-
#
|
6
|
-
# Also see Channel::Base for more documentation.
|
7
|
-
#
|
8
|
-
class Channel::TCPServer < Channel::Base
|
9
|
-
include Channel::TCP
|
10
|
-
|
11
|
-
# The <host, port> tuple this server is bound to.
|
12
|
-
attr_reader :bind_to
|
13
|
-
|
14
|
-
# The actual TCPServer instance
|
15
|
-
attr_reader :server
|
16
|
-
|
17
|
-
# Sockets that are open to clients
|
18
|
-
attr_reader :connections
|
19
|
-
|
20
|
-
def initialize(bind_to)
|
21
|
-
@bind_to = split_uri(bind_to)
|
22
|
-
@server = TCPServer.new(*@bind_to)
|
23
|
-
|
24
|
-
connection_pool = ObjectIO::Connection::Pool.new { accept_connections(server) }
|
25
|
-
serializer = ObjectIO::Serializer.new(self)
|
26
|
-
|
27
|
-
super(
|
28
|
-
ObjectIO::Reader.new(serializer, connection_pool),
|
29
|
-
nil)
|
30
|
-
end
|
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
|
-
#
|
37
|
-
def put(message)
|
38
|
-
communication_error "You cannot write to the server directly, transmit a "
|
39
|
-
"channel to the server instead."
|
40
|
-
end
|
41
|
-
|
42
|
-
def close
|
43
|
-
super
|
44
|
-
|
45
|
-
server.close if server
|
46
|
-
@server = nil
|
47
|
-
end
|
48
|
-
|
49
|
-
def transform(socket, obj)
|
50
|
-
if obj.kind_of?(Channel::TCPConnection::Identifier)
|
51
|
-
# We've been sent 'a' tcp channel. Assume that it's our own client end
|
52
|
-
# that we've been sent and turn it into a channel that communicates
|
53
|
-
# back there.
|
54
|
-
return Channel::TCPConnection.new(socket)
|
55
|
-
end
|
56
|
-
|
57
|
-
return obj
|
58
|
-
end
|
59
|
-
|
60
|
-
def identifier
|
61
|
-
communication_error "TCP server channels cannot be transmitted."
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
# Accept all pending connects and stores them in the connections array.
|
67
|
-
#
|
68
|
-
def accept_connections(server)
|
69
|
-
connections = []
|
70
|
-
loop do
|
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
|
79
|
-
end
|
80
|
-
|
81
|
-
fail "NOTREACHED: return should be from loop."
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
data/lib/cod/client.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
|
2
|
-
module Cod
|
3
|
-
# A client that consumes a service (Cod::Service).
|
4
|
-
#
|
5
|
-
class Client
|
6
|
-
attr_reader :incoming
|
7
|
-
attr_reader :outgoing
|
8
|
-
|
9
|
-
# Create a new client and tie it to an answer channel. The answer channel
|
10
|
-
# will often be anonymous - no one except the service needs to write
|
11
|
-
# there.
|
12
|
-
#
|
13
|
-
def initialize(requests, answers, timeout=1)
|
14
|
-
@timeout = timeout
|
15
|
-
@incoming = answers
|
16
|
-
@outgoing = requests
|
17
|
-
@request_id = 0
|
18
|
-
end
|
19
|
-
|
20
|
-
# Calls the service in a synchronous fashion. Returns the message the
|
21
|
-
# server sends back.
|
22
|
-
#
|
23
|
-
def call(message=nil)
|
24
|
-
expected_id = next_request_id
|
25
|
-
outgoing.put envelope(expected_id, message, incoming, true)
|
26
|
-
|
27
|
-
start_time = Time.now
|
28
|
-
loop do
|
29
|
-
received_id, answer = incoming.get(:timeout => @timeout)
|
30
|
-
return answer if received_id == expected_id
|
31
|
-
|
32
|
-
# We're receiving answers with request_ids that are outside the
|
33
|
-
# window that we would expect. Something is seriously amiss.
|
34
|
-
raise Cod::Channel::CommunicationError,
|
35
|
-
"Missed request." unless earlier?(expected_id, received_id)
|
36
|
-
|
37
|
-
# We've been waiting (and consuming answers) for too long - overall
|
38
|
-
# timeout has elapsed.
|
39
|
-
raise Cod::Channel::TimeoutError,
|
40
|
-
"Timed out while waiting for service request answer." \
|
41
|
-
if (Time.now-start_time) > @timeout
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# This sends the server a message without waiting for an answer. The
|
46
|
-
# server will throw away the answer produced.
|
47
|
-
#
|
48
|
-
def notify(message=nil)
|
49
|
-
outgoing.put envelope(next_request_id, message, incoming, false)
|
50
|
-
nil
|
51
|
-
end
|
52
|
-
|
53
|
-
# Closes all resources that are held in the client.
|
54
|
-
#
|
55
|
-
def close
|
56
|
-
incoming.close
|
57
|
-
outgoing.close
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
# Creates a message to send to the service.
|
63
|
-
#
|
64
|
-
def envelope(id, message, incoming_channel, needs_answer)
|
65
|
-
[id, message, incoming_channel, needs_answer]
|
66
|
-
end
|
67
|
-
|
68
|
-
# Returns a sequence of request ids.
|
69
|
-
#
|
70
|
-
def next_request_id
|
71
|
-
@request_id += 1
|
72
|
-
end
|
73
|
-
|
74
|
-
# True if the received request id answers a request that has been
|
75
|
-
# earlier than the expected request id.
|
76
|
-
#
|
77
|
-
def earlier?(expected, received)
|
78
|
-
expected > received
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|