cod 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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