Skip to content

Commit

Permalink
At this point it looks like delayed job is doing its job, but since s…
Browse files Browse the repository at this point in the history
…mtp is not working correctly feedback is minimal
  • Loading branch information
Ryan Neufeld committed Jul 31, 2009
1 parent 035b4b1 commit 2ae9fbc
Show file tree
Hide file tree
Showing 24 changed files with 1,363 additions and 12 deletions.
2 changes: 1 addition & 1 deletion app/controllers/admin/freemailer_campaigns_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def clear_active
def send_campaign
campaign = @session_user.active_campaign
campaign.send_campaign
flash[:notice] = "Mail Campaign Sent."
flash[:notice] = "Mailing Campaign will be sent shortly."
@session_user.active_campaign = nil
@session_user.save
redirect_to freemailer_campaigns_url
Expand Down
30 changes: 19 additions & 11 deletions app/models/freemailer_campaign.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,28 @@ def preview
def send_campaign
if not self[:sent]
freemailer_campaign_contacts.each do |contact_join|
begin
Freemailer.deliver_from_template(self,contact_join.contact)
contact_join.delivery_status = "Successful"; contact_join.save
rescue Net::SMTPError => e
debugger
contact_join.delivery_status = e.to_s
end
self.send_later(:send_individual_mail,contact_join)
end
self[:sent] = true; self.save
puts sent
end
end

def send_individual_mail(contact_join)
begin
if contact_join.delivery_status !~ /Success/
raise Net::SMTPFatalError if rand[2] == 1
Freemailer.deliver_from_template(self,contact_join.contact)
contact_join.delivery_status = "Successful"
contact_join.save
end
rescue Net::SMTPError => e
puts "Delivery error rescued!"
debugger
contact_join.delivery_status = e.to_s
contact_join.save
end
end

def preview_user
{
'first name' => 'John',
Expand All @@ -73,17 +82,16 @@ def fill_template_for_contact(person)

def fill_template(user_hash)
user_hash.default = ''
(body_template || "-- Body is empty --").gsub(/\[\[(.*?)\]\]/) do |item|
(body_template || "-- Body is empty -- ").gsub(/\[\[(.*?)\]\]/) do |item|
user_hash[$1].to_s
end
end

def successfully_sent
freemailer_campaign_contacts.reject {|sending| sending.delivery_status != "Successful"}.count
freemailer_campaign_contacts.count( :conditions => { :delivery_status => "Successful" })
end

def remove_active_campaign
debugger
if sender.active_campaign == self
sender.active_campaign = nil
sender.save
Expand Down
20 changes: 20 additions & 0 deletions vendor/plugins/delayed_job/MIT-LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2005 Tobias Luetke

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
107 changes: 107 additions & 0 deletions vendor/plugins/delayed_job/README.textile
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
h1. Delayed::Job

Delated_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background.

It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks. Amongst those tasks are:

* sending massive newsletters
* image resizing
* http downloads
* updating smart collections
* updating solr, our search server, after product changes
* batch imports
* spam checks

h2. Setup

The library evolves around a delayed_jobs table which looks as follows:

create_table :delayed_jobs, :force => true do |table|
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
table.text :handler # YAML-encoded string of the object that will do work
table.string :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.timestamps
end

On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries.

The default MAX_ATTEMPTS is 25. After this, the job either deleted (default), or left in the database with "failed_at" set.
With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours.

The default MAX_RUN_TIME is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to
make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take.

By default, it will delete failed jobs (and it always deletes successful jobs). If you want to keep failed jobs, set
Delayed::Job.destroy_failed_jobs = false. The failed jobs will be marked with non-null failed_at.

Here is an example of changing job parameters in Rails:

# config/initializers/delayed_job_config.rb
Delayed::Job.destroy_failed_jobs = false
silence_warnings do
Delayed::Job.const_set("MAX_ATTEMPTS", 3)
Delayed::Job.const_set("MAX_RUN_TIME", 5.minutes)
end

Note: If your error messages are long, consider changing last_error field to a :text instead of a :string (255 character limit).


h2. Usage

Jobs are simple ruby objects with a method called perform. Any object which responds to perform can be stuffed into the jobs table.
Job objects are serialized to yaml so that they can later be resurrected by the job runner.

class NewsletterJob < Struct.new(:text, :emails)
def perform
emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
end
end

Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))

There is also a second way to get jobs in the queue: send_later.


BatchImporter.new(Shop.find(1)).send_later(:import_massive_csv, massive_csv)


This will simply create a Delayed::PerformableMethod job in the jobs table which serializes all the parameters you pass to it. There are some special smarts for active record objects
which are stored as their text representation and loaded from the database fresh when the job is actually run later.


h2. Running the jobs

Run @script/generate delayed_job@ to add @script/delayed_job@. This script can then be used to manage a process which will start working off jobs.

# Runs two workers in separate processes.
$ ruby script/delayed_job -e production -n 2 start
$ ruby script/delayed_job -e production stop

You can invoke @rake jobs:work@ which will start working off jobs. You can cancel the rake task with @CTRL-C@.

Workers can be running on any computer, as long as they have access to the database and their clock is in sync. You can even
run multiple workers on per computer, but you must give each one a unique name. (TODO: put in an example)
Keep in mind that each worker will check the database at least every 5 seconds.

Note: The rake task will exit if the database has any network connectivity problems.

h3. Cleaning up

You can invoke @rake jobs:clear@ to delete all jobs in the queue.

h3. Changes

* 1.7.0: Added failed_at column which can optionally be set after a certain amount of failed job attempts. By default failed job attempts are destroyed after about a month.

