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 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
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
19
+ test.sqlite3
20
+ *.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in delayed_job_celluloid.gemspec
4
+ gemspec
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,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'test'
6
+ #SO MUCH NOISE
7
+ #test.warning = true
8
+ test.pattern = 'spec/**/*_spec.rb'
9
+ end
10
+
11
+ task :default => :test
@@ -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,3 @@
1
+ module DelayedJobCelluloid
2
+ VERSION = "0.1.0"
3
+ 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,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
4
+ require 'delayed_job_celluloid/command'
5
+ DelayedJobCelluloid::Command.new(ARGV).run
@@ -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,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: "spec/db/test.sqlite3"
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ class TestJob
2
+
3
+ def test_this
4
+ sleep(1)
5
+ end
6
+
7
+ end
@@ -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