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.
Files changed (50) hide show
  1. data/Gemfile +1 -1
  2. data/HISTORY.txt +5 -1
  3. data/README +12 -13
  4. data/Rakefile +7 -1
  5. data/examples/{ping.rb → ping_pong/ping.rb} +1 -1
  6. data/examples/{pong.rb → ping_pong/pong.rb} +1 -1
  7. data/examples/{presence_client.rb → presence/client.rb} +0 -0
  8. data/examples/{presence_server.rb → presence/server.rb} +0 -0
  9. data/examples/queue/README +9 -0
  10. data/examples/queue/client.rb +31 -0
  11. data/examples/queue/queue.rb +51 -0
  12. data/examples/queue/send.rb +9 -0
  13. data/examples/service.rb +2 -2
  14. data/examples/tcp.rb +1 -1
  15. data/lib/cod.rb +47 -82
  16. data/lib/cod/beanstalk.rb +7 -0
  17. data/lib/cod/beanstalk/channel.rb +170 -0
  18. data/lib/cod/beanstalk/serializer.rb +80 -0
  19. data/lib/cod/beanstalk/service.rb +53 -0
  20. data/lib/cod/channel.rb +54 -12
  21. data/lib/cod/pipe.rb +188 -0
  22. data/lib/cod/select.rb +47 -0
  23. data/lib/cod/select_group.rb +87 -0
  24. data/lib/cod/service.rb +55 -42
  25. data/lib/cod/simple_serializer.rb +19 -0
  26. data/lib/cod/tcp_client.rb +202 -0
  27. data/lib/cod/tcp_server.rb +124 -0
  28. data/lib/cod/work_queue.rb +129 -0
  29. metadata +31 -45
  30. data/examples/pubsub/README +0 -12
  31. data/examples/pubsub/client.rb +0 -13
  32. data/examples/pubsub/directory.rb +0 -13
  33. data/examples/service_directory.rb +0 -32
  34. data/lib/cod/channel/base.rb +0 -185
  35. data/lib/cod/channel/beanstalk.rb +0 -69
  36. data/lib/cod/channel/pipe.rb +0 -137
  37. data/lib/cod/channel/tcp.rb +0 -16
  38. data/lib/cod/channel/tcpconnection.rb +0 -67
  39. data/lib/cod/channel/tcpserver.rb +0 -84
  40. data/lib/cod/client.rb +0 -81
  41. data/lib/cod/connection/beanstalk.rb +0 -77
  42. data/lib/cod/directory.rb +0 -98
  43. data/lib/cod/directory/countdown.rb +0 -31
  44. data/lib/cod/directory/subscription.rb +0 -59
  45. data/lib/cod/object_io.rb +0 -6
  46. data/lib/cod/objectio/connection.rb +0 -106
  47. data/lib/cod/objectio/reader.rb +0 -98
  48. data/lib/cod/objectio/serializer.rb +0 -26
  49. data/lib/cod/objectio/writer.rb +0 -27
  50. 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,6 +0,0 @@
1
- module Cod::ObjectIO; end
2
-
3
- require 'cod/objectio/connection'
4
- require 'cod/objectio/reader'
5
- require 'cod/objectio/writer'
6
- require 'cod/objectio/serializer'
@@ -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
@@ -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