cod 0.3.1 → 0.4.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 +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,77 +0,0 @@
|
|
1
|
-
module Cod
|
2
|
-
# Wraps the lower level beanstalk connection and exposes only methods that
|
3
|
-
# we need; also makes tube handling a bit more predictable.
|
4
|
-
#
|
5
|
-
# This class is NOT thread safe.
|
6
|
-
#
|
7
|
-
class Connection::Beanstalk
|
8
|
-
# The url that was used to connect to the beanstalk server.
|
9
|
-
attr_reader :url
|
10
|
-
|
11
|
-
# Connection to the beanstalk server.
|
12
|
-
attr_reader :connection
|
13
|
-
|
14
|
-
def initialize(url)
|
15
|
-
@url = url
|
16
|
-
connect
|
17
|
-
end
|
18
|
-
|
19
|
-
def initialize_copy(from)
|
20
|
-
@url = from.url
|
21
|
-
connect
|
22
|
-
end
|
23
|
-
|
24
|
-
# Writes a raw message as a job to the tube given by name.
|
25
|
-
#
|
26
|
-
def put(name, message)
|
27
|
-
connection.use name
|
28
|
-
# TODO throws EOFError when the beanstalkd server goes away
|
29
|
-
connection.put message
|
30
|
-
end
|
31
|
-
|
32
|
-
# Returns true if there are jobs waiting in the tube given by 'name'
|
33
|
-
def waiting?(name)
|
34
|
-
connection.stats_tube(name)['current-jobs-ready'] > 0
|
35
|
-
rescue Beanstalk::NotFoundError
|
36
|
-
# Tube could not be found. No one has written to it! Nothing is waiting.
|
37
|
-
false
|
38
|
-
end
|
39
|
-
|
40
|
-
# Removes and returns the next message waiting in the tube given by name.
|
41
|
-
#
|
42
|
-
def get(name, opts={})
|
43
|
-
watch(name) do
|
44
|
-
# TODO throws EOFError when the beanstalkd queue goes away.
|
45
|
-
job = connection.reserve(opts[:timeout])
|
46
|
-
job.delete
|
47
|
-
|
48
|
-
job.body
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Closes the connection
|
53
|
-
#
|
54
|
-
def close
|
55
|
-
connection.close if connection
|
56
|
-
@connection = nil
|
57
|
-
end
|
58
|
-
|
59
|
-
# Creates a connection
|
60
|
-
#
|
61
|
-
def connect
|
62
|
-
# TODO throws Errno::ECONNREFUSED if the beanstalkd doesn't answer
|
63
|
-
@connection = Beanstalk::Connection.new(url)
|
64
|
-
@watching = nil
|
65
|
-
end
|
66
|
-
private
|
67
|
-
def watch(name)
|
68
|
-
unless @watching == name
|
69
|
-
connection.ignore(@watching)
|
70
|
-
connection.watch(name)
|
71
|
-
@watching = name
|
72
|
-
end
|
73
|
-
|
74
|
-
yield
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
data/lib/cod/directory.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
module Cod
|
2
|
-
# A directory where one can publish messages given a topic string.
|
3
|
-
#
|
4
|
-
# The channel given will be where people should subscribe.
|
5
|
-
#
|
6
|
-
class Directory
|
7
|
-
# The channel the directory receives subscription messages on.
|
8
|
-
#
|
9
|
-
attr_reader :channel
|
10
|
-
|
11
|
-
# Subscriptions this directory handles.
|
12
|
-
#
|
13
|
-
attr_reader :subscriptions
|
14
|
-
|
15
|
-
def initialize(channel)
|
16
|
-
@channel = channel
|
17
|
-
@subscriptions = Set.new
|
18
|
-
end
|
19
|
-
|
20
|
-
# Sends the message to all subscribers that listen to this topic. Returns
|
21
|
-
# the number of subscribers this message has been sent to.
|
22
|
-
#
|
23
|
-
def publish(topic, message)
|
24
|
-
process_control_messages
|
25
|
-
|
26
|
-
n = 0
|
27
|
-
failed_subscriptions = []
|
28
|
-
for subscription in @subscriptions
|
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
|
39
|
-
end
|
40
|
-
|
41
|
-
process_control_messages
|
42
|
-
|
43
|
-
remove_subscriptions { |sub| failed_subscriptions.include?(sub) }
|
44
|
-
return n
|
45
|
-
end
|
46
|
-
|
47
|
-
# Closes all resources used by the directory.
|
48
|
-
#
|
49
|
-
def close
|
50
|
-
channel.close
|
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
|
62
|
-
|
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
|
68
|
-
while channel.waiting?
|
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
|
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.
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
def remove_subscriptions(&block)
|
93
|
-
@subscriptions.delete_if(&block)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
require 'cod/directory/countdown'
|
@@ -1,31 +0,0 @@
|
|
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,59 +0,0 @@
|
|
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.
|
7
|
-
#
|
8
|
-
class Subscription
|
9
|
-
attr_reader :matcher
|
10
|
-
attr_reader :channel
|
11
|
-
attr_reader :countdown
|
12
|
-
|
13
|
-
def initialize(matcher, channel, topic_id)
|
14
|
-
@matcher = matcher
|
15
|
-
@channel = channel
|
16
|
-
@countdown = Countdown.new
|
17
|
-
@identifier = topic_id
|
18
|
-
end
|
19
|
-
|
20
|
-
def ===(other)
|
21
|
-
matcher === other
|
22
|
-
end
|
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
|
-
|
36
|
-
def put(msg)
|
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)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/lib/cod/object_io.rb
DELETED
@@ -1,106 +0,0 @@
|
|
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
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
module Cod::ObjectIO
|
2
|
-
# Reads objects from one or more IO streams.
|
3
|
-
#
|
4
|
-
class Reader
|
5
|
-
attr_reader :waiting_messages
|
6
|
-
attr_reader :pool
|
7
|
-
|
8
|
-
# Initializes an object reader that reads from one or several IO objects.
|
9
|
-
#
|
10
|
-
# Example:
|
11
|
-
# connection = Connection::Pool.new { make_new_connection }
|
12
|
-
# reader = Reader.new(serializer, connection)
|
13
|
-
#
|
14
|
-
def initialize(serializer, conn_pool)
|
15
|
-
@serializer = serializer
|
16
|
-
@waiting_messages = []
|
17
|
-
@pool = conn_pool
|
18
|
-
end
|
19
|
-
|
20
|
-
def get(opts={})
|
21
|
-
return waiting_messages.shift if queued?
|
22
|
-
|
23
|
-
start_time = Time.now
|
24
|
-
loop do
|
25
|
-
read_from_wire opts
|
26
|
-
|
27
|
-
# Early return in case we have a message waiting
|
28
|
-
return waiting_messages.shift if queued?
|
29
|
-
|
30
|
-
if opts[:timeout] && (Time.now-start_time) > opts[:timeout]
|
31
|
-
raise Cod::Channel::TimeoutError,
|
32
|
-
"No messages waiting in pipe."
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
fail "NOTREACHED"
|
37
|
-
end
|
38
|
-
|
39
|
-
def waiting?
|
40
|
-
read_from_wire
|
41
|
-
queued?
|
42
|
-
rescue Cod::Channel::CommunicationError
|
43
|
-
queued?
|
44
|
-
end
|
45
|
-
|
46
|
-
def queued?
|
47
|
-
! waiting_messages.empty?
|
48
|
-
end
|
49
|
-
|
50
|
-
def close
|
51
|
-
@pool.close
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
# Checks if data is waiting and processes messages.
|
57
|
-
#
|
58
|
-
def read_from_wire(opts={})
|
59
|
-
# Establish new connections and register them
|
60
|
-
@pool.accept
|
61
|
-
|
62
|
-
# Process all waiting data
|
63
|
-
process_nonblock(@pool.connections)
|
64
|
-
end
|
65
|
-
|
66
|
-
# Reads all data waiting in each io in the ios array.
|
67
|
-
#
|
68
|
-
def process_nonblock(ios)
|
69
|
-
ios.each do |io|
|
70
|
-
process_nonblock_single(io)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Reads all data waiting in a single io.
|
75
|
-
#
|
76
|
-
def process_nonblock_single(io)
|
77
|
-
buffer = io.read_nonblock(1024*1024*1024)
|
78
|
-
|
79
|
-
sio = StringIO.new(buffer)
|
80
|
-
while not sio.eof?
|
81
|
-
waiting_messages << deserialize(io, sio)
|
82
|
-
end
|
83
|
-
rescue Errno::EAGAIN
|
84
|
-
# read failed because there was no data. This is expected.
|
85
|
-
return
|
86
|
-
rescue EOFError
|
87
|
-
@pool.report_failed(io)
|
88
|
-
end
|
89
|
-
|
90
|
-
# Deserializes a message (in message format, string) into the object that
|
91
|
-
# was transmitted. Overwrite this message if you want to control the
|
92
|
-
# message format.
|
93
|
-
#
|
94
|
-
def deserialize(*args)
|
95
|
-
@serializer.deserialize(*args)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|