relaxed-job 1.0.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 David Dollar
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.
data/README.rdoc ADDED
@@ -0,0 +1,16 @@
1
+ = relaxed-job
2
+
3
+ Relaxed Job
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with Rakefile or VERSION.
12
+ * Send me a pull request. Bonus points for topic branches.
13
+
14
+ == Copyright
15
+
16
+ Copyright (c) 2009 David Dollar. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
5
+ require 'relaxed_job/tasks'
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "relaxed-job"
11
+ gem.summary = %Q{Relaxed Job}
12
+ gem.description = gem.summary
13
+ gem.email = "<ddollar@gmail.com>"
14
+ gem.homepage = "http://github.com/ddollar/relaxed-job"
15
+ gem.authors = ["David Dollar"]
16
+
17
+ gem.add_development_dependency "rspec"
18
+
19
+ gem.add_dependency "couchrest"
20
+ gem.add_dependency "libdir"
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
25
+ end
26
+
27
+ require 'spec/rake/spectask'
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
34
+ spec.libs << 'lib' << 'spec'
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :spec => :check_dependencies
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ if File.exist?('VERSION')
46
+ version = File.read('VERSION')
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "relaxed-job #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1 @@
1
+ require 'relaxed_job'
@@ -0,0 +1,12 @@
1
+ module RelaxedJob
2
+
3
+ def self.couchdb(url)
4
+ database = CouchRest.database!(url)
5
+ database.update_designs(File.join(File.dirname(__FILE__), 'relaxed_job', 'designs'))
6
+ database
7
+ end
8
+
9
+ end
10
+
11
+ require 'relaxed_job/queue'
12
+ require 'relaxed_job/worker'
@@ -0,0 +1,5 @@
1
+ function(doc) {
2
+ if (doc.class == 'job' && doc.state == 'complete') {
3
+ emit(doc.completed_at, doc);
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ function(doc) {
2
+ if (doc.class == 'job' && doc.state == 'error') {
3
+ emit(doc.errored_at, doc);
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ function(doc) {
2
+ if (doc.class == 'job' && doc.state == 'locked') {
3
+ emit(doc.locked_by, doc);
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ function(doc) {
2
+ if (doc.class == 'job' && doc.state == 'pending') {
3
+ emit(doc.queued_at, doc);
4
+ }
5
+ }
@@ -0,0 +1,106 @@
1
+ require 'couchrest'
2
+
3
+ class RelaxedJob::Queue
4
+
5
+ attr_reader :couchdb_url
6
+ attr_reader :options
7
+
8
+ def initialize(couchdb_url)
9
+ @couchdb_url = couchdb_url
10
+ @options = options
11
+ end
12
+
13
+ def enqueue(object)
14
+ enqueue_with_method object, :perform
15
+ end
16
+
17
+ def enqueue_with_method(object, method, *args)
18
+ couchdb.save_doc({
19
+ 'class' => 'job',
20
+ 'state' => 'pending',
21
+ 'method' => method.to_s,
22
+ 'arguments' => args.to_a,
23
+ 'object' => Marshal.dump(object),
24
+ 'queued_at' => Time.now.utc
25
+ })
26
+ end
27
+
28
+ def lock(count)
29
+ pending_jobs(count).each do |job|
30
+ job['state'] = 'locked'
31
+ job['locked_by'] = worker_name
32
+ couchdb.save_doc(job)
33
+ end
34
+ end
35
+
36
+ def clear_locks!
37
+ locked_jobs.each do |job|
38
+ job['state'] = 'pending'
39
+ job.delete('locked_by')
40
+ couchdb.save_doc(job)
41
+ end
42
+ end
43
+
44
+ def work(worker_name=worker_name)
45
+ lock 3
46
+
47
+ counts = { :complete => 0, :error => 0 }
48
+
49
+ locked_jobs.each do |job|
50
+ begin
51
+ object = Marshal.load(job['object'])
52
+ object.send(job['method'], *(job['arguments']))
53
+
54
+ counts[:complete] += 1
55
+
56
+ job['state'] = 'complete'
57
+ job['completed_at'] = Time.now.utc
58
+ couchdb.save_doc(job)
59
+ rescue StandardError => ex
60
+ counts[:error] += 1
61
+
62
+ job['state'] = 'error'
63
+ job['exception'] = Marshal.dump(ex)
64
+ job['errored_at'] = Time.now.utc
65
+ couchdb.save_doc(job)
66
+ end
67
+ end
68
+
69
+ counts
70
+ end
71
+
72
+ def worker_name
73
+ "host:#{Socket.gethostname} pid:#{$$}"
74
+ rescue
75
+ "pid:#{$$}"
76
+ end
77
+
78
+ ## job types #################################################################
79
+
80
+ def completed_jobs
81
+ jobs_by_type(:completed)
82
+ end
83
+
84
+ def errored_jobs
85
+ jobs_by_type(:errored)
86
+ end
87
+
88
+ def locked_jobs
89
+ jobs_by_type(:locked, :key => worker_name)
90
+ end
91
+
92
+ def pending_jobs(count)
93
+ jobs_by_type(:pending, :limit => count)
94
+ end
95
+
96
+ private ######################################################################
97
+
98
+ def couchdb
99
+ @couchdb ||= RelaxedJob.couchdb(couchdb_url)
100
+ end
101
+
102
+ def jobs_by_type(type, options={})
103
+ couchdb.view("jobs/#{type}", options)['rows'].map { |row| row['value'] }
104
+ end
105
+
106
+ end
@@ -0,0 +1,19 @@
1
+ require 'relaxed_job/worker'
2
+
3
+ def relaxed_job_url
4
+ ENV['RELAXED_JOB_COUCHDB_URL'] || 'http://localhost:5984/relaxed_job'
5
+ end
6
+
7
+ namespace :jobs do
8
+
9
+ desc "Run job daemon"
10
+ task :work do
11
+ RelaxedJob::Worker.new(relaxed_job_url).start
12
+ end
13
+
14
+ task :test do
15
+ queue = RelaxedJob::Queue.new(relaxed_job_url)
16
+ queue.enqueue_with_method("", :to_s)
17
+ end
18
+
19
+ end
@@ -0,0 +1,62 @@
1
+ require 'benchmark'
2
+ require 'couchrest'
3
+ require 'relaxed_job'
4
+
5
+ class RelaxedJob::Worker
6
+
7
+ SLEEP = 5
8
+
9
+ attr_reader :queue
10
+ attr_reader :options
11
+
12
+ def initialize(couchrest_url, options={})
13
+ @queue = RelaxedJob::Queue.new(couchrest_url)
14
+ @options = options
15
+ end
16
+
17
+ def name
18
+ "testname"
19
+ end
20
+
21
+ def start
22
+ say "*** Starting job worker #{queue.worker_name}"
23
+
24
+ trap('TERM') { say 'Exiting...'; $exit = true }
25
+ trap('INT') { say 'Exiting...'; $exit = true }
26
+
27
+ loop do
28
+ result = nil
29
+
30
+ realtime = Benchmark.realtime do
31
+ result = queue.work
32
+ sleep 1
33
+ end
34
+
35
+ count = result.values.inject(0) { |a,v| a+v }
36
+
37
+ break if $exit
38
+
39
+ if count.zero?
40
+ sleep(SLEEP)
41
+ else
42
+ say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result[:error]]
43
+ end
44
+
45
+ break if $exit
46
+ end
47
+
48
+ ensure
49
+ queue.clear_locks!
50
+ end
51
+
52
+ def say(text)
53
+ puts text unless options[:quiet]
54
+ end
55
+
56
+ private ######################################################################
57
+
58
+ def quiet
59
+ options[:quiet]
60
+ end
61
+
62
+ end
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{relaxed-job}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["David Dollar"]
12
+ s.date = %q{2009-11-09}
13
+ s.description = %q{Relaxed Job}
14
+ s.email = %q{<ddollar@gmail.com>}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/relaxed-job.rb",
27
+ "lib/relaxed_job.rb",
28
+ "lib/relaxed_job/designs/jobs/views/completed/map.js",
29
+ "lib/relaxed_job/designs/jobs/views/errored/map.js",
30
+ "lib/relaxed_job/designs/jobs/views/locked/map.js",
31
+ "lib/relaxed_job/designs/jobs/views/pending/map.js",
32
+ "lib/relaxed_job/queue.rb",
33
+ "lib/relaxed_job/tasks.rb",
34
+ "lib/relaxed_job/worker.rb",
35
+ "relaxed-job.gemspec",
36
+ "spec/rcov.opts",
37
+ "spec/relaxed_job/queue_spec.rb",
38
+ "spec/spec.opts",
39
+ "spec/spec_helper.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/ddollar/relaxed-job}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.5}
45
+ s.summary = %q{Relaxed Job}
46
+ s.test_files = [
47
+ "spec/relaxed_job/queue_spec.rb",
48
+ "spec/spec_helper.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ s.add_development_dependency(%q<rspec>, [">= 0"])
57
+ s.add_runtime_dependency(%q<couchrest>, [">= 0"])
58
+ s.add_runtime_dependency(%q<libdir>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<rspec>, [">= 0"])
61
+ s.add_dependency(%q<couchrest>, [">= 0"])
62
+ s.add_dependency(%q<libdir>, [">= 0"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<rspec>, [">= 0"])
66
+ s.add_dependency(%q<couchrest>, [">= 0"])
67
+ s.add_dependency(%q<libdir>, [">= 0"])
68
+ end
69
+ end
data/spec/rcov.opts ADDED
@@ -0,0 +1,2 @@
1
+ --exclude "spec/*,gems/*"
2
+ --rails
@@ -0,0 +1,81 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "RelaxedJob::Queue" do
4
+
5
+ before(:each) do
6
+ @queue = test_delayed_job_queue
7
+ @enqueued = TestFileWriter.new
8
+ end
9
+
10
+ after(:all) do
11
+ couchdb_cleanup
12
+ end
13
+
14
+ describe "using enqueue to queue a class" do
15
+ before(:each) do
16
+ @queue.enqueue @enqueued
17
+ @results = @queue.work
18
+ end
19
+
20
+ it "has the right data in the file" do
21
+ File.read(@enqueued.filename).should == 'perform'
22
+ end
23
+
24
+ it "returns the count of jobs" do
25
+ @results.should == { :complete => 1, :error => 0 }
26
+ end
27
+ end
28
+
29
+ describe "using enqueue_with_method to call a different method" do
30
+ before(:each) do
31
+ @queue.enqueue_with_method @enqueued, :perform_two
32
+ @queue.work
33
+ end
34
+
35
+ it "has the right data in the file" do
36
+ File.read(@enqueued.filename).should == 'perform_two'
37
+ end
38
+ end
39
+
40
+ describe "using enqueue_with_method to call a method with arguments" do
41
+ before(:each) do
42
+ @queue.enqueue_with_method @enqueued, :perform_args, 'one', 'two'
43
+ @queue.work
44
+ end
45
+
46
+ it "has the right data in the file" do
47
+ File.read(@enqueued.filename).should == '["one", "two"]'
48
+ end
49
+ end
50
+
51
+ describe "with an erroring method" do
52
+ before(:each) do
53
+ @queue.enqueue_with_method @enqueued, :perform_with_error, 'ErrorText'
54
+ @results = @queue.work
55
+ end
56
+
57
+ it "stores the errored job with its exception" do
58
+ job = @queue.errored_jobs.last
59
+ exception = Marshal.load(job['exception'])
60
+ exception.message.should == 'ErrorText'
61
+ end
62
+
63
+ it "returns the count of jobs" do
64
+ @results.should == { :complete => 0, :error => 1 }
65
+ end
66
+ end
67
+
68
+ describe "with locked jobs" do
69
+ before(:each) do
70
+ @queue.enqueue_with_method @enqueued, :perform_with_error, 'ErrorText'
71
+ @queue.lock(1)
72
+ end
73
+
74
+ it "should be able to unlock jobs" do
75
+ @queue.locked_jobs.length.should == 1
76
+ @queue.clear_locks!
77
+ @queue.locked_jobs.length.should == 0
78
+ end
79
+ end
80
+
81
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'couchrest'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'relaxed-job'
7
+ require 'spec'
8
+ require 'spec/autorun'
9
+
10
+ TEST_RELAXED_JOB_COUCH_URL = 'http://localhost:5984/relaxed_job_test'
11
+
12
+ class TestFileWriter
13
+ attr_reader :filename
14
+
15
+ def initialize
16
+ @filename = "/tmp/relaxed_job_test/#{rand(1000000)}.file"
17
+ end
18
+
19
+ def perform
20
+ write_to_file('perform')
21
+ end
22
+
23
+ def perform_two
24
+ write_to_file('perform_two')
25
+ end
26
+
27
+ def perform_args(*args)
28
+ write_to_file(args.inspect)
29
+ end
30
+
31
+ def perform_with_error(message)
32
+ raise message
33
+ end
34
+
35
+ def write_to_file(data)
36
+ File.open(filename, 'w') do |file|
37
+ file.print data
38
+ end
39
+ end
40
+ end
41
+
42
+ def test_delayed_job_queue
43
+ RelaxedJob::Queue.new(TEST_RELAXED_JOB_COUCH_URL)
44
+ end
45
+
46
+ def couchdb_cleanup
47
+ CouchRest.database(TEST_RELAXED_JOB_COUCH_URL).delete!
48
+ end
49
+
50
+ Spec::Runner.configure do |config|
51
+ config.before(:all) do
52
+ FileUtils.mkdir_p('/tmp/relaxed_job_test')
53
+ end
54
+
55
+ config.after(:all) do
56
+ FileUtils.rm_rf('/tmp/relaxed_job_test')
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: relaxed-job
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Dollar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-09 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: couchrest
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: libdir
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Relaxed Job
46
+ email: <ddollar@gmail.com>
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.rdoc
54
+ files:
55
+ - .document
56
+ - .gitignore
57
+ - LICENSE
58
+ - README.rdoc
59
+ - Rakefile
60
+ - VERSION
61
+ - lib/relaxed-job.rb
62
+ - lib/relaxed_job.rb
63
+ - lib/relaxed_job/designs/jobs/views/completed/map.js
64
+ - lib/relaxed_job/designs/jobs/views/errored/map.js
65
+ - lib/relaxed_job/designs/jobs/views/locked/map.js
66
+ - lib/relaxed_job/designs/jobs/views/pending/map.js
67
+ - lib/relaxed_job/queue.rb
68
+ - lib/relaxed_job/tasks.rb
69
+ - lib/relaxed_job/worker.rb
70
+ - relaxed-job.gemspec
71
+ - spec/rcov.opts
72
+ - spec/relaxed_job/queue_spec.rb
73
+ - spec/spec.opts
74
+ - spec/spec_helper.rb
75
+ has_rdoc: true
76
+ homepage: http://github.com/ddollar/relaxed-job
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --charset=UTF-8
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.5
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Relaxed Job
103
+ test_files:
104
+ - spec/relaxed_job/queue_spec.rb
105
+ - spec/spec_helper.rb