├── .gitignore ├── Rakefile ├── lib ├── capistrano-resque │ ├── version.rb │ ├── tasks │ │ └── capistrano-resque.rake │ └── capistrano_integration.rb └── capistrano-resque.rb ├── Gemfile ├── capistrano-resque.gemspec ├── LICENSE ├── Changelog.md ├── Gemfile.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | require "bundler/gem_tasks" 3 | -------------------------------------------------------------------------------- /lib/capistrano-resque/version.rb: -------------------------------------------------------------------------------- 1 | module CapistranoResque 2 | unless defined?(::CapistranoResque::VERSION) 3 | VERSION = "0.2.3".freeze 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem 'resque', :git => 'git://github.com/resque/resque.git', :branch => '1-x-stable' 4 | gem 'resque-scheduler' 5 | 6 | gemspec 7 | -------------------------------------------------------------------------------- /lib/capistrano-resque.rb: -------------------------------------------------------------------------------- 1 | require "capistrano-resque/version" 2 | 3 | if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new("3.0.0") 4 | load File.expand_path("../capistrano-resque/tasks/capistrano-resque.rake", __FILE__) 5 | else 6 | require "capistrano-resque/capistrano_integration" 7 | end 8 | 9 | -------------------------------------------------------------------------------- /capistrano-resque.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path("../lib/capistrano-resque/version", __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.name = "capistrano-resque" 6 | gem.version = CapistranoResque::VERSION.dup 7 | gem.author = "Steven Shingler" 8 | gem.email = "shingler@gmail.com" 9 | gem.homepage = "https://github.com/sshingler/capistrano-resque" 10 | gem.summary = %q{Resque integration for Capistrano} 11 | gem.description = %q{Capistrano plugin that integrates Resque server tasks.} 12 | 13 | gem.files = `git ls-files`.split("\n") 14 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 15 | gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)} 16 | gem.require_paths = ["lib"] 17 | 18 | gem.add_runtime_dependency "capistrano" 19 | gem.add_runtime_dependency "resque" 20 | gem.add_runtime_dependency "resque-scheduler" 21 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Steven Shingler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # 0.2.3 and newer 2 | * Please see the [Github Releases](https://github.com/sshingler/capistrano-resque/releases) page for changelog info 3 | 4 | # 0.2.2 5 | * Start all background tasks with `nohup` to avoid `SIGHUP` problems 6 | * Add a `:resque_verbose` option to toggle verbose output (defaults to `true`) 7 | 8 | # 0.2.1 9 | * Create the directory for pid files when it doesn't exist 10 | * Default pid files to `#{shared_path}/tmp/pids` now 11 | * Added a `:resque_pid_path` option to specify a custom path 12 | 13 | # 0.2.0 14 | * Added support for Capistrano 3.0 15 | * Set MUTE environment variable for resque_scheduler 16 | * Added a `resque_environment_task` option to load the `environment` rake task before running Resque workers 17 | * Add a resque:scheduler:status task 18 | * Detect stale PID files and clean up instead of aborting 19 | * Add a `resque_rails_env` setting in case workers need to be run in a different environment than the app itself 20 | 21 | # 0.1.0 22 | * Interval is configurable 23 | * Fix issue where pids weren't created correctly 24 | 25 | # 0.0.9 26 | * Start workers in threads 27 | 28 | # 0.0.8 29 | * Using stable branch of Resque, rather than the released gem, to take advantage of logging ability, losing shell redirection 30 | * Using SIGQUIT to kill processes as they aren't terminating properly 31 | 32 | # 0.0.7 33 | * Different workers for different roles 34 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/resque/resque.git 3 | revision: 41469a3eaa9394a60a2ff249c4d00a41f06b4b22 4 | branch: 1-x-stable 5 | specs: 6 | resque (1.25.2) 7 | mono_logger (~> 1.0) 8 | multi_json (~> 1.0) 9 | redis-namespace (~> 1.3) 10 | sinatra (>= 0.9.2) 11 | vegas (~> 0.1.2) 12 | 13 | PATH 14 | remote: . 15 | specs: 16 | capistrano-resque (0.2.3) 17 | capistrano 18 | resque 19 | resque-scheduler 20 | 21 | GEM 22 | remote: http://rubygems.org/ 23 | specs: 24 | airbrussh (1.1.1) 25 | sshkit (>= 1.6.1, != 1.7.0) 26 | capistrano (3.6.1) 27 | airbrussh (>= 1.0.0) 28 | capistrano-harrow 29 | i18n 30 | rake (>= 10.0.0) 31 | sshkit (>= 1.9.0) 32 | capistrano-harrow (0.5.3) 33 | i18n (0.7.0) 34 | mono_logger (1.1.0) 35 | multi_json (1.10.1) 36 | net-scp (1.2.1) 37 | net-ssh (>= 2.6.5) 38 | net-ssh (3.2.0) 39 | rack (1.5.2) 40 | rack-protection (1.5.3) 41 | rack 42 | rake (11.3.0) 43 | redis (3.1.0) 44 | redis-namespace (1.5.1) 45 | redis (~> 3.0, >= 3.0.4) 46 | resque-scheduler (3.0.0) 47 | mono_logger (~> 1.0) 48 | redis (~> 3.0) 49 | resque (~> 1.25) 50 | rufus-scheduler (~> 2.0) 51 | rufus-scheduler (2.0.24) 52 | tzinfo (>= 0.3.22) 53 | sinatra (1.4.5) 54 | rack (~> 1.4) 55 | rack-protection (~> 1.4) 56 | tilt (~> 1.3, >= 1.3.4) 57 | sshkit (1.11.3) 58 | net-scp (>= 1.1.2) 59 | net-ssh (>= 2.8.0) 60 | thread_safe (0.3.4) 61 | tilt (1.4.1) 62 | tzinfo (1.2.2) 63 | thread_safe (~> 0.1) 64 | vegas (0.1.11) 65 | rack (>= 1.0.0) 66 | 67 | PLATFORMS 68 | ruby 69 | 70 | DEPENDENCIES 71 | capistrano-resque! 72 | resque! 73 | resque-scheduler 74 | 75 | BUNDLED WITH 76 | 1.13.4 77 | -------------------------------------------------------------------------------- /lib/capistrano-resque/tasks/capistrano-resque.rake: -------------------------------------------------------------------------------- 1 | namespace :load do 2 | task :defaults do 3 | set :workers, {"*" => 1} 4 | set :resque_extra_env, "" 5 | set :resque_kill_signal, "QUIT" 6 | set :interval, "5" 7 | set :resque_environment_task, false 8 | set :resque_log_file, "/dev/null" 9 | set :resque_verbose, true 10 | set :resque_pid_path, -> { File.join(shared_path, 'tmp', 'pids') } 11 | set :resque_dynamic_schedule, false 12 | end 13 | end 14 | 15 | namespace :resque do 16 | def rails_env 17 | fetch(:resque_rails_env) || 18 | fetch(:rails_env) || # capistrano-rails doesn't automatically set this (yet), 19 | fetch(:stage) # so we need to fall back to the stage. 20 | end 21 | 22 | def output_redirection 23 | ">> #{fetch(:resque_log_file)} 2>> #{fetch(:resque_log_file)}" 24 | end 25 | 26 | def workers_roles 27 | return fetch(:workers).keys if fetch(:workers).first[1].is_a? Hash 28 | [:resque_worker] 29 | end 30 | 31 | def for_each_workers(&block) 32 | if fetch(:workers).first[1].is_a? Hash 33 | workers_roles.each do |role| 34 | yield(role.to_sym, fetch(:workers)[role.to_sym]) 35 | end 36 | else 37 | yield(:resque_worker, fetch(:workers)) 38 | end 39 | end 40 | 41 | def create_pid_path 42 | if !(test "[ -d #{fetch(:resque_pid_path)} ]") 43 | info "Creating #{fetch(:resque_pid_path)}" 44 | execute :mkdir, "-p #{fetch(:resque_pid_path)}" 45 | end 46 | end 47 | 48 | desc "See current worker status" 49 | task :status do 50 | on roles(*workers_roles) do 51 | if test "[ -e #{fetch(:resque_pid_path)}/resque_work_1.pid ]" 52 | within current_path do 53 | files = capture(:ls, "-1 #{fetch(:resque_pid_path)}/resque_work*.pid") 54 | files.each_line do |file| 55 | info capture(:ps, "-f -p $(cat #{file.chomp}) | sed -n 2p") 56 | end 57 | end 58 | end 59 | end 60 | end 61 | 62 | desc "Start Resque workers" 63 | task :start do 64 | 65 | for_each_workers do |role, workers| 66 | on roles(role) do 67 | create_pid_path 68 | worker_id = 1 69 | workers.each_pair do |queue, number_of_workers| 70 | info "Starting #{number_of_workers} worker(s) with QUEUE: #{queue}" 71 | number_of_workers.times do 72 | pid = "#{fetch(:resque_pid_path)}/resque_work_#{worker_id}.pid" 73 | within current_path do 74 | execute :nohup, %{#{SSHKit.config.command_map[:rake]} RACK_ENV=#{rails_env} RAILS_ENV=#{rails_env} #{fetch(:resque_extra_env)} QUEUE="#{queue}" PIDFILE=#{pid} BACKGROUND=yes #{"VERBOSE=1 " if fetch(:resque_verbose)}INTERVAL=#{fetch(:interval)} #{"environment " if fetch(:resque_environment_task)}resque:work #{output_redirection}} 75 | end 76 | worker_id += 1 77 | end 78 | end 79 | end 80 | end 81 | end 82 | 83 | # See https://github.com/defunkt/resque#signals for a descriptions of signals 84 | # QUIT - Wait for child to finish processing then exit (graceful) 85 | # TERM / INT - Immediately kill child then exit (stale or stuck) 86 | # USR1 - Immediately kill child but don't exit (stale or stuck) 87 | # USR2 - Don't start to process any new jobs (pause) 88 | # CONT - Start to process new jobs again after a USR2 (resume) 89 | desc "Quit running Resque workers" 90 | task :stop do 91 | on roles(*workers_roles) do 92 | if test "[ -e #{fetch(:resque_pid_path)}/resque_work_1.pid ]" 93 | within current_path do 94 | pids = capture(:ls, "-1 #{fetch(:resque_pid_path)}/resque_work*.pid").lines.map(&:chomp) 95 | pids.each do |pid_file| 96 | pid = capture(:cat, pid_file) 97 | if test "kill -0 #{pid} > /dev/null 2>&1" 98 | execute :kill, "-s #{fetch(:resque_kill_signal)} #{pid} && rm #{pid_file}" 99 | else 100 | info "Process #{pid} from #{pid_file} is not running, cleaning up stale PID file" 101 | execute :rm, pid_file 102 | end 103 | end 104 | end 105 | else 106 | info "No resque PID files found" 107 | end 108 | end 109 | end 110 | 111 | desc "Restart running Resque workers" 112 | task :restart do 113 | invoke "resque:stop" 114 | invoke "resque:start" 115 | end 116 | 117 | namespace :scheduler do 118 | desc "See current scheduler status" 119 | task :status do 120 | on roles :resque_scheduler do 121 | pid = "#{fetch(:resque_pid_path)}/scheduler.pid" 122 | if test "[ -e #{pid} ]" 123 | info capture(:ps, "-f -p $(cat #{pid}) | sed -n 2p") 124 | end 125 | end 126 | end 127 | 128 | desc "Starts resque scheduler with default configs" 129 | task :start do 130 | on roles :resque_scheduler do 131 | create_pid_path 132 | pid = "#{fetch(:resque_pid_path)}/scheduler.pid" 133 | within current_path do 134 | execute :nohup, %{#{SSHKit.config.command_map[:rake]} RACK_ENV=#{rails_env} RAILS_ENV=#{rails_env} #{fetch(:resque_extra_env)} PIDFILE=#{pid} BACKGROUND=yes #{"VERBOSE=1 " if fetch(:resque_verbose)}MUTE=1 #{"DYNAMIC_SCHEDULE=yes " if fetch(:resque_dynamic_schedule)}#{"environment " if fetch(:resque_environment_task)}resque:scheduler #{output_redirection}} 135 | end 136 | end 137 | end 138 | 139 | desc "Stops resque scheduler" 140 | task :stop do 141 | on roles :resque_scheduler do 142 | pid = "#{fetch(:resque_pid_path)}/scheduler.pid" 143 | if test "[ -e #{pid} ]" 144 | execute :kill, "-s #{fetch(:resque_kill_signal)} $(cat #{pid}); rm #{pid}" 145 | end 146 | end 147 | end 148 | 149 | desc "Restart resque scheduler" 150 | task :restart do 151 | invoke "resque:scheduler:stop" 152 | invoke "resque:scheduler:start" 153 | end 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Capistrano Resque 2 | 3 | Basic tasks for putting some Resque in your Cap. This should be fully compatible with both Capistrano 2.x and 3.x, 4 | but if you run into any issues please report them. 5 | 6 | At this time, we are only targeting Resque 1.x; the 2.0/master branch is still a work-in-progress without a published gem. 7 | 8 | ### In your Gemfile: 9 | 10 | ```ruby 11 | gem "capistrano-resque", "~> 0.2.2", require: false 12 | ``` 13 | 14 | ### In lib/tasks: 15 | 16 | You'll need to make sure your app is set to include Resque's rake tasks. Per the 17 | [Resque 1.x README](https://github.com/resque/resque/blob/1-x-stable/README.markdown#in-a-rails-3-app-as-a-gem), 18 | you'll need to add `require 'resque/tasks'` somewhere under the `lib/tasks` directory (e.g. in a `lib/tasks/resque.rake` file). 19 | 20 | ### In your Capfile: 21 | 22 | Put this line __after__ any of capistrano's own `require`/`load` statements (specifically `load 'deploy'` for Cap v2): 23 | 24 | ```ruby 25 | require "capistrano-resque" 26 | ``` 27 | 28 | Note: You must tell Bundler not to automatically require the file (by using `require: false`), 29 | otherwise the gem will try to load the Capistrano tasks outside of the context of running 30 | the `cap` command (e.g. running `rails console`). 31 | 32 | ### In your deploy.rb: 33 | 34 | ```ruby 35 | # Specify the server that Resque will be deployed on. If you are using Cap v3 36 | # and have multiple stages with different Resque requirements for each, then 37 | # these __must__ be set inside of the applicable config/deploy/... stage files 38 | # instead of config/deploy.rb: 39 | role :resque_worker, "app_domain" 40 | role :resque_scheduler, "app_domain" 41 | 42 | set :workers, { "my_queue_name" => 2 } 43 | 44 | # We default to storing PID files in a tmp/pids folder in your shared path, but 45 | # you can customize it here (make sure to use a full path). The path will be 46 | # created before starting workers if it doesn't already exist. 47 | # set :resque_pid_path, -> { File.join(shared_path, 'tmp', 'pids') } 48 | 49 | # Uncomment this line if your workers need access to the Rails environment: 50 | # set :resque_environment_task, true 51 | ``` 52 | 53 | You can also specify multiple queues and the number of workers 54 | for each queue: 55 | 56 | ```ruby 57 | set :workers, { "archive" => 1, "mailing" => 3, "search_index, cache_warming" => 1 } 58 | ``` 59 | 60 | The above will start five workers in total: 61 | 62 | * one listening on the `archive` queue 63 | * one listening on the `search_index, cache_warming` queue 64 | * three listening on the `mailing` queue 65 | 66 | If you need to pass arbitrary data (like other non-standard environment variables) to the "start" command, you can specify: 67 | 68 | ```ruby 69 | set :resque_extra_env, "SEARCH_SERVER=172.18.0.52" 70 | ``` 71 | 72 | This can be useful for customizing Resque tasks in complex server environments. 73 | 74 | ### Multiple Servers/Roles 75 | 76 | You can also start up workers on multiple servers/roles: 77 | 78 | ```ruby 79 | role :worker_server_A, 80 | role :worker_servers_B_and_C, [, ] 81 | 82 | set :workers, { 83 | worker_server_A: { 84 | "archive" => 1, 85 | "mailing" => 1 86 | }, 87 | worker_servers_B_and_C: { 88 | "search_index" => 1, 89 | } 90 | } 91 | ``` 92 | 93 | The above will start four workers in total: 94 | 95 | * one `archive` on Server A 96 | * one `mailing` on Server A 97 | * one `search_index` on Server B 98 | * one `search_index` on Server C 99 | 100 | ### Rails Environment 101 | 102 | With Rails, Resque requires loading the Rails environment task to have access to your models, etc. (e.g. `QUEUE=* rake environment resque:work`). However, Resque is often used without Rails (and even if you are using Rails, you may not need/want to load the Rails environment). As such, the `environment` task is not automatically included. 103 | 104 | If you would like to load the `environment` task automatically, add this to your `deploy.rb`: 105 | 106 | ```ruby 107 | set :resque_environment_task, true 108 | ``` 109 | 110 | If you would like your workers to use a different Rails environment than your actual Rails app: 111 | 112 | ```ruby 113 | set :resque_rails_env, "my_resque_env" 114 | ``` 115 | 116 | ### The tasks 117 | 118 | Running cap -vT | grep resque should give you... 119 | 120 | ``` 121 | ➔ cap -vT | grep resque 122 | cap resque:status # Check workers status 123 | cap resque:start # Start Resque workers 124 | cap resque:stop # Quit running Resque workers 125 | cap resque:restart # Restart running Resque workers 126 | cap resque:scheduler:restart # 127 | cap resque:scheduler:start # Starts Resque Scheduler with default configs 128 | cap resque:scheduler:stop # Stops Resque Scheduler 129 | ``` 130 | 131 | ### Restart on deployment 132 | 133 | To restart you workers automatically when `cap deploy:restart` is executed 134 | add the following line to your `deploy.rb`: 135 | 136 | ```ruby 137 | after "deploy:restart", "resque:restart" 138 | ``` 139 | 140 | ### Logging 141 | 142 | Backgrounding and logging are current sticking points. I'm using the HEAD of resque's 1-x-stable branch for the 0.0.8 release because it has some new logging functions not yet slated for a resque release. 143 | 144 | In your Gemfile, you will need to specify: 145 | 146 | ```ruby 147 | gem 'resque', :git => 'git://github.com/resque/resque.git', :branch => '1-x-stable' 148 | ``` 149 | 150 | Also, you will need to include: 151 | 152 | ```ruby 153 | Resque.logger = Logger.new("new_resque_log_file") 154 | ``` 155 | 156 | ...somewhere sensible, such as in your resque.rake, to achieve logging. 157 | 158 | The chatter on: https://github.com/defunkt/resque/pull/450 gives more information. If using HEAD of this resque branch doesn't work for you, then pin to v0.0.7 of this project. 159 | 160 | ### Redirecting output 161 | 162 | Due to issues in the way Resque 1.x handles background processes, we automatically redirect stderr and stdout to `/dev/null`. 163 | 164 | If you'd like to capture this output instead, just specify a log file: 165 | 166 | ```ruby 167 | set :resque_log_file, "log/resque.log" 168 | ``` 169 | 170 | You can also disable the `VERBOSE` option to reduce the amount of log output: 171 | 172 | ```ruby 173 | set :resque_verbose, false 174 | ``` 175 | 176 | ### Limitations 177 | 178 | Starting workers is done concurrently via Capistrano and you are limited by ssh connections limit on your server (default limit is 10) 179 | 180 | To to use more workers, please change your sshd configuration (/etc/ssh/sshd_config) 181 | 182 | MaxStartups 100 183 | 184 | 185 | ### Contributing 186 | 187 | 1. Fork it 188 | 2. Create your feature branch (`git checkout -b my-new-feature`) 189 | 3. Commit your changes (`git commit -am 'Added some feature'`) 190 | 4. If possible, make sure your changes apply to both the Capistrano v2 and v3 code (`capistrano_integration.rb` is v2, `capistrano-resque.rake` is v3) 191 | 5. Push to the branch (`git push origin my-new-feature`) 192 | 6. Create new Pull Request 193 | 194 | ### License 195 | 196 | Please see the included LICENSE file. 197 | -------------------------------------------------------------------------------- /lib/capistrano-resque/capistrano_integration.rb: -------------------------------------------------------------------------------- 1 | require "capistrano" 2 | require "capistrano/version" 3 | 4 | module CapistranoResque 5 | class CapistranoIntegration 6 | def self.load_into(capistrano_config) 7 | capistrano_config.load do 8 | 9 | _cset(:workers, {"*" => 1}) 10 | _cset(:resque_extra_env, "") 11 | _cset(:resque_kill_signal, "QUIT") 12 | _cset(:interval, "5") 13 | _cset(:resque_environment_task, false) 14 | _cset(:resque_log_file, "/dev/null") 15 | _cset(:resque_verbose, true) 16 | _cset(:resque_pid_path) { File.join(shared_path, 'tmp', 'pids') } 17 | _cset(:resque_dynamic_schedule, false) 18 | 19 | def rails_env 20 | fetch(:resque_rails_env, fetch(:rails_env, "production")) 21 | end 22 | 23 | def output_redirection 24 | ">> #{fetch(:resque_log_file)} 2>> #{fetch(:resque_log_file)}" 25 | end 26 | 27 | def workers_roles 28 | return workers.keys if workers.first[1].is_a? Hash 29 | [:resque_worker] 30 | end 31 | 32 | def for_each_workers(&block) 33 | if workers.first[1].is_a? Hash 34 | workers_roles.each do |role| 35 | yield(role.to_sym, workers[role.to_sym]) 36 | end 37 | else 38 | yield(:resque_worker,workers) 39 | end 40 | end 41 | 42 | def status_command 43 | "if [ -e #{fetch(:resque_pid_path)}/resque_work_1.pid ]; then \ 44 | for f in $(ls #{fetch(:resque_pid_path)}/resque_work*.pid); \ 45 | do ps -p $(cat $f) | sed -n 2p ; done \ 46 | ;fi" 47 | end 48 | 49 | def start_command(queue, pid) 50 | "cd #{current_path} && RACK_ENV=#{rails_env} RAILS_ENV=#{rails_env} #{resque_extra_env} QUEUE=\"#{queue}\" \ 51 | PIDFILE=#{pid} BACKGROUND=yes \ 52 | #{"VERBOSE=1 " if fetch(:resque_verbose)}\ 53 | INTERVAL=#{interval} \ 54 | nohup #{fetch(:bundle_cmd, "bundle")} exec rake \ 55 | #{"environment " if fetch(:resque_environment_task)}\ 56 | resque:work #{output_redirection}" 57 | end 58 | 59 | def stop_command 60 | "if [ -e #{fetch(:resque_pid_path)}/resque_work_1.pid ]; then \ 61 | for f in `ls #{fetch(:resque_pid_path)}/resque_work*.pid`; \ 62 | do \ 63 | if kill -0 `cat $f`> /dev/null 2>&1; then \ 64 | kill -s #{resque_kill_signal} `cat $f` \ 65 | && rm $f \ 66 | ;else \ 67 | echo 'Resque was not running, cleaning up stale PID file' \ 68 | && rm $f \ 69 | ;fi \ 70 | ;done \ 71 | ;fi" 72 | end 73 | 74 | def status_scheduler 75 | "if [ -e #{fetch(:resque_pid_path)}/scheduler.pid ]; then \ 76 | ps -p $(cat #{fetch(:resque_pid_path)}/scheduler.pid) | sed -n 2p \ 77 | ;fi" 78 | end 79 | 80 | def start_scheduler(pid) 81 | "cd #{current_path} && RACK_ENV=#{rails_env} RAILS_ENV=#{rails_env} #{resque_extra_env} \ 82 | PIDFILE=#{pid} BACKGROUND=yes \ 83 | #{"VERBOSE=1 " if fetch(:resque_verbose)}\ 84 | MUTE=1 \ 85 | #{"DYNAMIC_SCHEDULE=yes " if fetch(:resque_dynamic_schedule)}\ 86 | nohup #{fetch(:bundle_cmd, "bundle")} exec rake \ 87 | #{"environment " if fetch(:resque_environment_task)}\ 88 | resque:scheduler #{output_redirection}" 89 | end 90 | 91 | def stop_scheduler(pid) 92 | "if [ -e #{pid} ]; then \ 93 | kill -s #{resque_kill_signal} $(cat #{pid}) ; rm #{pid} \ 94 | ;fi" 95 | end 96 | 97 | def create_pid_path 98 | "if [ ! -d #{fetch(:resque_pid_path)} ]; then \ 99 | echo 'Creating #{fetch(:resque_pid_path)}' \ 100 | && mkdir -p #{fetch(:resque_pid_path)}\ 101 | ;fi" 102 | end 103 | 104 | namespace :resque do 105 | desc "See current worker status" 106 | task :status, :roles => lambda { workers_roles() }, :on_no_matching_servers => :continue do 107 | run(status_command) 108 | end 109 | 110 | desc "Start Resque workers" 111 | task :start, :roles => lambda { workers_roles() }, :on_no_matching_servers => :continue do 112 | run(create_pid_path) 113 | for_each_workers do |role, workers| 114 | worker_id = 1 115 | workers.each_pair do |queue, number_of_workers| 116 | logger.info "Starting #{number_of_workers} worker(s) with QUEUE: #{queue}" 117 | threads = [] 118 | number_of_workers.times do 119 | pid = "#{fetch(:resque_pid_path)}/resque_work_#{worker_id}.pid" 120 | threads << Thread.new(pid) { |pid| run(start_command(queue, pid), :roles => role) } 121 | worker_id += 1 122 | end 123 | threads.each(&:join) 124 | end 125 | end 126 | end 127 | 128 | # See https://github.com/defunkt/resque#signals for a descriptions of signals 129 | # QUIT - Wait for child to finish processing then exit (graceful) 130 | # TERM / INT - Immediately kill child then exit (stale or stuck) 131 | # USR1 - Immediately kill child but don't exit (stale or stuck) 132 | # USR2 - Don't start to process any new jobs (pause) 133 | # CONT - Start to process new jobs again after a USR2 (resume) 134 | desc "Quit running Resque workers" 135 | task :stop, :roles => lambda { workers_roles() }, :on_no_matching_servers => :continue do 136 | run(stop_command) 137 | end 138 | 139 | desc "Restart running Resque workers" 140 | task :restart, :roles => lambda { workers_roles() }, :on_no_matching_servers => :continue do 141 | stop 142 | start 143 | end 144 | 145 | namespace :scheduler do 146 | desc "See current scheduler status" 147 | task :status, :roles => :resque_scheduler do 148 | run(status_scheduler) 149 | end 150 | 151 | desc "Starts resque scheduler with default configs" 152 | task :start, :roles => :resque_scheduler do 153 | run(create_pid_path) 154 | pid = "#{fetch(:resque_pid_path)}/scheduler.pid" 155 | run(start_scheduler(pid)) 156 | end 157 | 158 | desc "Stops resque scheduler" 159 | task :stop, :roles => :resque_scheduler do 160 | pid = "#{fetch(:resque_pid_path)}/scheduler.pid" 161 | run(stop_scheduler(pid)) 162 | end 163 | 164 | task :restart do 165 | stop 166 | start 167 | end 168 | end 169 | end 170 | end 171 | end 172 | end 173 | end 174 | 175 | if Capistrano::Configuration.instance 176 | CapistranoResque::CapistranoIntegration.load_into(Capistrano::Configuration.instance) 177 | end 178 | --------------------------------------------------------------------------------