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
@@ -0,0 +1,129 @@
1
+
2
+
3
+ module Cod
4
+ # Describes a queue that stores work items given through #schedule and works
5
+ # through those items in order if #predicate is true.
6
+ #
7
+ # Synopsis:
8
+ # queue = WorkQueue.new
9
+ # queue.predicate { true }
10
+ # queue.schedule {
11
+ # # some work
12
+ # }
13
+ #
14
+ # # Will try to work through items right now.
15
+ # queue.try_work
16
+ #
17
+ # # Will cleanly shutdown background threads, but not finish work.
18
+ # queue.shutdown
19
+ #
20
+ class WorkQueue # :nodoc:
21
+ def initialize
22
+ # NOTE: This is an array that is protected by careful coding, rather
23
+ # than a mutex. Queue would be right, but Rubys GIL will interfere with
24
+ # that producing more deadlocks than I would like.
25
+ @queue = Array.new
26
+
27
+ @try_work_exclusive_section = ExclusiveSection.new
28
+
29
+ @thread = Thread.start(&method(:thread_main))
30
+ end
31
+
32
+ # The internal thread that is used to work on scheduled items in the
33
+ # background.
34
+ attr_reader :thread
35
+
36
+ def try_work
37
+ @try_work_exclusive_section.enter {
38
+ # NOTE if predicate is nil or not set, no work will be accomplished.
39
+ # This is the way I need it.
40
+ while !@queue.empty? && @predicate && @predicate.call
41
+ wi = @queue.shift
42
+ wi.call
43
+ end
44
+ }
45
+ end
46
+
47
+ # Before any kind of work is attempted, this predicate must evaluate to
48
+ # true. It is tested repeatedly.
49
+ #
50
+ # Example:
51
+ # work_queue.predicate { connection.established? }
52
+ #
53
+ def predicate(&predicate)
54
+ @predicate = predicate
55
+ end
56
+
57
+ # Schedules a piece of work.
58
+ # Example:
59
+ # work_queue.schedule { a_piece_of_work }
60
+ #
61
+ def schedule(&work)
62
+ @queue << work
63
+ end
64
+
65
+ # Shuts down the queue properly, without waiting for work to be completed.
66
+ #
67
+ def shutdown
68
+ @shutdown_requested = true
69
+ @thread.join
70
+ end
71
+
72
+ # Returns the size of the queue.
73
+ #
74
+ def size
75
+ @queue.size
76
+ end
77
+
78
+ def clear_thread_semaphore
79
+ @one_turn = false
80
+ end
81
+ def thread_semaphore_set?
82
+ @one_turn
83
+ end
84
+ private
85
+ def thread_main
86
+ Thread.current.abort_on_exception = true
87
+
88
+ loop do
89
+ sleep 0.01
90
+
91
+ try_work
92
+
93
+ # Signal the outside world that we've been around this loop once.
94
+ # See #clear_thread_semaphore and #thread_semaphore_set?
95
+ @one_turn = true
96
+
97
+ return if @shutdown_requested
98
+ end
99
+ end
100
+ end
101
+
102
+ # A section of code that is entered only once. Instead of blocking threads
103
+ # that are waiting to enter, it just returns nil.
104
+ #
105
+ class ExclusiveSection # :nodoc:
106
+ def initialize
107
+ @mutex = Mutex.new
108
+ @threads_in_block = 0
109
+ end
110
+
111
+ # If no one is in the block given to #enter currently, this will yield
112
+ # to the block. If one thread is already executing that block, it will
113
+ # return nil.
114
+ #
115
+ def enter
116
+ @mutex.synchronize {
117
+ return if @threads_in_block > 0
118
+ @threads_in_block += 1 }
119
+ begin
120
+ yield
121
+ ensure
122
+ fail "Assert fails, #{@threads_in_block} threads in block" \
123
+ if @threads_in_block != 1
124
+ @mutex.synchronize {
125
+ @threads_in_block -= 1 }
126
+ end
127
+ end
128
+ end
129
+ end
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.1
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-20 00:00:00.000000000Z
12
+ date: 2011-11-29 00:00:00.000000000Z
13
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
25
14
  - !ruby/object:Gem::Dependency