* 1.6.0: Renamed locked_until to locked_at. We now store when we start a given job instead of how long it will be locked by the worker. This allows us to get a reading on how long a job took to execute.

* 1.5.0: Job runners can now be run in parallel. Two new database columns are needed: locked_until and locked_by. This allows us to use pessimistic locking instead of relying on row level locks. This enables us to run as many worker processes as we need to speed up queue processing.

* 1.2.0: Added #send_later to Object for simpler job creation

* 1.0.0: Initial release
22 changes: 22 additions & 0 deletions vendor/plugins/delayed_job/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- encoding: utf-8 -*-
begin
require 'jeweler'
rescue LoadError
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
exit 1
end

Jeweler::Tasks.new do |s|
s.name = "delayed_job"
s.summary = "Database-backed asynchronous priority queue system -- Extracted from Shopify"
s.email = "[email protected]"
s.homepage = "http://github.com/tobi/delayed_job/tree/master"
s.description = "Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks."
s.authors = ["Tobias Lütke"]

s.has_rdoc = true
s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
s.extra_rdoc_files = ["README.textile"]

s.test_files = Dir['spec/**/*']
end
1 change: 1 addition & 0 deletions vendor/plugins/delayed_job/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.8.0
61 changes: 61 additions & 0 deletions vendor/plugins/delayed_job/delayed_job.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# -*- encoding: utf-8 -*-

Gem::Specification.new do |s|
s.name = %q{delayed_job}
s.version = "1.8.0"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Tobias L\303\274tke"]
s.date = %q{2009-07-19}
s.description = %q{Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks.}
s.email = %q{[email protected]}
s.extra_rdoc_files = [
"README.textile"
]
s.files = [
".gitignore",
"MIT-LICENSE",
"README.textile",
"Rakefile",
"VERSION",
"delayed_job.gemspec",
"generators/delayed_job/delayed_job_generator.rb",
"generators/delayed_job/templates/migration.rb",
"generators/delayed_job/templates/script",
"init.rb",
"lib/delayed/command.rb",
"lib/delayed/job.rb",
"lib/delayed/message_sending.rb",
"lib/delayed/performable_method.rb",
"lib/delayed/worker.rb",
"lib/delayed_job.rb",
"recipes/delayed_job.rb",
"spec/database.rb",
"spec/delayed_method_spec.rb",
"spec/job_spec.rb",
"spec/story_spec.rb",
"tasks/jobs.rake",
"tasks/tasks.rb"
]
s.homepage = %q{http://github.com/tobi/delayed_job/tree/master}
s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.3}
s.summary = %q{Database-backed asynchronous priority queue system -- Extracted from Shopify}
s.test_files = [
"spec/database.rb",
"spec/delayed_method_spec.rb",
"spec/job_spec.rb",
"spec/story_spec.rb"
]

if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3

if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class DelayedJobGenerator < Rails::Generator::Base

def manifest
record do |m|
m.template 'script', 'script/delayed_job', :chmod => 0755
m.migration_template "migration.rb", 'db/migrate',
:migration_file_name => "create_delayed_jobs"
end
end

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class CreateDelayedJobs < ActiveRecord::Migration
def self.up
create_table :delayed_jobs, :force => true do |table|
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
table.text :handler # YAML-encoded string of the object that will do work
table.text :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.timestamps
end

end

def self.down
drop_table :delayed_jobs
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env ruby

# Daemons sets pwd to /, so we have to explicitly set RAILS_ROOT
RAILS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))

require File.join(File.dirname(__FILE__), *%w(.. vendor plugins delayed_job lib delayed command))
Delayed::Command.new(ARGV).daemonize
1 change: 1 addition & 0 deletions vendor/plugins/delayed_job/init.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require File.dirname(__FILE__) + '/lib/delayed_job'
65 changes: 65 additions & 0 deletions vendor/plugins/delayed_job/lib/delayed/command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require 'rubygems'
require 'daemons'
require 'optparse'

module Delayed
class Command
attr_accessor :worker_count

def initialize(args)
@options = {:quiet => true}
@worker_count = 1

opts = OptionParser.new do |opts|
opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"

opts.on('-h', '--help', 'Show this message') do
puts opts
exit 1
end
opts.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |e|
ENV['RAILS_ENV'] = e
end
opts.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
@options[:min_priority] = n
end
opts.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
@options[:max_priority] = n
end
opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
@worker_count = worker_count.to_i rescue 1
end
end
@args = opts.parse!(args)
end

def daemonize
worker_count.times do |worker_index|
process_name = worker_count == 1 ? "delayed_job" : "delayed_job.#{worker_index}"
Daemons.run_proc(process_name, :dir => "#{RAILS_ROOT}/tmp/pids", :dir_mode => :normal, :ARGV => @args) do |*args|
run process_name
end
end
end

def run(worker_name = nil)
Dir.chdir(RAILS_ROOT)
require File.join(RAILS_ROOT, 'config', 'environment')

# Replace the default logger
logger = Logger.new(File.join(RAILS_ROOT, 'log', 'delayed_job.log'))
logger.level = ActiveRecord::Base.logger.level
ActiveRecord::Base.logger = logger
ActiveRecord::Base.clear_active_connections!
Delayed::Worker.logger = logger
Delayed::Job.worker_name = "#{worker_name} #{Delayed::Job.worker_name}"

Delayed::Worker.new(@options).start
rescue => e
logger.fatal e
STDERR.puts e.message
exit 1
end

end
end
Loading

0 comments on commit 2ae9fbc

Please sign in to comment.