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
@@ -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