26
15
  name: rspec
27
- requirement: &70169583689920 !ruby/object:Gem::Requirement
16
+ requirement: &70297088779060 !ruby/object:Gem::Requirement
28
17
  none: false
29
18
  requirements:
30
19
  - - ! '>='
@@ -32,10 +21,10 @@ dependencies:
32
21
  version: '0'
33
22
  type: :development
34
23
  prerelease: false
35
- version_requirements: *70169583689920
24
+ version_requirements: *70297088779060
36
25
  - !ruby/object:Gem::Dependency
37
26
  name: flexmock
38
- requirement: &70169583689340 !ruby/object:Gem::Requirement
27
+ requirement: &70297088777820 !ruby/object:Gem::Requirement
39
28
  none: false
40
29
  requirements:
41
30
  - - ! '>='
@@ -43,10 +32,10 @@ dependencies:
43
32
  version: '0'
44
33
  type: :development
45
34
  prerelease: false
46
- version_requirements: *70169583689340
35
+ version_requirements: *70297088777820
47
36
  - !ruby/object:Gem::Dependency
48
37
  name: sdoc
49
- requirement: &70169583688800 !ruby/object:Gem::Requirement
38
+ requirement: &70297088776720 !ruby/object:Gem::Requirement
50
39
  none: false
51
40
  requirements:
52
41
  - - ! '>='
@@ -54,7 +43,7 @@ dependencies:
54
43
  version: '0'
55
44
  type: :development
56
45
  prerelease: false
57
- version_requirements: *70169583688800
46
+ version_requirements: *70297088776720
58
47
  description:
59
48
  email: kaspar.schiess@absurd.li
60
49
  executables: []
@@ -67,37 +56,31 @@ files:
67
56
  - LICENSE
68
57
  - Rakefile
69
58
  - README
70
- - lib/cod/channel/base.rb
71
- - lib/cod/channel/beanstalk.rb
72
- - lib/cod/channel/pipe.rb
73
- - lib/cod/channel/tcp.rb
74
- - lib/cod/channel/tcpconnection.rb
75
- - lib/cod/channel/tcpserver.rb
59
+ - lib/cod/beanstalk/channel.rb
60
+ - lib/cod/beanstalk/serializer.rb
61
+ - lib/cod/beanstalk/service.rb
62
+ - lib/cod/beanstalk.rb
76
63
  - lib/cod/channel.rb
77
- - lib/cod/client.rb
78
- - lib/cod/connection/beanstalk.rb
79
- - lib/cod/directory/countdown.rb
80
- - lib/cod/directory/subscription.rb
81
- - lib/cod/directory.rb
82
- - lib/cod/object_io.rb
83
- - lib/cod/objectio/connection.rb
84
- - lib/cod/objectio/reader.rb
85
- - lib/cod/objectio/serializer.rb
86
- - lib/cod/objectio/writer.rb
64
+ - lib/cod/pipe.rb
65
+ - lib/cod/select.rb
66
+ - lib/cod/select_group.rb
87
67
  - lib/cod/service.rb
88
- - lib/cod/topic.rb
68
+ - lib/cod/simple_serializer.rb
69
+ - lib/cod/tcp_client.rb
70
+ - lib/cod/tcp_server.rb
71
+ - lib/cod/work_queue.rb
89
72
  - lib/cod.rb
90
73
  - examples/example_scaffold.rb
91
74
  - examples/master_child.rb
92
- - examples/ping.rb
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
75
+ - examples/ping_pong/ping.rb
76
+ - examples/ping_pong/pong.rb
77
+ - examples/presence/client.rb
78
+ - examples/presence/server.rb
79
+ - examples/queue/client.rb
80
+ - examples/queue/queue.rb
81
+ - examples/queue/README
82
+ - examples/queue/send.rb
99
83
  - examples/service.rb
