wind_up 0.0.2
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.
- checksums.yaml +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGES.md +9 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +22 -0
- data/README.md +92 -0
- data/Rakefile +16 -0
- data/lib/wind_up/calls.rb +27 -0
- data/lib/wind_up/celluloid_ext.rb +10 -0
- data/lib/wind_up/exceptions.rb +3 -0
- data/lib/wind_up/queue_manager.rb +138 -0
- data/lib/wind_up/queue_proxy.rb +37 -0
- data/lib/wind_up/railtie.rb +7 -0
- data/lib/wind_up/routers.rb +132 -0
- data/lib/wind_up/version.rb +3 -0
- data/lib/wind_up.rb +20 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/wind_up/queue_manager_spec.rb +105 -0
- data/spec/wind_up/queue_spec.rb +372 -0
- data/spec/wind_up/routers_spec.rb +58 -0
- data/spec/wind_up_spec.rb +14 -0
- data/wind_up.gemspec +24 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 79c283c998f19e33759901fca41cfb5c00219db2
|
4
|
+
data.tar.gz: 739bf5be6f00edba35343cc8f1fbf9a15b909ad8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ae64913bcdd2eebcc0bd26f12a8c0649d259b44cb3bb191a35f631c842604791d7111a36a60c62f28d336ca17ed221304f55aad8bcdf83e5085defc0be1c98e
|
7
|
+
data.tar.gz: e13d341414cb2b0ad0c65cdf7757648bbd37110dcc4e5435c782205e81d01153f9c42cab2907d32b38709574218155f138085100c0ae5b915917f4dea820a92a
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
wind_up
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p0
|
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
wind_up (0.0.2)
|
5
|
+
celluloid (~> 0.14.1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
celluloid (0.14.1)
|
11
|
+
timers (>= 1.0.0)
|
12
|
+
diff-lcs (1.2.4)
|
13
|
+
rake (10.1.0)
|
14
|
+
rspec (2.13.0)
|
15
|
+
rspec-core (~> 2.13.0)
|
16
|
+
rspec-expectations (~> 2.13.0)
|
17
|
+
rspec-mocks (~> 2.13.0)
|
18
|
+
rspec-core (2.13.1)
|
19
|
+
rspec-expectations (2.13.0)
|
20
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
21
|
+
rspec-mocks (2.13.1)
|
22
|
+
timers (1.1.0)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
rake
|
29
|
+
rspec
|
30
|
+
wind_up!
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ryan Chan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
Wind Up
|
2
|
+
=======
|
3
|
+
WindUp is a drop-in replacement for Celluloid's `PoolManager` class. So why
|
4
|
+
would you use WindUp?
|
5
|
+
|
6
|
+
* Asynchronous message passing - get all the nice worker-level concurrency
|
7
|
+
Celluloid give you (#sleep, #Celluloid::IO, #future, etc)
|
8
|
+
* Separate proxies for QueueManager and queues - no more unexpected behavior
|
9
|
+
between #is_a? and #class
|
10
|
+
* Single queue handles multiple workers - Extend WindUp with
|
11
|
+
[MultiWindUp](https://www.github.com/ryanlchan/multi_wind_up) and you can
|
12
|
+
have one pool execute multiple types of workers simultaneously
|
13
|
+
|
14
|
+
Usage
|
15
|
+
-----
|
16
|
+
|
17
|
+
WindUp `Queues` are almost drop-in replacements for Celluloid pools.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
q = AnyCelluloidClass.queue size: 3 # size defaults to number of cores
|
21
|
+
q.any_method # perform synchronously
|
22
|
+
q.async.long_running_method # perform asynchronously
|
23
|
+
q.future.i_want_this_back # perform as a future
|
24
|
+
```
|
25
|
+
|
26
|
+
`Queues` use two separate proxies to control `Queue` commands vs
|
27
|
+
`QueueManager` commands.
|
28
|
+
```ruby
|
29
|
+
# .queue returns the proxy for the queue (i.e. workers)
|
30
|
+
q = AnyCelluloidClass.queue # => WindUp::QueueProxy(AnyCelluloidClass)
|
31
|
+
|
32
|
+
# Get the proxy for the manager from the QueueProxy
|
33
|
+
q.__manager__ # => Celluloid::ActorProxy(WindUp::QueueManager)
|
34
|
+
|
35
|
+
# Return to the queue from the manager
|
36
|
+
q.__manager__.queue # WindUp::QueueProxy(AnyCelluloidClass)
|
37
|
+
```
|
38
|
+
|
39
|
+
You may store these `Queue` object in the registry as any actor
|
40
|
+
```ruby
|
41
|
+
Celluloid::Actor[:queue] = q
|
42
|
+
```
|
43
|
+
|
44
|
+
Changing Routing Behavior (Advanced)
|
45
|
+
------------------------------------
|
46
|
+
|
47
|
+
WindUp accepts multiple types of routing behavior. Supported behaviors include:
|
48
|
+
|
49
|
+
* :random - Route messages to workers randomly
|
50
|
+
* :round_robin - Route messages to each worker sequentially
|
51
|
+
* :smallest_mailbox - Route messages to the worker with the smallest mailbox
|
52
|
+
* :first_available - Route messages to the first available worker (Default)
|
53
|
+
|
54
|
+
To configure your queue to use a specific routing style, specify the routing
|
55
|
+
behavior when calling .queue:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# Use a random router
|
59
|
+
Klass.queue router: :random
|
60
|
+
```
|
61
|
+
|
62
|
+
You can specify your own custom routing behavior as well:
|
63
|
+
```ruby
|
64
|
+
class FirstRouter < WindUp::Router::Base
|
65
|
+
# Returns the next subscriber to route a message to
|
66
|
+
def next_subscriber
|
67
|
+
subscribers.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Register this router
|
72
|
+
WindUp::Routers.register :first, FirstRouter
|
73
|
+
|
74
|
+
# Use this router
|
75
|
+
q = Klass.queue router: :first
|
76
|
+
|
77
|
+
```
|
78
|
+
|
79
|
+
Multiple worker types per queue
|
80
|
+
-------------------------------
|
81
|
+
|
82
|
+
This part of WindUp has been extracted to its own gem,
|
83
|
+
[MultiWindUp](https://www.github.com/ryanlchan/multi_wind_up).
|
84
|
+
WindUp now only contains the pooling implementation.
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
1. Fork it
|
89
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
90
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
91
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
92
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new('spec')
|
5
|
+
|
6
|
+
# If you want to make this the default task
|
7
|
+
task :default => :spec
|
8
|
+
task :test => :spec
|
9
|
+
|
10
|
+
task :console do
|
11
|
+
exec "irb -r wind_up -I ./lib"
|
12
|
+
end
|
13
|
+
|
14
|
+
task :rspec do
|
15
|
+
exec "rspec -f doc --color"
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module WindUp
|
2
|
+
# WindUp's ForwardedCall tells an actor to pull a message from a source
|
3
|
+
# mailbox when processed
|
4
|
+
class ForwardedCall < Celluloid::Call
|
5
|
+
|
6
|
+
# Do not block if no work found
|
7
|
+
TIMEOUT = 0
|
8
|
+
|
9
|
+
def initialize(source)
|
10
|
+
@source = source
|
11
|
+
end
|
12
|
+
|
13
|
+
# Pull the next message from the source, if available
|
14
|
+
def dispatch(obj)
|
15
|
+
msg = @source.receive(TIMEOUT)
|
16
|
+
::Celluloid.mailbox << msg if msg
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Wraps a TerminationRequest for standard ordering within a mailbox
|
21
|
+
class DelayedTerminationRequest < Celluloid::Call
|
22
|
+
def initialize
|
23
|
+
@method = :terminate
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# Manages a queue of workers
|
2
|
+
# Accumulates/stores messages and supervises a group of workers
|
3
|
+
# WindUp `Queues` are almost drop-in replacements for Celluloid pools.
|
4
|
+
#
|
5
|
+
# ```ruby
|
6
|
+
# q = AnyCelluloidClass.queue size: 3 # size defaults to number of cores
|
7
|
+
# q.any_method # perform synchronously
|
8
|
+
# q.async.long_running_method # perform asynchronously
|
9
|
+
# q.future.i_want_this_back # perform as a future
|
10
|
+
# ```
|
11
|
+
#
|
12
|
+
# `Queues` use two separate proxies to control `Queue` commands vs
|
13
|
+
# `QueueManager` commands.
|
14
|
+
# ```ruby
|
15
|
+
# # .queue returns the proxy for the queue (i.e. workers)
|
16
|
+
# q = AnyCelluloidClass.queue # => WindUp::QueueProxy(AnyCelluloidClass)
|
17
|
+
#
|
18
|
+
# # Get the proxy for the manager from the QueueProxy
|
19
|
+
# q.__manager__ # => Celluloid::ActorProxy(WindUp::QueueManager)
|
20
|
+
#
|
21
|
+
# # Return to the queue from the manager
|
22
|
+
# q.__manager__.queue # WindUp::QueueProxy(AnyCelluloidClass)
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# You may store these `Queue` object in the registry as any actor
|
26
|
+
# ```ruby
|
27
|
+
# Celluloid::Actor[:queue] = q
|
28
|
+
# ```
|
29
|
+
module WindUp
|
30
|
+
class QueueManager
|
31
|
+
include Celluloid
|
32
|
+
attr_reader :size, :router, :worker_class
|
33
|
+
|
34
|
+
trap_exit :restart_actor
|
35
|
+
|
36
|
+
# Don't use QueueManager.new, use Klass.queue instead
|
37
|
+
def initialize(worker_class, options = {})
|
38
|
+
defaults = { :size => [Celluloid.cores, 2].max,
|
39
|
+
:router => :first_available }
|
40
|
+
options = defaults.merge options
|
41
|
+
|
42
|
+
@worker_class = worker_class
|
43
|
+
@args = options[:args] ? Array(options[:args]) : []
|
44
|
+
@size = options[:size]
|
45
|
+
|
46
|
+
router_class = Routers[options[:router]]
|
47
|
+
raise ArgumentError, "Router class not recognized" unless router_class
|
48
|
+
@router = router_class.new
|
49
|
+
|
50
|
+
@registry = Celluloid::Registry.root
|
51
|
+
@group = []
|
52
|
+
resize_group
|
53
|
+
end
|
54
|
+
|
55
|
+
# Terminate our supervised group on finalization
|
56
|
+
finalizer :__shutdown__
|
57
|
+
def __shutdown__
|
58
|
+
@router.shutdown
|
59
|
+
group.reverse_each(&:terminate)
|
60
|
+
end
|
61
|
+
|
62
|
+
###########
|
63
|
+
# Helpers #
|
64
|
+
###########
|
65
|
+
|
66
|
+
# Access the Queue's proxy
|
67
|
+
def queue
|
68
|
+
WindUp::QueueProxy.new Actor.current
|
69
|
+
end
|
70
|
+
|
71
|
+
# Resize this queue's worker group
|
72
|
+
# NOTE: Using this to down-size your queue CAN truncate ongoing work!
|
73
|
+
# Workers which are waiting on blocks/sleeping will receive a termination
|
74
|
+
# request prematurely!
|
75
|
+
# @param num [Integer] Number of workers to use
|
76
|
+
def size=(num)
|
77
|
+
@size = num
|
78
|
+
resize_group
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return the size of the queue backlog
|
82
|
+
# @return [Integer] the number of messages queueing
|
83
|
+
def backlog
|
84
|
+
@router.size
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
"<Celluloid::ActorProxy(#{self.class}) @size=#{@size} @worker_class=#{@worker_class} @backlog=#{backlog}>"
|
89
|
+
end
|
90
|
+
|
91
|
+
####################
|
92
|
+
# Group Management #
|
93
|
+
####################
|
94
|
+
|
95
|
+
# Restart a crashed actor
|
96
|
+
def restart_actor(actor, reason)
|
97
|
+
member = group.find do |_member|
|
98
|
+
_member.actor == actor
|
99
|
+
end
|
100
|
+
raise "A group member went missing. This shouldn't be!" unless member
|
101
|
+
|
102
|
+
if reason
|
103
|
+
member.restart(reason)
|
104
|
+
else
|
105
|
+
# Remove from group on clean shutdown
|
106
|
+
group.delete_if do |_member|
|
107
|
+
_member.actor == actor
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def group
|
114
|
+
@group ||= []
|
115
|
+
end
|
116
|
+
|
117
|
+
# Resize the worker group in this queue
|
118
|
+
# You should probably be using #size=
|
119
|
+
# @param target [Integer] the targeted number of workers to grow to
|
120
|
+
def resize_group(target = size)
|
121
|
+
delta = target - group.size
|
122
|
+
if delta == 0
|
123
|
+
# *Twiddle thumbs*
|
124
|
+
return
|
125
|
+
elsif delta > 0
|
126
|
+
# Increase pool size
|
127
|
+
delta.times do
|
128
|
+
worker = Celluloid::SupervisionGroup::Member.new @registry, @worker_class, :args => @args
|
129
|
+
group << worker
|
130
|
+
@router.add_subscriber(worker.actor.mailbox)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
# Truncate pool
|
134
|
+
delta.abs.times { @router << DelayedTerminationRequest.new }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# A proxy object which sends calls to a Queue mailbox
|
2
|
+
module WindUp
|
3
|
+
class QueueProxy < Celluloid::ActorProxy
|
4
|
+
def initialize(manager)
|
5
|
+
@mailbox = manager.router
|
6
|
+
@klass = manager.worker_class.to_s
|
7
|
+
@sync_proxy = ::Celluloid::SyncProxy.new(@mailbox, @klass)
|
8
|
+
@async_proxy = ::Celluloid::AsyncProxy.new(@mailbox, @klass)
|
9
|
+
@future_proxy = ::Celluloid::FutureProxy.new(@mailbox, @klass)
|
10
|
+
|
11
|
+
@manager_proxy = manager
|
12
|
+
end
|
13
|
+
|
14
|
+
# Escape route to access the QueueManager actor from the QueueProxy
|
15
|
+
def __manager__
|
16
|
+
@manager_proxy
|
17
|
+
end
|
18
|
+
|
19
|
+
# Reroute termination/alive? to the queue manager
|
20
|
+
def terminate
|
21
|
+
__manager__.terminate
|
22
|
+
end
|
23
|
+
|
24
|
+
def terminate!
|
25
|
+
__manager__.terminate!
|
26
|
+
end
|
27
|
+
|
28
|
+
def alive?
|
29
|
+
__manager__.alive?
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
orig = super
|
34
|
+
orig.sub("Celluloid::ActorProxy", "WindUp::QueueProxy")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# A duck-type of Celluloid::Mailbox which forwards messages to subscribers
|
2
|
+
#
|
3
|
+
# Router is not a mailbox in the strict sense. It acts as a forwarder for
|
4
|
+
# calls, accepting and routing them to the next available subscriber. As with
|
5
|
+
# Akka routers, pushing a call onto a Router is invoked concurrently from the
|
6
|
+
# sender and must be threadsafe.
|
7
|
+
module WindUp
|
8
|
+
module Routers
|
9
|
+
def self.register(name, klass)
|
10
|
+
registry[name] = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.registry
|
14
|
+
@registry ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.[](name)
|
18
|
+
registry[name] || registry.values.first
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Router
|
23
|
+
class Base
|
24
|
+
def initialize
|
25
|
+
@mutex = Mutex.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# List all subscribers
|
29
|
+
def subscribers
|
30
|
+
@subscribers ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Subscribe to this mailbox for updates of new messages
|
34
|
+
# @param subscriber [Object] the subscriber to send messages to
|
35
|
+
def add_subscriber(subscriber)
|
36
|
+
@mutex.synchronize do
|
37
|
+
subscribers << subscriber unless subscribers.include?(subscriber)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Remove a subscriber from thie mailbox
|
42
|
+
# @param subscriber [Object] the subscribed object
|
43
|
+
def remove_subscriber(subscriber)
|
44
|
+
@mutex.synchronize do
|
45
|
+
subscribers.delete subscriber
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Send the call to all subscribers
|
50
|
+
def <<(message)
|
51
|
+
@mutex.lock
|
52
|
+
begin
|
53
|
+
target = next_subscriber
|
54
|
+
send_message(target, message) if target
|
55
|
+
ensure
|
56
|
+
@mutex.unlock rescue nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def broadcast(message)
|
61
|
+
send_message(subscribers, message)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Send a message to the specified target
|
65
|
+
def send_message(target, message)
|
66
|
+
# Array-ize unless we're an Enumerable already that isn't a Mailbox
|
67
|
+
target = [target] unless target.is_a?(Enumerable) && !target.respond_to?(:receive)
|
68
|
+
|
69
|
+
target.each do |targ|
|
70
|
+
begin
|
71
|
+
targ << message
|
72
|
+
rescue Celluloid::MailboxError
|
73
|
+
# Mailbox died, remove subscriber
|
74
|
+
remove_subscriber targ
|
75
|
+
end
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Randomly route messages to workers
|
82
|
+
class Random < Base
|
83
|
+
def next_subscriber
|
84
|
+
subscribers.sample
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Basic router using a RoundRobin strategy
|
89
|
+
class RoundRobin < Base
|
90
|
+
# Signal new work to all subscribers/waiters
|
91
|
+
def next_subscriber
|
92
|
+
subscribers.rotate!
|
93
|
+
subscribers.last
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Send message to the worker with the smallest mailbox
|
98
|
+
class SmallestMailbox < Base
|
99
|
+
def next_subscriber
|
100
|
+
subscribers.sort { |a,b| a.size <=> b.size }.first
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# The strategy employed is similar to a ScatterGatherFirstCompleted router in
|
105
|
+
# Akka, but wrapping messages in the ForwardedCall structure so computation is
|
106
|
+
# only completed once.
|
107
|
+
class FirstAvailable < Base
|
108
|
+
def mailbox
|
109
|
+
@mailbox ||= Celluloid::Mailbox.new
|
110
|
+
end
|
111
|
+
|
112
|
+
def <<(msg)
|
113
|
+
@mutex.lock
|
114
|
+
begin
|
115
|
+
mailbox << msg
|
116
|
+
send_message(subscribers, WindUp::ForwardedCall.new(mailbox))
|
117
|
+
ensure
|
118
|
+
@mutex.unlock rescue nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def shutdown
|
123
|
+
mailbox.shutdown
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
Routers.register :first_avaialble, Router::FirstAvailable
|
129
|
+
Routers.register :round_robin, Router::RoundRobin
|
130
|
+
Routers.register :random, Router::Random
|
131
|
+
Routers.register :smallest_mailbox, Router::SmallestMailbox
|
132
|
+
end
|
data/lib/wind_up.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'wind_up/exceptions'
|
3
|
+
require 'wind_up/calls'
|
4
|
+
require 'wind_up/routers'
|
5
|
+
require 'wind_up/queue_proxy'
|
6
|
+
require 'wind_up/queue_manager'
|
7
|
+
require 'wind_up/version'
|
8
|
+
require 'wind_up/celluloid_ext'
|
9
|
+
|
10
|
+
module WindUp
|
11
|
+
def self.logger
|
12
|
+
Celluloid.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.logger=(logger)
|
16
|
+
Celluloid.logger = logger
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'wind_up/railtie' if defined?(::Rails)
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'pry'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'celluloid'
|
7
|
+
require 'wind_up'
|
8
|
+
|
9
|
+
Celluloid.shutdown; Celluloid.boot
|
10
|
+
|
11
|
+
def mute_celluloid_logging
|
12
|
+
# Temporarily mute celluloid logger
|
13
|
+
Celluloid.logger.level = Logger::FATAL
|
14
|
+
yield if block_given?
|
15
|
+
# Restore celluloid logger
|
16
|
+
Celluloid.logger.level = Logger::DEBUG
|
17
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class FakeWorker
|
4
|
+
include Celluloid
|
5
|
+
def process(queue = nil)
|
6
|
+
if queue
|
7
|
+
queue << :done
|
8
|
+
else
|
9
|
+
:done
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def crash
|
14
|
+
raise StandardError, "zomgcrash"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class SleepyWorker < FakeWorker
|
19
|
+
def sleepy
|
20
|
+
sleep 0.2
|
21
|
+
:done
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
describe WindUp::QueueManager do
|
27
|
+
let(:queue) { FakeWorker.queue size: 2 }
|
28
|
+
describe '#initialize' do
|
29
|
+
it 'creates a supervision group of workers' do
|
30
|
+
expect { FakeWorker.queue size: 1 }.to change { Celluloid::Actor.all.size }.by(2)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'creates as many workers as number of cores on the system' do
|
34
|
+
cores = FakeWorker.queue
|
35
|
+
cores.__manager__.size.should eq Celluloid.cores
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'requires a worker class' do
|
39
|
+
mute_celluloid_logging do
|
40
|
+
expect { WindUp::QueueManager.new }.to raise_exception
|
41
|
+
sleep 0.1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#terminate' do
|
47
|
+
it 'terminates the manager' do
|
48
|
+
queue.terminate
|
49
|
+
queue.should_not be_alive
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'terminates the pool' do
|
53
|
+
expect{ queue.terminate }.to change { Celluloid::Actor.all.size }.by(0)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#sync' do
|
58
|
+
it 'processs calls synchronously' do
|
59
|
+
queue.process.should be :done
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#async' do
|
64
|
+
it 'processs calls asynchronously' do
|
65
|
+
q = Queue.new
|
66
|
+
queue.async.process(q)
|
67
|
+
q.pop.should be :done
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'processes additional work when workers as sleeping' do
|
71
|
+
sleepy_queue = SleepyWorker.queue size: 1
|
72
|
+
start_time = Time.now
|
73
|
+
vals = 2.times.map { sleepy_queue.future.sleepy }
|
74
|
+
vals.each { |v| v.value }
|
75
|
+
(start_time - Time.now).should be < 0.3
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'handles crashed calls gracefully' do
|
79
|
+
mute_celluloid_logging do
|
80
|
+
queue.async.crash
|
81
|
+
queue.should be_alive
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#future' do
|
87
|
+
it 'processes calls as futures' do
|
88
|
+
f = queue.future.process
|
89
|
+
f.value.should be :done
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#size=' do
|
94
|
+
let(:manager) { queue.__manager__ }
|
95
|
+
it 'increases the size of the pool' do
|
96
|
+
manager.size.should eq 2
|
97
|
+
expect { manager.size = 3 }.to change{ Celluloid::Actor.all.size }.by(1)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'reduces the size of the pool' do
|
101
|
+
manager.size.should eq 2
|
102
|
+
expect { manager.size = 1 }.to change{ Celluloid::Actor.all.size }.by(-1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,372 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class FakeWorker
|
4
|
+
include Celluloid
|
5
|
+
def perform(*args); end;
|
6
|
+
end
|
7
|
+
|
8
|
+
# Because we use a Singleton model, it's tough to change configurations
|
9
|
+
# without redefining a ton of classes. This class just helps us dynamically
|
10
|
+
# create Queue classes using a block.
|
11
|
+
class QueueFactory
|
12
|
+
ALPHABET = ('a'..'z').to_a
|
13
|
+
def self.bake(&block)
|
14
|
+
name = 10.times.map{ ALPHABET.sample }.join.capitalize
|
15
|
+
c = WindUp::Queue.new name
|
16
|
+
|
17
|
+
if block_given?
|
18
|
+
c.instance_eval &block
|
19
|
+
else
|
20
|
+
c.instance_eval {
|
21
|
+
worker_class FakeWorker
|
22
|
+
}
|
23
|
+
end
|
24
|
+
return c
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# describe WindUp::Queue, pending: "rewrite" do
|
29
|
+
# let(:queue) { QueueFactory.bake }
|
30
|
+
# describe "#store" do
|
31
|
+
# context "by default" do
|
32
|
+
# it "sets the InMemory store" do
|
33
|
+
# queue.store.should be_a(WindUp::Store::InMemory)
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# context "when passed :memory or 'memory'" do
|
37
|
+
# it "sets the InMemory store" do
|
38
|
+
# queue.store(:memory).should be_a(WindUp::Store::InMemory)
|
39
|
+
# queue.store("memory").should be_a(WindUp::Store::InMemory)
|
40
|
+
# queue.store.should be_a(WindUp::Store::InMemory)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# context "when passed :redis or 'redis'" do
|
44
|
+
# it "sets the Redis store" do
|
45
|
+
# queue.store(:redis).should be_a(WindUp::Store::Redis)
|
46
|
+
# queue.store("redis").should be_a(WindUp::Store::Redis)
|
47
|
+
# queue.store.should be_a(WindUp::Store::Redis)
|
48
|
+
# end
|
49
|
+
|
50
|
+
# context "with a :url provided" do
|
51
|
+
# it "uses the url to connect to Redis" do
|
52
|
+
# queue.store(:redis, url: "redis://127.0.0.1:6379")
|
53
|
+
# queue.store.should be
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
|
57
|
+
# context 'with :connection set to a Redis client instance' do
|
58
|
+
# it 'works' do
|
59
|
+
# redis = Redis.new
|
60
|
+
# queue.store(:redis, connection: redis)
|
61
|
+
# queue.store.should be
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
|
65
|
+
# context 'with :connection set to a Redis connection_pool' do
|
66
|
+
# it 'works' do
|
67
|
+
# redis = ConnectionPool.new(size: 2, timeout: 5) { Redis.new }
|
68
|
+
# queue.store(:redis, connection: redis)
|
69
|
+
# queue.store.should be
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
|
75
|
+
# context '#pool' do
|
76
|
+
# context 'configured using :workers/:worker' do
|
77
|
+
# let(:queue) do
|
78
|
+
# QueueFactory.bake do
|
79
|
+
# worker_class FakeWorker
|
80
|
+
# workers 3
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
|
84
|
+
# it "sets up the pool" do
|
85
|
+
# queue.pool.should be
|
86
|
+
# queue.pool.size.should eq 3
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
|
90
|
+
# context ':pool_name' do
|
91
|
+
# context 'when given an existing pool' do
|
92
|
+
# it 'uses the existing pool' do
|
93
|
+
# Celluloid::Actor[:pool_name_spec] = FakeWorker.pool size: 2
|
94
|
+
# queue = QueueFactory.bake do
|
95
|
+
# pool_name :pool_name_spec
|
96
|
+
# end
|
97
|
+
# queue.pool.should be Celluloid::Actor[:pool_name_spec]
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
|
103
|
+
# context '#priority_level' do
|
104
|
+
# context 'that are equal' do
|
105
|
+
# it 'creates a queue with priority levels' do
|
106
|
+
# queue = QueueFactory.bake do
|
107
|
+
# worker_class FakeWorker
|
108
|
+
# priority_level :clone1
|
109
|
+
# priority_level :clone2
|
110
|
+
# end
|
111
|
+
|
112
|
+
# queue.should be
|
113
|
+
# queue.priority_levels.should_not be_empty
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
|
117
|
+
# context 'that are weighted' do
|
118
|
+
# it 'creates a queue with priority levels' do
|
119
|
+
# queue = QueueFactory.bake do
|
120
|
+
# worker_class FakeWorker
|
121
|
+
# priority_level :high, weight: 10
|
122
|
+
# priority_level :low, weight: 1
|
123
|
+
# end
|
124
|
+
|
125
|
+
# queue.should be
|
126
|
+
# queue.priority_levels.should_not be_empty
|
127
|
+
# queue.priority_level_weights.should eq({high: 10, low: 1})
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
|
131
|
+
# context 'that are strictly ordered' do
|
132
|
+
# it 'creates a queue with priority levels' do
|
133
|
+
# queue = QueueFactory.bake do
|
134
|
+
# worker_class FakeWorker
|
135
|
+
# strict true
|
136
|
+
# priority_level :clone1
|
137
|
+
# priority_level :clone2
|
138
|
+
# end
|
139
|
+
|
140
|
+
# queue.should be
|
141
|
+
# queue.priority_levels.should_not be_empty
|
142
|
+
# queue.should be_strict
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
|
146
|
+
# it 'does not create priority levels with the same name' do
|
147
|
+
# queue = QueueFactory.bake do
|
148
|
+
# worker_class FakeWorker
|
149
|
+
# strict true
|
150
|
+
# priority_level :clone1
|
151
|
+
# priority_level :clone1
|
152
|
+
# end
|
153
|
+
|
154
|
+
# queue.priority_levels.should eq Set[:clone1]
|
155
|
+
# end
|
156
|
+
# end # with priority leels
|
157
|
+
|
158
|
+
# describe '#push' do
|
159
|
+
# context 'when not given a priority level' do
|
160
|
+
# it 'pushes the argument to the store with default priority' do
|
161
|
+
# queue.store.should_receive(:push).with("test", priority_level: nil)
|
162
|
+
# queue.push "test"
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
# context 'when given a priority level' do
|
166
|
+
# it 'pushes the argument to the store with specified priority' do
|
167
|
+
# queue.store.should_receive(:push).with("test", priority_level: "high")
|
168
|
+
# queue.push "test", priority_level: "high"
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
# end
|
172
|
+
|
173
|
+
# describe '#pop' do
|
174
|
+
# context 'without a priority level argument' do
|
175
|
+
# context 'with a strictly ordered queue' do
|
176
|
+
# it 'pops priority levels in order' do
|
177
|
+
# queue = QueueFactory.bake do
|
178
|
+
# worker_class FakeWorker
|
179
|
+
# strict true
|
180
|
+
# priority_level :high
|
181
|
+
# priority_level :low
|
182
|
+
# end
|
183
|
+
# queue.store.should_receive(:pop).with([:high, :low]).at_least(1).times
|
184
|
+
# queue.pop
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
# context 'with a loosely ordered queue' do
|
188
|
+
# it 'pops priority levels in proportion' do
|
189
|
+
# queue = QueueFactory.bake do
|
190
|
+
# worker_class FakeWorker
|
191
|
+
# priority_level :high
|
192
|
+
# priority_level :low
|
193
|
+
# end
|
194
|
+
# queue.store.stub(:pop) { nil }
|
195
|
+
# queue.store.stub(:pop).with { [:high, :low] }.and_return { "success" }
|
196
|
+
# queue.store.stub(:pop).with { [:low, :high] }.and_return { "success" }
|
197
|
+
|
198
|
+
# queue.pop.should be
|
199
|
+
# end
|
200
|
+
# end
|
201
|
+
# end
|
202
|
+
# context 'with a priority_level argument' do
|
203
|
+
# it 'pops the specified priority' do
|
204
|
+
# queue.store.stub(:pop) { nil }
|
205
|
+
# queue.store.stub(:pop).with(["queue"]) { "work" }
|
206
|
+
# queue.pop(["queue"]).should be
|
207
|
+
# end
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
|
211
|
+
# describe '#workers' do
|
212
|
+
# it 'returns the number of workers in the pool' do
|
213
|
+
# queue = QueueFactory.bake do
|
214
|
+
# worker_class FakeWorker
|
215
|
+
# workers 2
|
216
|
+
# end
|
217
|
+
# queue.workers.should eq 2
|
218
|
+
# end
|
219
|
+
# end
|
220
|
+
|
221
|
+
# describe '#busy_workers' do
|
222
|
+
# it 'returns the number of busy_workers in the pool' do
|
223
|
+
# queue = QueueFactory.bake do
|
224
|
+
# worker_class FakeWorker
|
225
|
+
# workers 2
|
226
|
+
# end
|
227
|
+
# queue.busy_workers.should eq 0
|
228
|
+
# end
|
229
|
+
# end
|
230
|
+
|
231
|
+
# describe '#idle_workers' do
|
232
|
+
# it 'returns the number of idle_workers in the pool' do
|
233
|
+
# queue = QueueFactory.bake do
|
234
|
+
# worker_class FakeWorker
|
235
|
+
# workers 2
|
236
|
+
# end
|
237
|
+
# queue.idle_workers.should eq 2
|
238
|
+
# end
|
239
|
+
# end
|
240
|
+
|
241
|
+
# describe '#backlog?' do
|
242
|
+
# it 'returns true if the pool is fully utilized' do
|
243
|
+
# queue = QueueFactory.bake do
|
244
|
+
# worker_class FakeWorker
|
245
|
+
# end
|
246
|
+
# queue.should_not be_backlog
|
247
|
+
# end
|
248
|
+
# end
|
249
|
+
|
250
|
+
# describe '#size' do
|
251
|
+
# it 'returns the number of jobs in the queue store' do
|
252
|
+
# queue.store.should_receive(:size).and_return(4)
|
253
|
+
# queue.size.should eq 4
|
254
|
+
# end
|
255
|
+
# end
|
256
|
+
|
257
|
+
# describe '#strict?' do
|
258
|
+
# context 'when the queue is strictly ordered' do
|
259
|
+
# it 'returns true' do
|
260
|
+
# queue = QueueFactory.bake do
|
261
|
+
# worker_class FakeWorker
|
262
|
+
# strict true
|
263
|
+
# end
|
264
|
+
# queue.should be_strict
|
265
|
+
# end
|
266
|
+
# end
|
267
|
+
# context 'when the queue is not strictly ordered' do
|
268
|
+
# it 'returns false' do
|
269
|
+
# queue = QueueFactory.bake do
|
270
|
+
# worker_class FakeWorker
|
271
|
+
# end
|
272
|
+
# queue.should_not be_strict
|
273
|
+
# end
|
274
|
+
# end
|
275
|
+
# end
|
276
|
+
|
277
|
+
# describe '#priority_levels' do
|
278
|
+
# context 'with a queue with priority levels' do
|
279
|
+
# it 'returns the priority levels for this queue' do
|
280
|
+
# queue = QueueFactory.bake do
|
281
|
+
# worker_class FakeWorker
|
282
|
+
# priority_level :high
|
283
|
+
# priority_level :low
|
284
|
+
# end
|
285
|
+
# queue.priority_levels.should eq Set[:high, :low]
|
286
|
+
# end
|
287
|
+
|
288
|
+
# it 'does not return duplicates' do
|
289
|
+
# queue = QueueFactory.bake do
|
290
|
+
# worker_class FakeWorker
|
291
|
+
# priority_level :high, weight: 10
|
292
|
+
# priority_level :low, weight: 1
|
293
|
+
# end
|
294
|
+
# queue.priority_levels.should eq Set[:high, :low]
|
295
|
+
# end
|
296
|
+
# end
|
297
|
+
# context 'with a queue without priority levels' do
|
298
|
+
# it 'returns an empty array' do
|
299
|
+
# queue = QueueFactory.bake do
|
300
|
+
# worker_class FakeWorker
|
301
|
+
# end
|
302
|
+
# queue.priority_levels.should eq Set[]
|
303
|
+
# end
|
304
|
+
# end
|
305
|
+
# end
|
306
|
+
|
307
|
+
# describe '#priority_level_weights' do
|
308
|
+
# context 'when run on a queue without priority levels' do
|
309
|
+
# it 'returns an empty hash' do
|
310
|
+
# queue = QueueFactory.bake do
|
311
|
+
# worker_class FakeWorker
|
312
|
+
# end
|
313
|
+
# queue.priority_level_weights.should eq({})
|
314
|
+
# end
|
315
|
+
# end
|
316
|
+
|
317
|
+
# context 'when run on a queue with unweighted priority levels' do
|
318
|
+
# it "returns the priority levels with their weightings" do
|
319
|
+
# queue = QueueFactory.bake do
|
320
|
+
# worker_class FakeWorker
|
321
|
+
# priority_level :high
|
322
|
+
# priority_level :low
|
323
|
+
# end
|
324
|
+
# queue.priority_level_weights.should eq({high: 1, low: 1})
|
325
|
+
# end
|
326
|
+
# end
|
327
|
+
|
328
|
+
# context 'when run on a queue with unweighted priority levels' do
|
329
|
+
# it "returns the priority levels with their weightings" do
|
330
|
+
# queue = QueueFactory.bake do
|
331
|
+
# worker_class FakeWorker
|
332
|
+
# priority_level :high, weight: 10
|
333
|
+
# priority_level :low, weight: 1
|
334
|
+
# end
|
335
|
+
# queue.priority_level_weights.should eq({high: 10, low: 1})
|
336
|
+
# end
|
337
|
+
# end
|
338
|
+
# end
|
339
|
+
|
340
|
+
# describe '#default_priority_level' do
|
341
|
+
# context 'when run on a queue without priority levels' do
|
342
|
+
# it 'returns nil' do
|
343
|
+
# queue = QueueFactory.bake do
|
344
|
+
# worker_class FakeWorker
|
345
|
+
# end
|
346
|
+
# queue.default_priority_level.should_not be
|
347
|
+
# end
|
348
|
+
# end
|
349
|
+
|
350
|
+
# context 'when run on a queue with priority levels' do
|
351
|
+
# it "returns the first priority level" do
|
352
|
+
# queue = QueueFactory.bake do
|
353
|
+
# worker_class FakeWorker
|
354
|
+
# priority_level :high
|
355
|
+
# priority_level :low
|
356
|
+
# end
|
357
|
+
# queue.default_priority_level.should eq(:high)
|
358
|
+
# end
|
359
|
+
# end
|
360
|
+
|
361
|
+
# context 'when run on a queue with a default priority level explicitly set' do
|
362
|
+
# it "returns the set priority level" do
|
363
|
+
# queue = QueueFactory.bake do
|
364
|
+
# worker_class FakeWorker
|
365
|
+
# priority_level :high, weight: 10
|
366
|
+
# priority_level :low, weight: 1, default: true
|
367
|
+
# end
|
368
|
+
# queue.default_priority_level.should eq(:low)
|
369
|
+
# end
|
370
|
+
# end
|
371
|
+
# end
|
372
|
+
# end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'a router' do
|
4
|
+
describe '#add_subscriber' do
|
5
|
+
it 'adds a subscriber to the list of subscribers' do
|
6
|
+
subject.add_subscriber(subscriber)
|
7
|
+
subject.subscribers.should include(subscriber)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#<<' do
|
12
|
+
it 'forwards messages to a subscriber' do
|
13
|
+
subject.add_subscriber subscriber
|
14
|
+
subject << :ok
|
15
|
+
subscriber.receive(0).should be :ok
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe WindUp::Routers do
|
21
|
+
let(:subscriber) { Celluloid::Mailbox.new }
|
22
|
+
let(:origin) { WindUp::Routers[router_class].new }
|
23
|
+
subject { origin }
|
24
|
+
|
25
|
+
describe WindUp::Router::RoundRobin do
|
26
|
+
let(:router_class) { :round_robin }
|
27
|
+
it_behaves_like 'a router'
|
28
|
+
end
|
29
|
+
|
30
|
+
describe WindUp::Router::Random do
|
31
|
+
let(:router_class) { :random }
|
32
|
+
it_behaves_like 'a router'
|
33
|
+
end
|
34
|
+
|
35
|
+
describe WindUp::Router::SmallestMailbox do
|
36
|
+
let(:router_class) { :smallest_mailbox }
|
37
|
+
it_behaves_like 'a router'
|
38
|
+
end
|
39
|
+
|
40
|
+
describe WindUp::Router::FirstAvailable do
|
41
|
+
let(:router_class) { :first_avaialble }
|
42
|
+
|
43
|
+
describe '#add_subscriber' do
|
44
|
+
it 'adds a subscriber to the list of subscribers' do
|
45
|
+
subject.add_subscriber(subscriber)
|
46
|
+
subject.subscribers.should include(subscriber)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#<<' do
|
51
|
+
it 'publishes the Forwarder event to all subscribers' do
|
52
|
+
origin.add_subscriber(subscriber)
|
53
|
+
origin << "New work"
|
54
|
+
subscriber.receive(1).should be_a(WindUp::ForwardedCall)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WindUp do
|
4
|
+
describe '.logger' do
|
5
|
+
it "delegates get to Celluloid's logger" do
|
6
|
+
WindUp.logger.should == Celluloid.logger
|
7
|
+
end
|
8
|
+
|
9
|
+
it "delegates set to Celluloid's logger" do
|
10
|
+
Celluloid.should_receive(:logger=)
|
11
|
+
WindUp.logger = nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/wind_up.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wind_up/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "wind_up"
|
8
|
+
gem.version = WindUp::VERSION
|
9
|
+
gem.authors = ["Ryan Chan"]
|
10
|
+
gem.email = ["ryan@ryanlchan.com"]
|
11
|
+
gem.summary = %q{A drop-in replacement for Celluloid pools using the message API}
|
12
|
+
gem.description = %q{WindUp is a simple background processing library meant to improve on Celluloid's pools}
|
13
|
+
gem.homepage = "https://github.com/ryanlchan/wind_up"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
gem.add_development_dependency "rake"
|
22
|
+
|
23
|
+
gem.add_dependency "celluloid", "~> 0.14.1"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wind_up
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Chan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: celluloid
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.14.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.14.1
|
55
|
+
description: WindUp is a simple background processing library meant to improve on
|
56
|
+
Celluloid's pools
|
57
|
+
email:
|
58
|
+
- ryan@ryanlchan.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- .ruby-gemset
|
64
|
+
- .ruby-version
|
65
|
+
- CHANGES.md
|
66
|
+
- Gemfile
|
67
|
+
- Gemfile.lock
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- lib/wind_up.rb
|
72
|
+
- lib/wind_up/calls.rb
|
73
|
+
- lib/wind_up/celluloid_ext.rb
|
74
|
+
- lib/wind_up/exceptions.rb
|
75
|
+
- lib/wind_up/queue_manager.rb
|
76
|
+
- lib/wind_up/queue_proxy.rb
|
77
|
+
- lib/wind_up/railtie.rb
|
78
|
+
- lib/wind_up/routers.rb
|
79
|
+
- lib/wind_up/version.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
- spec/wind_up/queue_manager_spec.rb
|
82
|
+
- spec/wind_up/queue_spec.rb
|
83
|
+
- spec/wind_up/routers_spec.rb
|
84
|
+
- spec/wind_up_spec.rb
|
85
|
+
- wind_up.gemspec
|
86
|
+
homepage: https://github.com/ryanlchan/wind_up
|
87
|
+
licenses: []
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.0.3
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: A drop-in replacement for Celluloid pools using the message API
|
109
|
+
test_files:
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
- spec/wind_up/queue_manager_spec.rb
|
112
|
+
- spec/wind_up/queue_spec.rb
|
113
|
+
- spec/wind_up/routers_spec.rb
|
114
|
+
- spec/wind_up_spec.rb
|