afterparty 0.0.21 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.travis.yml +3 -1
- data/Gemfile +3 -1
- data/README.md +57 -13
- data/afterparty.gemspec +1 -0
- data/afterparty_test.sqlite3 +0 -0
- data/app/assets/javascripts/afterparty.js.coffee +14 -0
- data/app/assets/stylesheets/afterparty.css.sass +91 -0
- data/app/controllers/afterparty/dashboard_controller.rb +44 -0
- data/app/views/afterparty/dashboard/index.html.haml +85 -0
- data/config/routes.rb +9 -0
- data/lib/afterparty.rb +51 -7
- data/lib/afterparty/afterparty_job.rb +31 -0
- data/lib/afterparty/engine.rb +7 -0
- data/lib/afterparty/job_container.rb +36 -0
- data/lib/afterparty/jobs.rb +41 -0
- data/lib/afterparty/queue.rb +53 -0
- data/lib/afterparty/queue_helpers.rb +90 -15
- data/lib/afterparty/version.rb +1 -1
- data/lib/afterparty/worker.rb +46 -0
- data/lib/generators/afterparty_generator.rb +9 -0
- data/lib/generators/templates/initializer.rb +6 -0
- data/lib/generators/templates/jobs_migration.rb +21 -0
- data/lib/tasks/tasks.rake +25 -0
- data/spec/afterparty_job_spec.rb +17 -0
- data/spec/database.yml +3 -0
- data/spec/generators/afterparty_generator_spec.rb +10 -0
- data/spec/queue_functional_spec.rb +112 -0
- data/spec/queue_helpers_spec.rb +56 -0
- data/spec/schema.rb +14 -0
- data/spec/spec_helper.rb +15 -1
- metadata +44 -5
- data/lib/afterparty/redis_queue.rb +0 -57
- data/spec/redis_queue_spec.rb +0 -98
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'afterparty/queue_helpers'
|
3
|
+
class AfterpartyJob < ::ActiveRecord::Base
|
4
|
+
# include Afterparty::QueueHelpers
|
5
|
+
|
6
|
+
validates_presence_of :job_dump, :execute_at, :queue
|
7
|
+
|
8
|
+
scope :incomplete, -> { where(completed: false).order("execute_at") }
|
9
|
+
scope :valid, -> { incomplete.where(execute_at: 10.years.ago..DateTime.now) }
|
10
|
+
scope :completed, -> { where(completed: true).order("execute_at desc") }
|
11
|
+
|
12
|
+
def self.make_with_job job, queue=:default
|
13
|
+
afterparty_job = AfterpartyJob.new
|
14
|
+
afterparty_job.job_dump = job.to_yaml
|
15
|
+
afterparty_job.execute_at = Afterparty.queue_time(job)
|
16
|
+
afterparty_job.queue = queue
|
17
|
+
afterparty_job.completed = false
|
18
|
+
afterparty_job.save
|
19
|
+
afterparty_job
|
20
|
+
end
|
21
|
+
|
22
|
+
def reify
|
23
|
+
Afterparty.load(job_dump)
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute
|
27
|
+
if (j = reify)
|
28
|
+
j.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'iconv'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Afterparty
|
5
|
+
class JobContainer
|
6
|
+
attr_accessor :job, :raw, :execute_at, :job_id, :queue_name
|
7
|
+
|
8
|
+
#intialized from redis's WITHSCORES function
|
9
|
+
def initialize _raw, timestamp
|
10
|
+
@execute_at = Time.at(timestamp)
|
11
|
+
begin
|
12
|
+
@job = Afterparty.load(_raw)
|
13
|
+
@job_id = job.afterparty_job_id if @job.respond_to? :afterparty_job_id
|
14
|
+
@queue_name = job.afterparty_queue if @job.respond_to? :afterparty_queue
|
15
|
+
rescue Exception => e
|
16
|
+
ap "Error during load: #{e.message}"
|
17
|
+
@job = nil
|
18
|
+
end
|
19
|
+
@raw = _raw
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def job_class
|
24
|
+
if @job
|
25
|
+
@job.class
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def raw_string
|
32
|
+
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
33
|
+
ic.iconv(@raw.dup + ' ')[0..-2]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Afterparty
|
2
|
+
class MailerJob
|
3
|
+
attr_accessor :execute_at, :mail, :clazz, :method, :args
|
4
|
+
def initialize clazz, method, *args
|
5
|
+
# @mail = UserMailer.welcome_email(User.find(1))
|
6
|
+
@clazz = UserMailer
|
7
|
+
@method = method
|
8
|
+
@args = args
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
@mail = @clazz.send @method, *@args
|
13
|
+
@mail.deliver
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
desc = "Mailer: #{(@clazz || "nil")}."
|
18
|
+
desc << "Method: #{(@method || nil)}."
|
19
|
+
desc << "Args: #{(@args || nil)}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class BasicJob
|
24
|
+
attr_accessor :object, :method, :args
|
25
|
+
def initialize object, method, *args
|
26
|
+
@object = object
|
27
|
+
@method = method
|
28
|
+
@args = args
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
@object.send(:method, *@args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def description
|
36
|
+
desc = "Object: #{(@object || "nil")}."
|
37
|
+
desc << "Method: #{(@method || nil)}."
|
38
|
+
desc << "Args: #{(@args || nil)}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Afterparty
|
2
|
+
class Queue
|
3
|
+
attr_accessor :options, :temp_namespace, :login_block
|
4
|
+
include Afterparty::QueueHelpers
|
5
|
+
|
6
|
+
def initialize options={}, consumer_options={}
|
7
|
+
# @consumer = ThreadedQueueConsumer.new(self, consumer_options).start
|
8
|
+
@options = options
|
9
|
+
@options[:namespace] ||= "default"
|
10
|
+
# Afterparty.add_queue @options[:namespace]
|
11
|
+
@options[:sleep] ||= 5
|
12
|
+
@mutex = Mutex.new
|
13
|
+
@options[:logger] ||= Logger.new($stderr)
|
14
|
+
end
|
15
|
+
|
16
|
+
def push job
|
17
|
+
# @mutex.synchronize do
|
18
|
+
return nil if job.nil?
|
19
|
+
queue_name = @temp_namespace || @options[:namespace]
|
20
|
+
AfterpartyJob.make_with_job job, queue_name
|
21
|
+
# end
|
22
|
+
end
|
23
|
+
alias :<< :push
|
24
|
+
alias :eng :push
|
25
|
+
|
26
|
+
def pop
|
27
|
+
# @mutex.synchronize do
|
28
|
+
while true do
|
29
|
+
unless (_job = AfterpartyJob.valid.first).nil?
|
30
|
+
ap "poppin job"
|
31
|
+
_job.completed = true
|
32
|
+
_job.save
|
33
|
+
return _job
|
34
|
+
end
|
35
|
+
sleep(@options[:sleep])
|
36
|
+
end
|
37
|
+
# end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TestQueue < Queue
|
42
|
+
attr_accessor :completed_jobs
|
43
|
+
|
44
|
+
def initialize opts={}, consumer_opts={}
|
45
|
+
super
|
46
|
+
@completed_jobs = []
|
47
|
+
@exceptions = []
|
48
|
+
end
|
49
|
+
def handle_exception job, exception
|
50
|
+
@exceptions << [job, exception]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -4,16 +4,18 @@ module Afterparty
|
|
4
4
|
@temp_namespace = namespace
|
5
5
|
end
|
6
6
|
|
7
|
-
def redis_queue_name
|
8
|
-
|
7
|
+
def redis_queue_name
|
8
|
+
puts (a = Afterparty.redis_queue_name(@temp_namespace || @options[:namespace]))
|
9
|
+
a
|
9
10
|
end
|
10
11
|
|
11
12
|
def clear
|
12
|
-
redis_call :del
|
13
|
+
# redis_call :del
|
14
|
+
AfterpartyJob.destroy_all
|
13
15
|
end
|
14
16
|
|
15
17
|
def redis_call command, *args
|
16
|
-
result = Afterparty.
|
18
|
+
result = Afterparty.redis_call (@temp_namespace || @options[:namespace]), command, *args
|
17
19
|
@temp_namespace = nil
|
18
20
|
result
|
19
21
|
end
|
@@ -23,37 +25,110 @@ module Afterparty
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def jobs
|
26
|
-
_jobs = redis_call(:zrange, 0, -1)
|
27
|
-
_jobs.each_with_index do |job, i|
|
28
|
-
|
29
|
-
end
|
30
|
-
_jobs
|
28
|
+
# _jobs = redis_call(:zrange, 0, -1)
|
29
|
+
# _jobs.each_with_index do |job, i|
|
30
|
+
# _jobs[i] = Marshal.load(job)
|
31
|
+
# end
|
32
|
+
# _jobs
|
33
|
+
AfterpartyJob.incomplete
|
31
34
|
end
|
32
35
|
|
33
36
|
def jobs_with_scores
|
34
|
-
redis_call
|
37
|
+
hash_from_scores(redis_call(:zrange, 0, -1, {withscores: true}))
|
35
38
|
end
|
36
39
|
|
37
40
|
def valid_jobs
|
38
|
-
redis_call :zrangebyscore, 0, Time.now.to_i
|
41
|
+
# redis_call :zrangebyscore, 0, Time.now.to_i
|
42
|
+
AfterpartyJob.valid
|
43
|
+
end
|
44
|
+
|
45
|
+
def next_valid_job
|
46
|
+
# valid_jobs.first
|
47
|
+
AfterpartyJob.valid.first
|
39
48
|
end
|
40
49
|
|
41
50
|
def jobs_empty?
|
42
|
-
count = total_jobs_count
|
43
|
-
# ap count
|
44
|
-
count == 0
|
51
|
+
# count = total_jobs_count
|
52
|
+
# # ap count
|
53
|
+
# count == 0
|
54
|
+
AfterpartyJob.valid.empty?
|
45
55
|
end
|
46
56
|
|
47
57
|
def total_jobs_count
|
48
|
-
redis_call(:zcount, "-inf", "+inf")
|
58
|
+
# redis_call(:zcount, "-inf", "+inf")
|
59
|
+
AfterpartyJob.incomplete.count
|
49
60
|
end
|
50
61
|
|
51
62
|
def redis
|
52
63
|
@@redis
|
53
64
|
end
|
54
65
|
|
66
|
+
def last_completed
|
67
|
+
# @temp_namespace = "completed"
|
68
|
+
# redis_call(:zrange, -1, -1).first
|
69
|
+
AfterpartyJob.completed.first
|
70
|
+
end
|
71
|
+
|
72
|
+
def completed
|
73
|
+
# @temp_namespace = "completed"
|
74
|
+
# redis_call(:zrange, -20, -1).reverse
|
75
|
+
AfterpartyJob.completed
|
76
|
+
end
|
77
|
+
|
78
|
+
def completed_with_scores
|
79
|
+
@temp_namespace = "completed"
|
80
|
+
hash_from_scores(redis_call(:zrange, -20, -1, withscores: true)).reverse
|
81
|
+
end
|
82
|
+
|
83
|
+
def run(job)
|
84
|
+
real_job = job.reify
|
85
|
+
if real_job
|
86
|
+
job.execute
|
87
|
+
else
|
88
|
+
job.has_error = true
|
89
|
+
job.error_message = "Error marshaling job."
|
90
|
+
end
|
91
|
+
job.completed = true
|
92
|
+
job.completed_at = DateTime.now
|
93
|
+
job.save
|
94
|
+
rescue Exception => exception
|
95
|
+
handle_exception job, exception
|
96
|
+
end
|
97
|
+
|
98
|
+
def handle_exception(job, exception)
|
99
|
+
job.completed = true
|
100
|
+
job.completed_at = DateTime.now
|
101
|
+
job.has_error = true
|
102
|
+
job.error_message = exception.message
|
103
|
+
job.error_backtrace = exception.backtrace.join("\n")
|
104
|
+
job.save
|
105
|
+
logger_message = "Job Error: #{job.inspect}\n#{exception.message}"
|
106
|
+
logger_message << "\n#{exception.backtrace.join("\n")}"
|
107
|
+
@options[:logger].error logger_message
|
108
|
+
end
|
109
|
+
|
110
|
+
# &block takes a 'username' and 'password'
|
111
|
+
# argument. return true or false
|
112
|
+
def config_login &block
|
113
|
+
@login_block = block
|
114
|
+
end
|
115
|
+
|
116
|
+
def authenticate username, password
|
117
|
+
raise 'Must set queue.config_login to use dashboard' if @login_block.nil?
|
118
|
+
@login_block.call(username, password)
|
119
|
+
end
|
120
|
+
|
121
|
+
|
55
122
|
private
|
56
123
|
|
124
|
+
def hash_from_scores raw
|
125
|
+
arr = []
|
126
|
+
raw.each do |group|
|
127
|
+
arr << Afterparty::JobContainer.new(group[0], group[1])
|
128
|
+
end
|
129
|
+
arr
|
130
|
+
end
|
131
|
+
|
57
132
|
# returns true if job has an :execute_at value
|
58
133
|
def job_valid? job
|
59
134
|
job.respond_to?(:execute_at) && !job.execute_at.nil?
|
data/lib/afterparty/version.rb
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Afterparty
|
2
|
+
class Worker
|
3
|
+
include QueueHelpers
|
4
|
+
|
5
|
+
def initialize options = {}
|
6
|
+
@options = options
|
7
|
+
@options[:adapter] ||= :redis
|
8
|
+
@options[:namespace] ||= :default
|
9
|
+
@options[:sleep] ||= 10
|
10
|
+
@options[:logger] ||= Logger.new($stderr)
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def consume
|
15
|
+
@stopped = false
|
16
|
+
# puts "starting worker with namespace [#{@options[:namespace]}]."
|
17
|
+
@thread = Thread.new {
|
18
|
+
consume_sync
|
19
|
+
}
|
20
|
+
@thread
|
21
|
+
end
|
22
|
+
|
23
|
+
def consume_next
|
24
|
+
if (job = next_valid_job)
|
25
|
+
run job
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def consume_sync
|
30
|
+
while !@stopped
|
31
|
+
job = next_valid_job
|
32
|
+
if job
|
33
|
+
puts "Executing job: #{job.id}"
|
34
|
+
run job
|
35
|
+
else
|
36
|
+
sleep(@options[:sleep])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop
|
42
|
+
@stopped = true
|
43
|
+
@thread.join(0) if @thread
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
class AfterpartyGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def install
|
6
|
+
copy_file "jobs_migration.rb", "db/migrate/#{Time.now.strftime('%Y%m%d%H%M%S')}_create_afterparty_jobs.rb"
|
7
|
+
copy_file "initializer.rb", "config/initializers/afterparty.rb"
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateAfterpartyJobs < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :afterparty_jobs do |t|
|
4
|
+
t.text :job_dump
|
5
|
+
t.string :queue
|
6
|
+
t.datetime :execute_at
|
7
|
+
t.boolean :completed
|
8
|
+
t.boolean :has_error
|
9
|
+
t.text :error_message
|
10
|
+
t.text :error_backtrace
|
11
|
+
t.datetime :completed_at
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.down
|
18
|
+
drop_table :afterparty_jobs
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
namespace :jobs do
|
2
|
+
require 'mail'
|
3
|
+
|
4
|
+
desc "Start a new worker"
|
5
|
+
task work: :environment do
|
6
|
+
worker = Afterparty::Worker.new
|
7
|
+
worker.consume_sync
|
8
|
+
end
|
9
|
+
|
10
|
+
# desc "Clear all jobs"
|
11
|
+
# task clear: :environment do
|
12
|
+
# Rails.configuration.queue.clear
|
13
|
+
# end
|
14
|
+
|
15
|
+
desc "List Jobs"
|
16
|
+
task list: :environment do
|
17
|
+
jobs = Rails.configuration.queue.jobs
|
18
|
+
puts "#{jobs.size} total jobs."
|
19
|
+
jobs.each do |time, job|
|
20
|
+
puts time
|
21
|
+
puts job
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe AfterpartyJob do
|
3
|
+
before :each do
|
4
|
+
AfterpartyJob.destroy_all
|
5
|
+
end
|
6
|
+
|
7
|
+
it "makes a job correctly" do
|
8
|
+
tester = test_job
|
9
|
+
tester.execute_at = Time.now + 10
|
10
|
+
job = AfterpartyJob.make_with_job tester
|
11
|
+
job.reload
|
12
|
+
(reloaded = job.reify).class.should == tester.class
|
13
|
+
reloaded.execute_at.should == tester.execute_at
|
14
|
+
job.execute_at.should == reloaded.execute_at
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|