100
- - examples/service_directory.rb
101
84
  - examples/tcp.rb
102
85
  homepage: http://kschiess.github.com/cod
103
86
  licenses: []
@@ -113,6 +96,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
96
  - - ! '>='
114
97
  - !ruby/object:Gem::Version
115
98
  version: '0'
99
+ segments:
100
+ - 0
101
+ hash: -3574736004851783821
116
102
  required_rubygems_version: !ruby/object:Gem::Requirement
117
103
  none: false
118
104
  requirements:
@@ -121,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
107
  version: '0'
122
108
  requirements: []
123
109
  rubyforge_project:
124
- rubygems_version: 1.8.6
110
+ rubygems_version: 1.8.10
125
111
  signing_key:
126
112
  specification_version: 3
127
113
  summary: Really simple IPC.
@@ -1,12 +0,0 @@
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
-
@@ -1,13 +0,0 @@
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
-
@@ -1,13 +0,0 @@
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
-
@@ -1,32 +0,0 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
2
- require 'cod'
3
-
4
- announce = Cod.pipe
5
- directory = Cod::Directory.new(announce)
6
-
7
- pipes = []
8
- pids = ['foo.bar', /^foo\..+/].map { |match_expr|
9
- # Creates a communication channel that both the parent and the child know
10
- # about. After the fork, they will own unique ends to that channel.
11
- pipes << pipe = Cod.pipe # store in pipes as well to prevent GC
12
-
13
- # Create a child that will receive messages that match match_expr.
14
- fork do
15
- puts "Spawned child: #{Process.pid}"
16
- topic = Cod::Topic.new(match_expr, announce, pipe)
17
-
18
- sleep 0.1
19
- loop do
20
- message = topic.get
21
- puts "#{Process.pid}: received #{message.inspect}."
22
-
23
- break if message == :shutdown
24
- end
25
- end }
26
-
27
- directory.publish 'foo.bar', 'Hi everyone!' # to both childs
28
- directory.publish 'foo.baz', 'Hi you!' # only second child matches this
29
- directory.publish 'no.one', 'echo?' # no one matches this
30
-
31
- directory.publish 'foo.bar', :shutdown # shutdown children in orderly fashion
32
- Process.waitall
@@ -1,185 +0,0 @@
1
-
2
- module Cod
3
- # Channels are the communication primitives you've always wanted (secretly).
4
- # They are simple to use, abstract most of the OS away and allow direct
5
- # communication using language objects, not just strings. (Although you can
6
- # fall back to just strings if you want to)
7
- #
8
- # == Construction
9
- #
10
- # The simplest way to obtain a new channel is through the Module Cod. It
11
- # provides the following channel types:
12
- #
13
- # Cod.beanstalk(url, name) :: connects to beanstalkd daemon
14
- # Cod.pipe :: abstracts IO.pipe
15
- # Cod.tcp(url) :: connects to a tcp socket somewhere
16
- # Cod.tcpserver(url) :: the counterpart to Cod.tcp
17
- #
18
- # Please refer to the documentation of these methods for more information.
19
- #
20
- # == Basic Interaction
21
- #
22
- # You can #put messages (Ruby objects) into a channel on one side:
23
- #
24
- # channel.put("some message")
25
- #
26
- # and #get messages on the other side:
27
- #
28
- # channel.get # => "some message"
29
- #
30
- # == Querying
31
- #
32
- # Are there messages waiting to be read from the channel? Use #waiting?:
33
- #
34
- # channel.waiting? # => true or false
35
- #
36
- # == Cleaning up
37
- #
38
- # Be sure to #close the channel once you're done with it.
39
- #
40
- # channel.close
41
- #
42
- class Channel::Base
43
- def initialize(reader, writer)
44
- @reader = reader
45
- @writer = writer
46
- end
47
-
48
- # Writes a Ruby object (the 'message') to the channel. This object will
49
- # be queued in the channel and become available for #get in a FIFO manner.
50
- #
51
- # Issuing a #put may also close the channel instance for subsequent #get's.
52
- #
53
- # Example:
54
- # chan.put 'test'
55
- # chan.put true
56
- # chan.put :symbol
57
- #
58
- def put(message)
59
- # TODO Errno::EPIPE raised after a while when the receiver goes away.
60
- @writer.put(message)
61
- end
62
-
63
- # Reads a Ruby object (a message) from the channel. Some channels may not
64
- # allow reading after you've written to it once. Options that work:
65
- #
66
- # <code>:timeout</code> :: Time to wait before throwing Cod::Channel::TimeoutError.
67
- #
68
- def get(opts={})
69
- @reader.get(opts)
70
- end
71
-
72
- # Returns true if there are messages waiting in the channel.
73
- #
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?
83
- not_implemented
84
- end
85
-
86
- # Closes reader and writer.
87
- #
88
- def close
89
- @reader.close if @reader
90
- @writer.close if @writer
91
-
92
- @reader = @writer = nil
93
- end
94
-
95
- # Returns the Identifier class below the current channel class. This is
96
- # a helper function that should only be used by subclasses.
97
- #
98
- def identifier_class # :nodoc:
99
- self.class.const_get(:Identifier)
100
- end
101
-
102
- # Something to put into the data stream that is transmitted through a
103
- # channel that allows reconstitution of the channel at the other end.
104
- # The invariant is this:
105
- #
106
- # # channel1 and channel2 are abstract channels that illustrate my
107
- # # meaning
108
- # channel1.put channel2
109
- # channel2a = channel1.get
110
- # channel2a.put 'foo'
111
- # channel2.get # => 'foo'
112
- #
113
- # Note that this should also work if channel1 and channel2 are the same.
114
- #
115
- def identifier # :nodoc:
116
- identifier_class.new(self)
117
- end
118
-
119
- # ------------------------------------------------------------ marshalling
120
-
121
- # Makes sure that we don't marshal this object, but the memento object
122
- # returned by identifier.
123
- #
124
- def _dump(d) # :nodoc:
125
- wire_data = to_wire_data
126
- Marshal.dump(wire_data)
127
- end
128
-
129
- # Loads the object from string. This doesn't always return the same kind
130
- # of object that was serialized.
131
- #
132
- def self._load(string) # :nodoc:
133
- wire_data = Marshal.load(string)
134
- from_wire_data(wire_data)
135
- end
136
-
137
- private
138
-
139
- # Returns the objects that need to be transmitted in order to reconstruct
140
- # this object after transmission through the wire.
141
- #
142
- # If you're using a serialisation method other than the Ruby built in
143
- # one, use this to obtain something in lieu of a channel that can be sent
144
- # through the wire and reinterpreted at the other end.
145
- #
146
- # Example:
147
- #
148
- # # this should work:
149
- # obj = channel.to_wire_data
150
- # channel_equiv = Cod::Channel::Base.from_wire_data(obj)
151
- #
152
- def to_wire_data
153
- identifier
154
- end
155
-
156
- # Using an object previously returned by #to_wire_data, reconstitute the
157
- # original channel or something that is alike it. What you send to this
158
- # second channel (#put) you should be able to #get from this copy returned
159
- # here.
160
- #
161
- def self.from_wire_data(obj)
162
- obj.resolve
163
- end
164
-
165
- # ---------------------------------------------------------- error raising
166
-
167
- def direction_error(msg)
168
- raise Cod::Channel::DirectionError, msg
169
- end
170
-
171
- def communication_error(msg)
172
- raise Cod::Channel::CommunicationError, msg
173
- end
174
-
175
- def not_implemented
176
- trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}} # blatantly stolen from dependencies.rb in activesupport
177
- exception = NotImplementedError.new(
178
- "You called a method in Cod::Channel::Base. Missing implementation in "+
179
- "the subclass #{self.class.name}!")
180
- exception.set_backtrace trace
181
-
182
- raise exception
183
- end
184
- end
185
- end