jobit 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
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
+ log
19
+ .idea
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.0.1
2
+
3
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jobit.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Victor Yunevich
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 PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL 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.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ nothing yet
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,19 @@
1
+ module Jobit
2
+ class JobsController < ActionController::Base
3
+
4
+ def index
5
+ job = Jobit::Job.find_by_name(params[:id])
6
+
7
+ if job.nil?
8
+ json_response = {:status => 'not_found'}
9
+ else
10
+ json_response = job.session
11
+ if job.status == 'complete'
12
+ job.destroy if job.status == "complete"
13
+ end
14
+ end
15
+
16
+ render :json => json_response
17
+ end
18
+ end
19
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Jobit::Engine.routes.draw do
2
+ get '/(:id)', :to => 'jobs#index'
3
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/lib/jobit'
2
+
data/jobit.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/jobit/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Victor Yunevich"]
6
+ gem.email = ["v.t.g.m.b.x@gmail.com"]
7
+ gem.description = %q{Process background jobs in queue.}
8
+ gem.summary = %q{Background jobs processing}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "jobit"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Jobit::VERSION
17
+
18
+ gem.add_dependency "daemons"
19
+ gem.add_development_dependency "rspec"
20
+ end
data/lib/jobit.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "jobit/version"
2
+ require File.dirname(__FILE__) + '/jobit/storage'
3
+ require File.dirname(__FILE__) + '/jobit/jobby'
4
+ require File.dirname(__FILE__) + '/jobit/job'
5
+ require File.dirname(__FILE__) + '/jobit/worker'
6
+ require File.dirname(__FILE__) + '/jobit/engine' if defined?(Rails)
7
+ require File.dirname(__FILE__) + '/jobit/railtie' if defined?(Rails)
8
+
@@ -0,0 +1,88 @@
1
+ require 'rubygems'
2
+ require 'daemons'
3
+ require 'optparse'
4
+
5
+ module Jobit
6
+ class Command
7
+ attr_accessor :worker_count
8
+
9
+ def initialize(args)
10
+ @files_to_reopen = []
11
+ @options = {
12
+ :quiet => true,
13
+ :pid_dir => "#{Rails.root}/tmp/pids"
14
+ }
15
+
16
+ @worker_count = 1
17
+ @monitor = false
18
+
19
+ opts = OptionParser.new do |opts|
20
+ opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
21
+
22
+ opts.on('-h', '--help', 'Show this message') do
23
+ puts opts
24
+ exit 1
25
+ end
26
+ opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
27
+ @worker_count = worker_count.to_i rescue 1
28
+ end
29
+ end
30
+ @args = opts.parse!(args)
31
+ end
32
+
33
+ def daemonize
34
+ #Jobit::Worker.backend.before_fork
35
+
36
+ ObjectSpace.each_object(File) do |file|
37
+ @files_to_reopen << file unless file.closed?
38
+ end
39
+
40
+ dir = @options[:pid_dir]
41
+ Dir.mkdir(dir) unless File.exists?(dir)
42
+
43
+ if @worker_count > 1 && @options[:identifier]
44
+ raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
45
+ elsif @worker_count == 1 && @options[:identifier]
46
+ process_name = "jobit.#{@options[:identifier]}"
47
+ run_process(process_name, dir)
48
+ else
49
+ worker_count.times do |worker_index|
50
+ process_name = worker_count == 1 ? "jobit" : "jobit.#{worker_index}"
51
+ run_process(process_name, dir)
52
+ end
53
+ end
54
+ end
55
+
56
+ def run_process(process_name, dir)
57
+ Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*args|
58
+ $0 = File.join(@options[:prefix], process_name) if @options[:prefix]
59
+ run process_name
60
+ end
61
+ end
62
+
63
+ def run(worker_name = nil)
64
+ Dir.chdir(Rails.root)
65
+
66
+ # Re-open file handles
67
+ @files_to_reopen.each do |file|
68
+ begin
69
+ file.reopen file.path, "a+"
70
+ file.sync = true
71
+ rescue ::Exception
72
+ end
73
+ end
74
+
75
+ #Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
76
+ #Delayed::Worker.backend.after_fork
77
+
78
+ worker = Jobit::Worker.new(@options)
79
+ #worker.name_prefix = "#{worker_name} "
80
+ worker.start
81
+ rescue => e
82
+ Rails.logger.fatal e
83
+ STDERR.puts e.message
84
+ exit 1
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,5 @@
1
+ module Jobit
2
+ class Engine < Rails::Engine
3
+ isolate_namespace Jobit
4
+ end
5
+ end
data/lib/jobit/job.rb ADDED
@@ -0,0 +1,102 @@
1
+ require 'logger'
2
+ require 'benchmark'
3
+
4
+ module Jobit
5
+
6
+ class Job
7
+
8
+ def self.jobs_path
9
+ if defined?(Rails)
10
+ "#{Rails.root.to_s}/tmp/jobit"
11
+ else
12
+ File.dirname(__FILE__) + "/../../tmp/jobit"
13
+ end
14
+ end
15
+
16
+ def self.worker_name
17
+ "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
18
+ end
19
+
20
+ def self.destroy_all
21
+ Storage.destroy_all
22
+ end
23
+
24
+ def self.destroy_failed
25
+ failed_jobs = Storage.where({:status => 'failed'})
26
+ for job in failed_jobs
27
+ job.destroy
28
+ end
29
+ true
30
+ end
31
+
32
+ def self.logger
33
+ if defined?(Rails)
34
+ Logger.new("#{Rails.root.to_s}/log/jobit.log", shift_age = 7, shift_size = 1048576)
35
+ else
36
+ Logger.new(File.dirname(__FILE__) + "/../../log/jobit.log")
37
+ end
38
+ end
39
+
40
+ # Jobit::Job.all
41
+ # returns array of all jobs
42
+ def self.all
43
+ Storage.all
44
+ end
45
+
46
+ # Jobit::Job.find(11111.111)
47
+ # returns job or nil
48
+ def self.find(id)
49
+ Storage.find(id)
50
+ end
51
+
52
+ # Jobit::Job.find_by_name('name')
53
+ # returns job or nil
54
+ def self.find_by_name(name)
55
+ Storage.find_by_name(name)
56
+ end
57
+
58
+ # Jobit::Job.where({:name => 'name'})
59
+ # returns array of found jobs
60
+ def self.where(search)
61
+ Storage.where(search)
62
+ end
63
+
64
+ # Jobit::Job.add(name, object, *args) {{options}}
65
+ # options:
66
+ # :priority => the job priority (Integer)
67
+ # :run_after => run job after some time from now ex: :run_after => 4.hours
68
+ # :schedule => run job at some time. ex: :schedule_at => "16:00"
69
+ # :repeat => how many times to repeat the job (Integer)
70
+ # :repeat_delay => delay in seconds before next repeat
71
+ def self.add(name, object, *args, &block)
72
+ unless JobitItems.method_defined?(object)
73
+ raise ArgumentError, "Can't add job #{object}. It's not defined in jobs."
74
+ end
75
+
76
+ options = {}
77
+ options = yield if block_given?
78
+
79
+ options[:name] = name
80
+ options[:object] = object
81
+ options[:args] = Marshal.dump(args)
82
+
83
+ Jobby.new(nil, options)
84
+ end
85
+
86
+ def self.work_off(num = 100)
87
+ complete, failed, reissued = 0, 0, 0
88
+ num.times do
89
+ jobs = self.where({:status => 'new'})
90
+ break unless jobs.size > 0
91
+ res = jobs.first.run_job
92
+ complete += res[0]
93
+ failed += res[1]
94
+ reissued += res[2]
95
+ break if $exit
96
+ end
97
+ [complete, failed, reissued]
98
+ end
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,152 @@
1
+ require File.dirname(__FILE__) + '/jobs/statuses'
2
+ require File.dirname(__FILE__) + '/jobs/commands'
3
+
4
+ module Jobit
5
+ class Jobby < Struct.new(
6
+ :id, :name, :object, :args, :message, :progress, :error, :priority, :run_at,
7
+ :schedule, :repeat, :repeat_delay, :created_at, :started_at, :stopped_at,
8
+ :locked_by, :locked_at, :tries, :status, :keep, :failed_at
9
+ )
10
+
11
+ MAX_ATTEMPTS = 25
12
+ MAX_RUN_TIME = 4 # hours
13
+
14
+ include Jobit::Jobs::Statuses
15
+ include Jobit::Jobs::Commands
16
+
17
+ def initialize(job = nil, options = {})
18
+ if job.nil?
19
+ create_new
20
+ if options
21
+ for key, val in options
22
+ self[key] = val
23
+ end
24
+ end
25
+ self.id = "#{priority}.#{id}"
26
+ Jobit::Storage.create(id, self)
27
+ else
28
+ job.each_with_index do |val, index|
29
+ self[index] = val
30
+ end
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ return unless id
36
+ Jobit::Storage.destroy(id)
37
+ clear
38
+ end
39
+
40
+ def process_job(args)
41
+ begin
42
+ logger.info "* [JOB] aquiring lock on #{name}"
43
+ runtime = Benchmark.realtime do
44
+ set_status 'running'
45
+ set_start_time
46
+ self.send(object,*args)
47
+ set_status 'complete'
48
+ set_stop_time
49
+ end
50
+ logger.info "* [JOB] #{name} completed after %.4f" % runtime
51
+ rescue Exception => e
52
+ log_exception(e)
53
+ msg = "#{e.message}\n\n#{e.backtrace.join("\n")}"
54
+ set_error(msg)
55
+ end
56
+ end
57
+
58
+ def run_job(worker_name = 'worker')
59
+ return nil if locked?
60
+ lock!(worker_name)
61
+ self.class.send(:include, JobitItems)
62
+ job_args = Marshal.restore(args)
63
+ num = repeat.to_i == 0 ? 1 : repeat.to_i
64
+ delay = repeat_delay.to_i
65
+ num.times do
66
+ process_job(job_args)
67
+ break if failed? # stop loop if job failed
68
+ increase_tries
69
+ sleep delay if delay > 0
70
+ end
71
+ #p self.class.send(:method_defined?, :perform)
72
+ #p self.class.send(:undef_method, :perform)
73
+ cleanup
74
+ end
75
+
76
+
77
+ def log_exception(error)
78
+ logger.error "* [JOB:#{name}] failed with #{error.class.name}: #{error.message} - #{tries} failed attempts"
79
+ logger.error(error)
80
+ end
81
+
82
+ private
83
+
84
+ def logger
85
+ @logger ||= Jobit::Job.logger
86
+ end
87
+
88
+ def create_new
89
+ self.id = Time.now.to_f
90
+ self.name = name
91
+ self.message = ''
92
+ self.progress = 0
93
+ self.priority = 0
94
+ self.run_at = nil
95
+ self.repeat = 0
96
+ self.repeat_delay = 0
97
+ self.created_at = id
98
+ self.tries = 0
99
+ self.status = 'new'
100
+ self.keep = false
101
+ self
102
+ end
103
+
104
+ def reload
105
+ reloaded_job = Storage.find(id)
106
+ reloaded_job.each_with_index do |val, index|
107
+ self[index] = val
108
+ end
109
+ end
110
+
111
+ def update
112
+ Jobit::Storage.update(id, self)
113
+ end
114
+
115
+ def cleanup
116
+ complete, failed, reissued = 0, 0, 0
117
+ if failed?
118
+ if tries < MAX_ATTEMPTS
119
+ self.tries += 1
120
+ self.run_at = Time.now + (tries ** 4) + 5
121
+ self.status = 'new'
122
+ unlock!
123
+ update
124
+ reissued = 1
125
+ else
126
+ logger.info "* [JOB:#{name}] Removing... Too many tries."
127
+ destroy unless keep?
128
+ failed = 1
129
+ end
130
+ elsif complete?
131
+ if keep?
132
+ set_progress(100)
133
+ set_stop_time
134
+ unlock!
135
+ else
136
+ destroy
137
+ end
138
+ complete = 1
139
+ end
140
+ [complete, failed, reissued]
141
+ end
142
+
143
+ def clear
144
+ options_size = self.size-1
145
+
146
+ for i in (0..options_size)
147
+ self[i]= nil
148
+ end
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,64 @@
1
+ module Jobit
2
+ module Jobs
3
+ module Commands
4
+ def set_start_time
5
+ self.started_at = Time.now.to_f
6
+ update
7
+ end
8
+
9
+ def set_stop_time
10
+ self.stopped_at = Time.now.to_f
11
+ update
12
+ end
13
+
14
+ def set_status(new_status)
15
+ self.status = new_status
16
+ update
17
+ end
18
+
19
+ def set_error(text)
20
+ self.error = text
21
+ self.status = 'failed'
22
+ self.stopped_at = Time.now.to_f
23
+ self.failed_at = stopped_at
24
+ update
25
+ end
26
+
27
+ def set_tries(num)
28
+ self.tries = num
29
+ update
30
+ end
31
+
32
+ def increase_tries
33
+ self.tries += 1
34
+ update
35
+ end
36
+
37
+ def set_progress(num)
38
+ self.progress = num
39
+ update
40
+ end
41
+
42
+ def add_message(text, force = false)
43
+ if self.keep? || force
44
+ self.message += text
45
+ update
46
+ end
47
+ end
48
+
49
+ def unlock!
50
+ self.locked_at= nil
51
+ self.locked_by= nil
52
+ update
53
+ end
54
+
55
+ def lock!(worker_name)
56
+ self.run_at= Time.now.to_f
57
+ self.locked_at= Time.now.to_f
58
+ self.locked_by= worker_name
59
+ update
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,53 @@
1
+ module Jobit
2
+ module Jobs
3
+ module Statuses
4
+ def running?
5
+ status == 'running'
6
+ end
7
+
8
+ def failed?
9
+ status == 'failed'
10
+ end
11
+
12
+ def complete?
13
+ status == 'complete'
14
+ end
15
+
16
+ def stopped?
17
+ status == 'stopped'
18
+ end
19
+
20
+ def new?
21
+ status == 'new'
22
+ end
23
+
24
+ def keep?
25
+ keep
26
+ end
27
+
28
+ def locked?
29
+ locked_at != nil
30
+ end
31
+
32
+ def current_job
33
+ self
34
+ end
35
+
36
+ def session
37
+ {
38
+ :name => name,
39
+ :message => message,
40
+ :progress => progress,
41
+ :error => error,
42
+ :run_at => run_at,
43
+ :created_at => created_at,
44
+ :started_at => started_at,
45
+ :stopped_at =>stopped_at,
46
+ :tries => tries,
47
+ :status => status,
48
+ :failed_at => failed_at
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ require 'jobit'
2
+ require 'rails'
3
+ module Jobit
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :jobit
6
+
7
+ rake_tasks do
8
+ load "tasks/jobit.rake"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+ module Jobit
3
+ class Storage
4
+
5
+ def self.all_files
6
+ Dir.mkdir(Jobit::Job.jobs_path) unless Dir.exist?(Jobit::Job.jobs_path)
7
+ Dir.glob("#{Jobit::Job.jobs_path}/*").find_all { |x| File.file? x }
8
+ end
9
+
10
+ def self.destroy_all
11
+ for file_name in self.all_files
12
+ File.delete(file_name)
13
+ end
14
+ end
15
+
16
+ def self.all
17
+ result = []
18
+
19
+ for file_name in self.all_files
20
+ obj = get_data_from_file(file_name)
21
+ result << Jobby.new(obj)
22
+ end
23
+
24
+ result
25
+ end
26
+
27
+ def self.find(id)
28
+ file_name = self.make_file_name(id)
29
+ return nil unless File.exist?(file_name)
30
+
31
+ obj = get_data_from_file(file_name)
32
+ Jobby.new(obj)
33
+ end
34
+
35
+
36
+ def self.find_by_name(name)
37
+
38
+ result = nil
39
+
40
+ for file_name in self.all_files
41
+ obj = get_data_from_file(file_name)
42
+ next if obj.nil?
43
+ if obj.name == name
44
+ result = Jobby.new(obj)
45
+ break
46
+ end
47
+ end
48
+
49
+ result
50
+ end
51
+
52
+ def self.where(search)
53
+ search_hash = {:name => search}
54
+ search_hash = search if search.is_a?(Hash)
55
+
56
+ result = []
57
+
58
+ for file_name in self.all_files
59
+ obj = get_data_from_file(file_name)
60
+ for key, val in search_hash
61
+ next unless obj.respond_to?(key)
62
+ result << Jobby.new(obj) if obj[key] == val
63
+ end
64
+ end
65
+
66
+ result
67
+ end
68
+
69
+ def self.destroy(file)
70
+ file_name = self.make_file_name(file)
71
+ return false unless File.exist?(file_name)
72
+ File.delete(file_name)
73
+ end
74
+
75
+ def self.create(file, content)
76
+ Dir.mkdir(Jobit::Job.jobs_path) unless Dir.exist?(Jobit::Job.jobs_path)
77
+ file_name = self.make_file_name(file)
78
+ data = Marshal.dump(content.to_hash)
79
+ File.open(file_name, 'w+b') { |f| f.write(data) }
80
+ true
81
+ end
82
+
83
+
84
+ def self.update(file, content)
85
+ file_name = self.make_file_name(file)
86
+ return false unless File.exist?(file_name)
87
+ File.open(file_name, 'w+b') { |f| f.write(Marshal.dump(content.to_hash)) }
88
+ true
89
+ end
90
+
91
+ private
92
+
93
+ def self.get_data_from_file(file_name)
94
+ begin
95
+ file = File.open(file_name)
96
+ obj = Marshal.load(file).to_struct
97
+ rescue
98
+ return nil
99
+ ensure
100
+ file.close
101
+ end
102
+ obj
103
+ end
104
+
105
+ def self.make_file_name(file)
106
+ File.join(Jobit::Job.jobs_path, file.to_s)
107
+ end
108
+
109
+
110
+ end
111
+ end
112
+
113
+ class Hash
114
+ def to_struct
115
+ Struct.new(*keys).new(*values)
116
+ end
117
+ end
118
+
119
+ class Struct
120
+ def to_hash
121
+ Hash[*members.zip(values).flatten]
122
+ end
123
+ end
@@ -0,0 +1,3 @@
1
+ module Jobit
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,49 @@
1
+ module Jobit
2
+ class Worker
3
+ SLEEP = 5
4
+
5
+ def logger
6
+ @logger ||= Jobit::Job.logger
7
+ end
8
+
9
+ def initialize(options={})
10
+ @quiet = options[:quiet]
11
+ #Delayed::Job.min_priority = options[:min_priority] if options.has_key?(:min_priority)
12
+ #Delayed::Job.max_priority = options[:max_priority] if options.has_key?(:max_priority)
13
+ end
14
+
15
+ def start
16
+ #say "*** Starting job worker #{Jobit::Job.worker_name}"
17
+ say "*** Starting job worker #{Jobit::Job.worker_name}"
18
+
19
+ trap('TERM') { say 'Exiting...'; $exit = true }
20
+ trap('INT') { say 'Exiting...'; $exit = true }
21
+
22
+ loop do
23
+ result = nil
24
+
25
+ realtime = Benchmark.realtime do
26
+ result = Jobit::Job.work_off
27
+ end
28
+
29
+ count = result.sum
30
+
31
+ break if $exit
32
+
33
+ if count.zero?
34
+ sleep(SLEEP)
35
+ else
36
+ say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result[1]]
37
+ end
38
+
39
+ break if $exit
40
+ end
41
+ end
42
+
43
+ def say(text)
44
+ puts text unless @quiet
45
+ logger.info text if logger
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'tasks')
@@ -0,0 +1,20 @@
1
+ # Re-definitions are appended to existing tasks
2
+ task :environment
3
+
4
+ namespace :jobit do
5
+ desc "Clear the Jobit job queue."
6
+ task :clear => :environment do
7
+ Jobit::Job.destroy_all
8
+ end
9
+
10
+ desc "Clear the Jobit filed jobs."
11
+ task :clear_failed => :environment do
12
+ Jobit::Job.destroy_failed
13
+ end
14
+
15
+ desc "Start a Jobit worker."
16
+ task :work => :environment do
17
+ p Rails.root
18
+ Jobit::Worker.new().start
19
+ end
20
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ describe Jobit::Storage do
5
+
6
+ def struct
7
+ struct = Struct.new(:id, :name)
8
+ struct.new(@file_name, @job_name)
9
+ end
10
+
11
+ before(:each) do
12
+ @file_name = Time.now.to_f
13
+ @job_name = "job#1"
14
+ @total_files = Jobit::Storage.all_files.size
15
+ Jobit::Storage.create(@file_name, struct).should eq(true)
16
+ end
17
+
18
+ after(:each) do
19
+ Jobit::Storage.destroy(@file_name)
20
+ end
21
+
22
+ after(:all) do
23
+ begin
24
+ Dir.delete(Jobit.jobs_path)
25
+ rescue
26
+ nil
27
+ end
28
+ end
29
+
30
+ it "creates file with struct id equal to filename" do
31
+ job = Jobit::Storage.find(@file_name)
32
+ job.should be_a(Struct)
33
+ job.id.should eq(@file_name)
34
+ end
35
+
36
+ it ".find returns file value" do
37
+ job = Jobit::Storage.find(@file_name)
38
+ job.should be_a(Struct)
39
+ job.name.should eq(@job_name)
40
+ end
41
+
42
+ it ".all retrieves list of jobs" do
43
+ jobs = Jobit::Storage.all
44
+ jobs.should be_a(Array)
45
+ jobs.size.should eq(@total_files+1)
46
+ end
47
+
48
+
49
+ it ".find_by_name returns file value" do
50
+ job = Jobit::Storage.find_by_name(@job_name)
51
+ job.should be_a(Struct)
52
+ job.name.should eq(@job_name)
53
+ end
54
+
55
+ it ".find_by_name with wrong name returns nil" do
56
+ job = Jobit::Storage.find_by_name('wrong_name')
57
+ job.should eq(nil)
58
+ end
59
+
60
+ it ".where(name) returns file value" do
61
+ jobs = Jobit::Storage.where(@job_name)
62
+ jobs.should be_a(Array)
63
+ jobs.first.name.should eq(@job_name)
64
+ end
65
+
66
+ it ".where(hash) returns file value" do
67
+ jobs = Jobit::Storage.where({:name => @job_name})
68
+ jobs.should be_a(Array)
69
+ jobs.first.name.should eq(@job_name)
70
+ end
71
+
72
+ it ".where(wrong hash key) returns empty array" do
73
+ jobs = Jobit::Storage.where({:name1 => @job_name})
74
+ jobs.should be_a(Array)
75
+ jobs.size.should eq(0)
76
+ end
77
+
78
+ it ".where(wrong hash value) returns empty array" do
79
+ jobs = Jobit::Storage.where({:name => 'wrong_name'})
80
+ jobs.should be_a(Array)
81
+ jobs.size.should eq(0)
82
+ end
83
+
84
+ it "find by name and update" do
85
+ job = Jobit::Storage.find_by_name(@job_name)
86
+ job.name = "new name"
87
+ Jobit::Storage.update(job.id, job)
88
+ job = Jobit::Storage.find(job.id)
89
+ job.should be_a(Struct)
90
+ job.name.should eq('new name')
91
+ end
92
+
93
+ end
@@ -0,0 +1,236 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ module JobitItems
5
+
6
+ def good_job(opt1, opt2)
7
+ set_progress(10)
8
+ unless opt1 == 'val1'
9
+ raise(NoMethodError, 'wrong arguments 0')
10
+ end
11
+ unless opt2 == 'val2'
12
+ raise(NoMethodError, 'wrong arguments 1')
13
+ end
14
+ set_progress(100)
15
+ end
16
+
17
+ def bad_job(text)
18
+ add_message "start"
19
+ set_progress(10)
20
+ add_message "end"
21
+ raise(NoMethodError, "Some dumb error #{text}")
22
+ end
23
+ end
24
+
25
+ describe Jobit do
26
+ describe "Job" do
27
+
28
+ before(:each) do
29
+ @job_name = 'job-1'
30
+ @total_jobs = Jobit::Job.all.size
31
+ @new_job = Jobit::Job.add(@job_name, :good_job, 'val1', 'val2') {
32
+ {:priority => 1}
33
+ }
34
+ end
35
+
36
+ after(:each) do
37
+ @new_job.destroy
38
+ end
39
+
40
+ after(:all) do
41
+ begin
42
+ Dir.delete(Jobit::JOBS_PATH)
43
+ rescue
44
+ nil
45
+ end
46
+ end
47
+
48
+ it "creates new job" do
49
+ @new_job.name.should eq(@job_name)
50
+ end
51
+
52
+ it 'returns nil if not found' do
53
+ job = Jobit::Job.find('123')
54
+ job.should eq(nil)
55
+ end
56
+
57
+ it 'returns job if found by id' do
58
+ job = Jobit::Job.find(@new_job.id)
59
+ job.name.should eq(@job_name)
60
+ end
61
+
62
+ it 'returns job if found_by_name' do
63
+ job = Jobit::Job.find_by_name(@job_name)
64
+ job.name.should eq(@job_name)
65
+ end
66
+
67
+ it 'returns array of all jobs' do
68
+ jobs = Jobit::Job.all
69
+ jobs.should be_a(Array)
70
+ jobs.size.should eq(@total_jobs+1)
71
+ jobs.first.name.should be_a(String)
72
+ end
73
+
74
+ it 'returns array of found jobs' do
75
+ jobs = Jobit::Job.where(:name => @job_name)
76
+ jobs.should be_a(Array)
77
+ jobs.size.should eq(1)
78
+ jobs.first.name.should eq(@job_name)
79
+ end
80
+
81
+ it 'returns empty array if wrong key' do
82
+ jobs = Jobit::Job.where(:name1 => @job_name)
83
+ jobs.should be_a(Array)
84
+ jobs.size.should eq(0)
85
+ end
86
+
87
+ it 'returns empty array if wrong value' do
88
+ jobs = Jobit::Job.where(:name => 'wrong name')
89
+ jobs.should be_a(Array)
90
+ jobs.size.should eq(0)
91
+ end
92
+
93
+ it 'destroys job' do
94
+ @new_job.destroy
95
+ job = Jobit::Job.find_by_name(@job_name)
96
+ job.should eq(nil)
97
+ end
98
+
99
+ it 'update status' do
100
+ @new_job.status.should eq('new')
101
+ @new_job.set_status 'running'
102
+ @new_job.status.should eq('running')
103
+ job = Jobit::Job.find_by_name(@job_name)
104
+ job.status.should eq('running')
105
+ end
106
+
107
+ it 'update error' do
108
+ @new_job.error.should eq(nil)
109
+ @new_job.set_error 'my error'
110
+ @new_job.error.should eq('my error')
111
+ job = Jobit::Job.find_by_name(@job_name)
112
+ job.error.should eq('my error')
113
+ end
114
+
115
+ it 'update error' do
116
+ @new_job.tries.should eq(0)
117
+ @new_job.set_tries 1
118
+ @new_job.tries.should eq(1)
119
+ job = Jobit::Job.find_by_name(@job_name)
120
+ job.tries.should eq(1)
121
+ end
122
+
123
+ it 'update progress' do
124
+ @new_job.progress.should eq(0)
125
+ @new_job.set_progress 10
126
+ @new_job.progress.should eq(10)
127
+ job = Jobit::Job.find_by_name(@job_name)
128
+ job.progress.should eq(10)
129
+ end
130
+
131
+ it 'adds message' do
132
+ @new_job.message.should eq('')
133
+ @new_job.add_message('hello',true)
134
+ @new_job.message.should eq('hello')
135
+ job = Jobit::Job.find_by_name(@job_name)
136
+ job.message.should eq('hello')
137
+ @new_job.add_message(' there',true)
138
+ @new_job.message.should eq('hello there')
139
+ job = Jobit::Job.find_by_name(@job_name)
140
+ job.message.should eq('hello there')
141
+ end
142
+
143
+ it 'running good job' do
144
+ @new_job.run_job
145
+ job = Jobit::Job.find_by_name(@job_name)
146
+ job.should eq(nil)
147
+ end
148
+
149
+ it 'running good job 5 times' do
150
+ job = Jobit::Job.add('good_job', :good_job, 'val1', 'val2') { {
151
+ :priority => 0,
152
+ :repeat => 5
153
+ } }
154
+ job.keep?.should eq(false)
155
+ job.run_job
156
+ job = Jobit::Job.find_by_name('good_job')
157
+ job.should eq(nil)
158
+ end
159
+
160
+ it 'running good job 5 times and keep file' do
161
+ job = Jobit::Job.add('good_job_keep', :good_job, 'val1', 'val2') { {
162
+ :priority => 0,
163
+ :repeat => 5,
164
+ :keep => true
165
+ } }
166
+ job.run_job
167
+ job = Jobit::Job.find_by_name('good_job_keep')
168
+ job.tries.should eq(5)
169
+ job.run_at.should_not eq(nil)
170
+ job.destroy
171
+ end
172
+
173
+ it 'running bad job' do
174
+ job = Jobit::Job.add('bad_job', :bad_job, 'val1') {
175
+ {
176
+ :priority => 0,
177
+ :keep => true
178
+ }
179
+ }
180
+
181
+ job.run_job
182
+ job = Jobit::Job.find_by_name('bad_job')
183
+ job.tries.should eq(1)
184
+ job.message.should eq('startend')
185
+ job.status.should eq('new')
186
+ job.progress.should eq(10)
187
+ job.destroy
188
+ end
189
+
190
+ it 'running bad job 5 times should run it just once' do
191
+ job = Jobit::Job.add('bad_job_5', :bad_job, 'val1') {
192
+ {
193
+ :priority => 0,
194
+ :repeat => 5
195
+ }
196
+ }
197
+
198
+ job.run_job
199
+ job = Jobit::Job.find_by_name('bad_job_5')
200
+ job.tries.should eq(1)
201
+ job.status.should eq('new')
202
+ job.progress.should eq(10)
203
+ job.destroy
204
+ end
205
+
206
+ it 'work_off test' do
207
+ @new_job.destroy
208
+ for i in (0..5)
209
+ Jobit::Job.add("work_off_#{i}", :good_job, 'val1', 'val2') { {
210
+ :priority => rand(6),
211
+ :keep => true
212
+ } }
213
+ end
214
+ Jobit::Job.add('work_off_bad', :bad_job, 'val1') { {
215
+ :priority => 2,
216
+ :keep => true
217
+ } }
218
+
219
+ Jobit::Job.work_off
220
+ jobs = []
221
+ for i in (0..5)
222
+ job = Jobit::Job.find_by_name("work_off_#{i}")
223
+ job.status.should eq('complete'), " #{job}"
224
+ job.destroy
225
+ job.id.should eq(nil)
226
+ end
227
+
228
+ job_failed = Jobit::Job.find_by_name('work_off_bad')
229
+ job_failed.tries.should eq(25)
230
+ job_failed.status.should eq('failed')
231
+ job_failed.destroy
232
+
233
+ end
234
+
235
+ end
236
+ end
@@ -0,0 +1 @@
1
+ require 'jobit'
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jobit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Victor Yunevich
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-03 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: daemons
16
+ requirement: &2162428060 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2162428060
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2162427640 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2162427640
36
+ description: Process background jobs in queue.
37
+ email:
38
+ - v.t.g.m.b.x@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - .rspec
45
+ - CHANGELOG.md
46
+ - Gemfile
47
+ - LICENSE
48
+ - README.md
49
+ - Rakefile
50
+ - app/controllers/jobit/jobs_controller.rb
51
+ - config/routes.rb
52
+ - init.rb
53
+ - jobit.gemspec
54
+ - lib/jobit.rb
55
+ - lib/jobit/command.rb
56
+ - lib/jobit/engine.rb
57
+ - lib/jobit/job.rb
58
+ - lib/jobit/jobby.rb
59
+ - lib/jobit/jobs/commands.rb
60
+ - lib/jobit/jobs/statuses.rb
61
+ - lib/jobit/railtie.rb
62
+ - lib/jobit/storage.rb
63
+ - lib/jobit/version.rb
64
+ - lib/jobit/worker.rb
65
+ - lib/tasks/jobit.rake
66
+ - lib/tasks/tasks.rb
67
+ - spec/jobit/storage_spec.rb
68
+ - spec/jobit_spec.rb
69
+ - spec/spec_helper.rb
70
+ homepage: ''
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.6
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Background jobs processing
94
+ test_files:
95
+ - spec/jobit/storage_spec.rb
96
+ - spec/jobit_spec.rb
97
+ - spec/spec_helper.rb