delayed_job_celluloid 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +78 -0
- data/Rakefile +11 -0
- data/delayed_job_celluloid.gemspec +28 -0
- data/lib/delayed_job_celluloid/command.rb +104 -0
- data/lib/delayed_job_celluloid/launcher.rb +20 -0
- data/lib/delayed_job_celluloid/manager.rb +113 -0
- data/lib/delayed_job_celluloid/version.rb +3 -0
- data/lib/delayed_job_celluloid/worker.rb +85 -0
- data/lib/delayed_job_celluloid.rb +13 -0
- data/lib/generators/delayed_job_celluloid/delayed_job_celluloid_generator.rb +11 -0
- data/lib/generators/delayed_job_celluloid/templates/script +5 -0
- data/spec/command_test.rb +18 -0
- data/spec/database.yml +3 -0
- data/spec/launcher_spec.rb +22 -0
- data/spec/manager_spec.rb +88 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/test_job.rb +7 -0
- data/spec/worker_spec.rb +38 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YmNjODUyMWU4YjE2ZWE2ZDBjNjk5OTIxM2U0ZTNmNWU2YTcyODczOA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZGZjMTFhZmE4ZjhhNTQ5ZjU5YjFiYjM0ZWM0MjA3YTE3N2UzZWU4MQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MjJkYjY2OWFlYmVjZjQ1Zjc0MGI3ODYxYWM5M2UxYmUwNzFkMWQzYWQ0OTlm
|
10
|
+
MjFiYzhhZDU0ZDExNWNjNjY4NGNhMmQ5NGIyNWJiZjA3NmI2YzAwYmQzYzBk
|
11
|
+
YjI0MDZkOGY1YmQ1YzIwMzQ1OGQ1ODhmMGMxODIyMDI0OGUxMGM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NjAzMDMyNjNlNWQ5Njg1NjA2NzhkMjUxNzRlOTNhMGMwNmI4MzBkYzFlZWRi
|
14
|
+
ZDVhNjg5NmE5ZWM2ZDlkYWEwNGMzZjQxNTQ5NjE5OTRlZGEyOGRmYWJkMWNh
|
15
|
+
MTUxYzRkYjBlMTQ1ZjM1MWRiMmU5Y2JhN2IzMDI3NWMwZjUwZTM=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 tom
|
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,78 @@
|
|
1
|
+
# DelayedJobCelluloid
|
2
|
+
|
3
|
+
Based on awesome gems like Sidekiq and Suckerpunch, DelayedJobCelluloid allows delayed job workers to be run in multiple threads within a single process using the Celluloid actor pattern. The delayed_job workers by default start a new single-threaded process for each worker. By running workers in a single multi-threaded process instead, delayed_job_celluloid is more efficient in terms of memory use and speed.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add delayed_job_celluloid to your gem file
|
8
|
+
|
9
|
+
gem 'delayed_job_celluloid
|
10
|
+
|
11
|
+
Run bundle install
|
12
|
+
|
13
|
+
bundle install delayed_job_celluloid
|
14
|
+
|
15
|
+
To add the startup script to your script directory, run the generator
|
16
|
+
|
17
|
+
rails generate delayed_job_celluloid
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
First, make sure you have your preferred delayed job backend installed, for instance delayed_job_activerecord, or delayed_job_mongoid. See [delayed_job](https://github.com/collectiveidea/delayed_job) for more information.
|
22
|
+
|
23
|
+
To start working off of your delayed_job queues simply run the below from your application's root directory
|
24
|
+
|
25
|
+
bundle exec script/delayed_job_celluloid
|
26
|
+
|
27
|
+
To specify the number of worker threads to start, pass the -n parameter to the startup script. For example, the below command would start 5 worker threads. The default is 2.
|
28
|
+
|
29
|
+
bundle exec script/delayed_job_celluloid -n 5
|
30
|
+
|
31
|
+
One important thing to bear in mind with this is that you should have a database connection pool size that is at least equal to the number of worker threads you are running due to the fact that each thread will need its own connection. if there are not enough connections available you will get database errors. You can set this in the database.yml file in your app.
|
32
|
+
|
33
|
+
development:
|
34
|
+
adapter: postgresql
|
35
|
+
database: dev
|
36
|
+
host: localhost
|
37
|
+
pool: 5
|
38
|
+
|
39
|
+
Currently the gem does not support daemonization of the main process because I haven't needed it as I am using it in conjuction with Unicorn on Heroku. If you are running your app on Heroku, this gem will allow you to run multiple workers in a single Unicorn process. An example unicorn config is as follows:
|
40
|
+
|
41
|
+
# config/unicorn.rb
|
42
|
+
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 2)
|
43
|
+
timeout 15
|
44
|
+
preload_app true
|
45
|
+
|
46
|
+
before_fork do |server, worker|
|
47
|
+
|
48
|
+
#Start the Delayed Job worker inside of a Unicorn process
|
49
|
+
@delayed_job_celluloid_pid ||= spawn("bundle exec script/delayed_job_celluloid -n 5")
|
50
|
+
|
51
|
+
Signal.trap 'TERM' do
|
52
|
+
puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
|
53
|
+
Process.kill 'QUIT', Process.pid
|
54
|
+
end
|
55
|
+
|
56
|
+
defined?(ActiveRecord::Base) and
|
57
|
+
ActiveRecord::Base.connection.disconnect!
|
58
|
+
end
|
59
|
+
|
60
|
+
after_fork do |server, worker|
|
61
|
+
|
62
|
+
Signal.trap 'TERM' do
|
63
|
+
puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
|
64
|
+
end
|
65
|
+
|
66
|
+
defined?(ActiveRecord::Base) and
|
67
|
+
ActiveRecord::Base.establish_connection
|
68
|
+
end
|
69
|
+
|
70
|
+
If you are interested in adding daemonization to the gem itself, feel free to fork it and submit a pull request.
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
1. Fork it
|
75
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
76
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
77
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
78
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'delayed_job_celluloid/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "delayed_job_celluloid"
|
8
|
+
s.version = DelayedJobCelluloid::VERSION
|
9
|
+
s.authors = ["Tom Mooney"]
|
10
|
+
s.email = ["tmooney3979@gmail.com"]
|
11
|
+
s.description = %q{run delayed_job workers in multiple threads within a single process}
|
12
|
+
s.summary = %q{multi-threaded delayed_job workers!}
|
13
|
+
s.homepage = ""
|
14
|
+
s.license = "MIT"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'delayed_job'
|
22
|
+
s.add_dependency 'celluloid'
|
23
|
+
|
24
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
s.add_development_dependency "rake"
|
26
|
+
s.add_development_dependency "sqlite3"
|
27
|
+
s.add_development_dependency "delayed_job_active_record"
|
28
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
$stdout.sync = true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'celluloid/autostart'
|
5
|
+
|
6
|
+
module DelayedJobCelluloid
|
7
|
+
|
8
|
+
class Shutdown < Interrupt; end
|
9
|
+
|
10
|
+
class Command
|
11
|
+
|
12
|
+
attr_accessor :worker_count
|
13
|
+
|
14
|
+
def initialize(args)
|
15
|
+
parse_options(args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
self_read, self_write = IO.pipe
|
20
|
+
|
21
|
+
%w(INT TERM).each do |sig|
|
22
|
+
trap sig do
|
23
|
+
self_write.puts(sig)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'delayed_job_celluloid/launcher'
|
28
|
+
@launcher = Launcher.new(@options, @worker_count)
|
29
|
+
|
30
|
+
begin
|
31
|
+
@launcher.run
|
32
|
+
|
33
|
+
while readable_io = IO.select([self_read])
|
34
|
+
signal = readable_io.first[0].gets.strip
|
35
|
+
handle_signal(signal)
|
36
|
+
end
|
37
|
+
rescue Interrupt
|
38
|
+
@launcher.stop
|
39
|
+
exit(0)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_signal(signal)
|
44
|
+
case signal
|
45
|
+
when 'INT','TERM'
|
46
|
+
raise Interrupt
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_options(args)
|
52
|
+
@options = {
|
53
|
+
:quiet => true,
|
54
|
+
:timeout => 8
|
55
|
+
}
|
56
|
+
|
57
|
+
@worker_count = 2
|
58
|
+
|
59
|
+
opts = OptionParser.new do |opts|
|
60
|
+
opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
|
61
|
+
|
62
|
+
opts.on('-h', '--help', 'Show this message') do
|
63
|
+
puts opts
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
opts.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |e|
|
67
|
+
STDERR.puts "The -e/--environment option has been deprecated and has no effect. Use RAILS_ENV and see http://github.com/collectiveidea/delayed_job/issues/#issue/7"
|
68
|
+
end
|
69
|
+
opts.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
|
70
|
+
@options[:min_priority] = n
|
71
|
+
end
|
72
|
+
opts.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
|
73
|
+
@options[:max_priority] = n
|
74
|
+
end
|
75
|
+
opts.on('-n', '--number_of_workers=workers', "Number of worker threads to start") do |worker_count|
|
76
|
+
@worker_count = worker_count.to_i rescue 1
|
77
|
+
end
|
78
|
+
opts.on('--sleep-delay N', "Amount of time to sleep when no jobs are found") do |n|
|
79
|
+
@options[:sleep_delay] = n.to_i
|
80
|
+
end
|
81
|
+
opts.on('--read-ahead N', "Number of jobs from the queue to consider") do |n|
|
82
|
+
@options[:read_ahead] = n
|
83
|
+
end
|
84
|
+
opts.on('-p', '--prefix NAME', "String to be prefixed to worker process names") do |prefix|
|
85
|
+
@options[:prefix] = prefix
|
86
|
+
end
|
87
|
+
opts.on('--queues=queues', "Specify which queue DJ must look up for jobs") do |queues|
|
88
|
+
@options[:queues] = queues.split(',')
|
89
|
+
end
|
90
|
+
opts.on('--queue=queue', "Specify which queue DJ must look up for jobs") do |queue|
|
91
|
+
@options[:queues] = queue.split(',')
|
92
|
+
end
|
93
|
+
opts.on('--exit-on-complete', "Exit when no more jobs are available to run. This will exit if all jobs are scheduled to run in the future.") do
|
94
|
+
@options[:exit_on_complete] = true
|
95
|
+
end
|
96
|
+
opts.on('-t', '--timeout NUM', "Shutdown timeout") do |prefix|
|
97
|
+
@options[:timeout] = Integer(arg)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
@args = opts.parse!(args)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'manager'
|
2
|
+
|
3
|
+
module DelayedJobCelluloid
|
4
|
+
class Launcher
|
5
|
+
attr_reader :manager, :options
|
6
|
+
def initialize(options, worker_count)
|
7
|
+
@options = options
|
8
|
+
@manager = Manager.new(options, worker_count)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
manager.async.start
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop
|
16
|
+
manager.async.stop(options[:timeout])
|
17
|
+
manager.wait(:shutdown)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require_relative 'worker'
|
2
|
+
|
3
|
+
module DelayedJobCelluloid
|
4
|
+
class Manager
|
5
|
+
include Celluloid
|
6
|
+
include Logger
|
7
|
+
|
8
|
+
trap_exit :worker_died
|
9
|
+
|
10
|
+
attr_reader :ready
|
11
|
+
attr_reader :busy
|
12
|
+
|
13
|
+
def initialize(options={}, worker_count)
|
14
|
+
@options = options
|
15
|
+
@worker_count = worker_count || 1
|
16
|
+
@done_callback = nil
|
17
|
+
|
18
|
+
@in_progress = {}
|
19
|
+
@threads = {}
|
20
|
+
@done = false
|
21
|
+
@busy = []
|
22
|
+
@ready = @worker_count.times.map do
|
23
|
+
w = Worker.new_link(options, current_actor)
|
24
|
+
w.proxy_id = w.object_id
|
25
|
+
w
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def start
|
30
|
+
@ready.each_with_index do |worker, index|
|
31
|
+
worker.name = "delayed_job.#{index}"
|
32
|
+
worker.async.start
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop(timeout)
|
37
|
+
@done = true
|
38
|
+
|
39
|
+
info "Shutting down #{@ready.size} idle workers"
|
40
|
+
@ready.each do |worker|
|
41
|
+
worker.terminate if worker.alive?
|
42
|
+
end
|
43
|
+
@ready.clear
|
44
|
+
|
45
|
+
return after(0) { signal(:shutdown) } if @busy.empty?
|
46
|
+
hard_shutdown_in timeout
|
47
|
+
end
|
48
|
+
|
49
|
+
def work(worker)
|
50
|
+
@ready.delete(worker)
|
51
|
+
@busy << worker
|
52
|
+
end
|
53
|
+
|
54
|
+
def worker_done(worker)
|
55
|
+
@busy.delete(worker)
|
56
|
+
if stopped?
|
57
|
+
worker.terminate if worker.alive?
|
58
|
+
signal(:shutdown) if @busy.empty?
|
59
|
+
else
|
60
|
+
@ready << worker if worker.alive?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def worker_died(worker, reason)
|
65
|
+
debug "#{worker.inspect} died because of #{reason}" unless reason.nil?
|
66
|
+
@busy.delete(worker)
|
67
|
+
unless stopped?
|
68
|
+
worker = Worker.new_link(@options, current_actor)
|
69
|
+
worker.name = "restarted"
|
70
|
+
@ready << worker
|
71
|
+
worker.async.start
|
72
|
+
else
|
73
|
+
signal(:shutdown) if @busy.empty?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def stopped?
|
78
|
+
@done
|
79
|
+
end
|
80
|
+
|
81
|
+
def real_thread(proxy_id, thr)
|
82
|
+
@threads[proxy_id] = thr
|
83
|
+
end
|
84
|
+
|
85
|
+
def hard_shutdown_in(delay)
|
86
|
+
info "Pausing up to #{delay} seconds to allow workers to finish..."
|
87
|
+
|
88
|
+
after(delay) do
|
89
|
+
# We've reached the timeout and we still have busy workers.
|
90
|
+
# They must die but their messages shall live on.
|
91
|
+
info "Still waiting for #{@busy.size} busy workers"
|
92
|
+
|
93
|
+
# Re-enqueue terminated jobs
|
94
|
+
# NOTE: You may notice that we may push a job back to redis before
|
95
|
+
# the worker thread is terminated. This is ok because Sidekiq's
|
96
|
+
# contract says that jobs are run AT LEAST once. Process termination
|
97
|
+
# is delayed until we're certain the jobs are back in Redis because
|
98
|
+
# it is worse to lose a job than to run it twice.
|
99
|
+
#Sidekiq::Fetcher.strategy.bulk_requeue(@in_progress.values)
|
100
|
+
|
101
|
+
debug "Terminating #{@busy.size} busy worker threads"
|
102
|
+
@busy.each do |worker|
|
103
|
+
if worker.alive? && t = @threads.delete(worker.object_id)
|
104
|
+
t.raise Shutdown
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
after(0) { signal(:shutdown) }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'delayed_job'
|
2
|
+
require 'delayed/performable_mailer'
|
3
|
+
|
4
|
+
module DelayedJobCelluloid
|
5
|
+
class Worker < Delayed::Worker
|
6
|
+
include Celluloid
|
7
|
+
|
8
|
+
attr_accessor :proxy_id
|
9
|
+
|
10
|
+
def initialize(options={}, manager)
|
11
|
+
@manager = manager
|
12
|
+
super(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
return @name unless @name.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets the name of the worker.
|
20
|
+
def name=(val)
|
21
|
+
@name = val
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
begin
|
26
|
+
say "Starting job worker"
|
27
|
+
@manager.async.real_thread(proxy_id, Thread.current)
|
28
|
+
self.class.lifecycle.run_callbacks(:execute, self) do
|
29
|
+
loop do
|
30
|
+
self.class.lifecycle.run_callbacks(:loop, self) do
|
31
|
+
@realtime = Benchmark.realtime do
|
32
|
+
@result = work_off
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
count = @result.sum
|
37
|
+
|
38
|
+
if count.zero?
|
39
|
+
if self.class.exit_on_complete
|
40
|
+
say "No more jobs available. Exiting"
|
41
|
+
break
|
42
|
+
else
|
43
|
+
sleep(self.class.sleep_delay) unless stop?
|
44
|
+
end
|
45
|
+
else
|
46
|
+
say "#{count} jobs processed at %.4f j/s, %d failed" % [count / @realtime, @result.last]
|
47
|
+
end
|
48
|
+
|
49
|
+
break if stop?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
rescue DelayedJobCelluloid::Shutdown
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop
|
57
|
+
say "Exiting..."
|
58
|
+
@exit = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def work_off(num = 100)
|
62
|
+
success, failure = 0, 0
|
63
|
+
|
64
|
+
@manager.async.work(current_actor)
|
65
|
+
num.times do
|
66
|
+
case reserve_and_run_one_job
|
67
|
+
when true
|
68
|
+
success += 1
|
69
|
+
when false
|
70
|
+
failure += 1
|
71
|
+
else
|
72
|
+
@manager.async.worker_done(current_actor)
|
73
|
+
break # leave if no work could be done
|
74
|
+
end
|
75
|
+
if stop?
|
76
|
+
@manager.async.worker_done(current_actor)
|
77
|
+
break #leave if we're exiting
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
return [success, failure]
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'delayed_job_celluloid/command'
|
2
|
+
require 'delayed_job_celluloid/launcher'
|
3
|
+
require 'delayed_job_celluloid/manager'
|
4
|
+
require 'delayed_job_celluloid/worker'
|
5
|
+
|
6
|
+
module DelayedJobCelluloid
|
7
|
+
class << self
|
8
|
+
attr_accessor :logger
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
DelayedJobCelluloid.logger = Logger.new(STDERR)
|
13
|
+
Celluloid.logger = DelayedJobCelluloid.logger
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class DelayedJobCelluloidGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
6
|
+
|
7
|
+
def create_executable_file
|
8
|
+
template "script", "script/delayed_job_celluloid"
|
9
|
+
chmod "script/delayed_job_celluloid", 0755
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'test_job'
|
3
|
+
|
4
|
+
class CommandSpec < Minitest::Unit::TestCase
|
5
|
+
describe 'command' do
|
6
|
+
|
7
|
+
it "starts workers" do
|
8
|
+
i = 0
|
9
|
+
while i < 10 do
|
10
|
+
test = TestJob.new
|
11
|
+
test.delay.test_this
|
12
|
+
i += 1
|
13
|
+
end
|
14
|
+
DelayedJobCelluloid::Command.new(ARGV << '-n 2').run
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/spec/database.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'test_job'
|
3
|
+
|
4
|
+
class LauncherSpec < Minitest::Unit::TestCase
|
5
|
+
describe 'launcher' do
|
6
|
+
|
7
|
+
it "starts working jobs" do
|
8
|
+
DelayedJobCelluloid::Worker.exit_on_complete = true
|
9
|
+
|
10
|
+
i=0
|
11
|
+
while i < 10 do
|
12
|
+
test = TestJob.new
|
13
|
+
test.delay.test_this
|
14
|
+
i += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
launcher = DelayedJobCelluloid::Launcher.new({}, 2)
|
18
|
+
launcher.run
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
class ManagerSpec < Minitest::Unit::TestCase
|
4
|
+
describe 'manager' do
|
5
|
+
|
6
|
+
it "creates N worker instances" do
|
7
|
+
mgr = DelayedJobCelluloid::Manager.new({}, 3)
|
8
|
+
assert_equal mgr.ready.size, 3
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'starts specified number of workers upon start' do
|
12
|
+
worker = Minitest::Mock.new
|
13
|
+
start = Minitest::Mock.new
|
14
|
+
3.times {start.expect(:start, nil, [])}
|
15
|
+
3.times {worker.expect(:async, start, [])}
|
16
|
+
|
17
|
+
mgr = DelayedJobCelluloid::Manager.new({}, 0)
|
18
|
+
|
19
|
+
i = 0
|
20
|
+
while i < 3 do
|
21
|
+
worker.expect(:name=, nil, ["delayed_job.#{i}"])
|
22
|
+
mgr.ready << worker
|
23
|
+
i += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
mgr.start
|
27
|
+
|
28
|
+
assert_equal mgr.ready.size, 3
|
29
|
+
worker.verify
|
30
|
+
start.verify
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'stops workers on stop' do
|
34
|
+
worker = Minitest::Mock.new
|
35
|
+
3.times {worker.expect(:terminate, nil, [])}
|
36
|
+
3.times {worker.expect(:alive?, true, [])}
|
37
|
+
|
38
|
+
mgr = DelayedJobCelluloid::Manager.new({}, 0)
|
39
|
+
|
40
|
+
i = 0
|
41
|
+
while i < 3 do
|
42
|
+
mgr.ready << worker
|
43
|
+
i += 1
|
44
|
+
end
|
45
|
+
|
46
|
+
mgr.busy << worker
|
47
|
+
|
48
|
+
mgr.stop(8)
|
49
|
+
|
50
|
+
assert_equal mgr.ready.size, 0
|
51
|
+
worker.verify
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'removes workers from busy list and adds them to ready list on completion' do
|
55
|
+
worker = Minitest::Mock.new
|
56
|
+
worker.expect(:alive?, true, [])
|
57
|
+
|
58
|
+
mgr = DelayedJobCelluloid::Manager.new({}, 0)
|
59
|
+
mgr.busy << worker
|
60
|
+
|
61
|
+
assert_equal 0, mgr.ready.size
|
62
|
+
assert_equal 1, mgr.busy.size
|
63
|
+
|
64
|
+
mgr.worker_done(worker)
|
65
|
+
|
66
|
+
assert_equal 1, mgr.ready.size
|
67
|
+
assert_equal 0, mgr.busy.size
|
68
|
+
worker.verify
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'removes workers from busy list on worker_died and starts a new worker' do
|
72
|
+
worker = Minitest::Mock.new
|
73
|
+
|
74
|
+
mgr = DelayedJobCelluloid::Manager.new({}, 0)
|
75
|
+
mgr.busy << worker
|
76
|
+
|
77
|
+
assert_equal 0, mgr.ready.size
|
78
|
+
assert_equal 1, mgr.busy.size
|
79
|
+
|
80
|
+
mgr.worker_died(worker, "test")
|
81
|
+
|
82
|
+
assert_equal 0, mgr.ready.size
|
83
|
+
assert_equal 1, mgr.busy.size
|
84
|
+
worker.verify
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'delayed_job_celluloid'
|
2
|
+
require 'delayed_job'
|
3
|
+
require 'delayed_job_active_record'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/spec'
|
6
|
+
require 'minitest/pride'
|
7
|
+
require 'rails'
|
8
|
+
require 'active_record'
|
9
|
+
require 'sqlite3'
|
10
|
+
require 'logger'
|
11
|
+
require 'celluloid/autostart'
|
12
|
+
|
13
|
+
ROOT = File.join(File.dirname(__FILE__), '..')
|
14
|
+
RAILS_ROOT = ROOT
|
15
|
+
$LOAD_PATH << File.join(ROOT, 'lib')
|
16
|
+
|
17
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
18
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/db.log")
|
19
|
+
ActiveRecord::Base.establish_connection(config['test'])
|
20
|
+
DelayedJobCelluloid::Worker.logger = Logger.new(File.dirname(__FILE__) + "/dj.log")
|
21
|
+
|
22
|
+
ActiveRecord::Base.connection.create_table :delayed_jobs, :force => true do |table|
|
23
|
+
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
|
24
|
+
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
|
25
|
+
table.text :handler # YAML-encoded string of the object that will do work
|
26
|
+
table.string :last_error # reason for last failure (See Note below)
|
27
|
+
table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
|
28
|
+
table.datetime :locked_at # Set when a client is working on this object
|
29
|
+
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
|
30
|
+
table.string :locked_by # Who is working on this object (if locked)
|
31
|
+
table.string :queue
|
32
|
+
table.timestamps
|
33
|
+
end
|
data/spec/test_job.rb
ADDED
data/spec/worker_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'test_job'
|
3
|
+
|
4
|
+
DelayedJobCelluloid::Worker.backend = :active_record
|
5
|
+
|
6
|
+
class WorkerSpec < Minitest::Unit::TestCase
|
7
|
+
|
8
|
+
describe "Worker" do
|
9
|
+
|
10
|
+
before :all do
|
11
|
+
DelayedJobCelluloid::Worker.exit_on_complete = true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "runs jobs" do
|
15
|
+
|
16
|
+
manager = Minitest::Mock.new
|
17
|
+
async = Minitest::Mock.new
|
18
|
+
100.times {async.expect(:work, nil, [Celluloid::ActorProxy])}
|
19
|
+
100.times {async.expect(:worker_done, nil, [Celluloid::ActorProxy])}
|
20
|
+
async.expect(:real_thread, async, [nil, Thread])
|
21
|
+
100.times {manager.expect(:async, async, [])}
|
22
|
+
|
23
|
+
|
24
|
+
worker = DelayedJobCelluloid::Worker.new({},manager)
|
25
|
+
test = TestJob.new
|
26
|
+
test.delay.test_this
|
27
|
+
|
28
|
+
assert_equal 1, Delayed::Job.count
|
29
|
+
|
30
|
+
worker.start
|
31
|
+
|
32
|
+
assert_equal 0, Delayed::Job.count
|
33
|
+
manager.verify
|
34
|
+
async.verify
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: delayed_job_celluloid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Mooney
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: delayed_job
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
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: celluloid
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: delayed_job_active_record
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: run delayed_job workers in multiple threads within a single process
|
98
|
+
email:
|
99
|
+
- tmooney3979@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- delayed_job_celluloid.gemspec
|
110
|
+
- lib/delayed_job_celluloid.rb
|
111
|
+
- lib/delayed_job_celluloid/command.rb
|
112
|
+
- lib/delayed_job_celluloid/launcher.rb
|
113
|
+
- lib/delayed_job_celluloid/manager.rb
|
114
|
+
- lib/delayed_job_celluloid/version.rb
|
115
|
+
- lib/delayed_job_celluloid/worker.rb
|
116
|
+
- lib/generators/delayed_job_celluloid/delayed_job_celluloid_generator.rb
|
117
|
+
- lib/generators/delayed_job_celluloid/templates/script
|
118
|
+
- spec/command_test.rb
|
119
|
+
- spec/database.yml
|
120
|
+
- spec/launcher_spec.rb
|
121
|
+
- spec/manager_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
- spec/test_job.rb
|
124
|
+
- spec/worker_spec.rb
|
125
|
+
homepage: ''
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.0.7
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: multi-threaded delayed_job workers!
|
149
|
+
test_files:
|
150
|
+
- spec/command_test.rb
|
151
|
+
- spec/database.yml
|
152
|
+
- spec/launcher_spec.rb
|
153
|
+
- spec/manager_spec.rb
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
- spec/test_job.rb
|
156
|
+
- spec/worker_spec.rb
|