cascade 0.0.0 → 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/lib/cascade.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'cascade/worker'
2
+ require 'cascade/job'
3
+ require 'cascade/job_spec'
@@ -0,0 +1,27 @@
1
+ require 'cascade/job/callbacks'
2
+
3
+ module Cascade
4
+ module Job
5
+ include Callbacks
6
+
7
+ def self.included(mod)
8
+ mod.extend(ClassMethods)
9
+ end
10
+
11
+ def run
12
+ true
13
+ end
14
+
15
+ def describe
16
+ self.class.name
17
+ end
18
+
19
+ module ClassMethods
20
+ include Callbacks::ClassMethods
21
+
22
+ def enqueue(*args)
23
+ Worker.enqueue(name, *args)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module Cascade
2
+ module Job
3
+ module Callbacks
4
+ def run_callbacks(action, job_spec)
5
+ if self.class.instance_variable_defined?('@callbacks')
6
+ callbacks = self.class.instance_variable_get('@callbacks')
7
+ callbacks[action].each do |callback|
8
+ if callback.is_a?(Symbol)
9
+ send(callback, job_spec)
10
+ else
11
+ instance_exec job_spec, &callback
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ %w(before_queue before_run on_success on_error after_run).each do |action|
19
+ define_method(action) do |*args, &block|
20
+ add_callback(action.to_sym, args[0], &block)
21
+ end
22
+ end
23
+
24
+ def add_callback(action, method = nil, &block)
25
+ callbacks = @callbacks ||= Hash.new { |h,k| h[k] = [] }
26
+ callbacks[action] << (method || block)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ module Cascade
2
+ class JobSpec
3
+ include ::MongoMapper::Document
4
+ set_collection_name 'cascade_jobs'
5
+
6
+ key :class_name, String, :required => true
7
+ key :arguments, Array
8
+ key :priority, Integer, :default => 1
9
+ key :run_at, Time, :default => lambda { Time.now.utc }
10
+ key :attempts, Integer, :default => 0
11
+ key :locked_at, Time
12
+ key :locked_by, String
13
+ key :failed_at, Time
14
+ key :last_error, String
15
+ key :re_run, Boolean, :default => false
16
+
17
+ def job
18
+ @job ||= class_name.constantize.new(*arguments)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ module Cascade
2
+ class Worker
3
+ def self.start
4
+ trap('TERM') { $exit = true }
5
+ trap('INT') { $exit = true }
6
+
7
+ loop do
8
+ run
9
+ end
10
+ end
11
+
12
+ def self.run
13
+ find_available.each do |job_spec|
14
+ break if $exit
15
+ if lock_exclusively!(job_spec)
16
+ run_forked(job_spec)
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.run_forked(job_spec)
22
+ pid = fork do
23
+ job = job_spec.job
24
+ $0 = "Cascade::Job : #{name} : #{job.describe}"
25
+ run_job(job_spec, job)
26
+ end
27
+ Process.wait(pid)
28
+ end
29
+
30
+ def self.run_job(job_spec, job)
31
+ completed_successully = true
32
+ begin
33
+ job.run_callbacks(:before_run, job_spec)
34
+ job.run
35
+ job.run_callbacks(:on_success, job_spec)
36
+ rescue Exception => ex
37
+ job_spec.last_error = [ex, ex.backtrace].flatten.join("\n")
38
+ job_spec.failed_at = Time.now.utc
39
+ job.run_callbacks(:on_error, job_spec)
40
+ completed_successully = false
41
+ ensure
42
+ job.run_callbacks(:after_run, job_spec)
43
+ end
44
+ if completed_successully && !job_spec.re_run?
45
+ job_spec.destroy
46
+ else
47
+ job_spec.save!
48
+ end
49
+ completed_successully
50
+ end
51
+
52
+ def self.enqueue(class_name, *args)
53
+ job_spec = JobSpec.new(:class_name => class_name,
54
+ :arguments => args,
55
+ :run_at => Time.now.utc,
56
+ :priority => 1)
57
+
58
+ job = job_spec.job
59
+ job.run_callbacks(:before_queue, job_spec)
60
+
61
+ job_spec.save!
62
+ job_spec
63
+ end
64
+
65
+ def self.name=(name)
66
+ @name = name
67
+ end
68
+
69
+ def self.name
70
+ @name ||= generate_name
71
+ end
72
+
73
+ def self.generate_name
74
+ "#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
75
+ end
76
+
77
+ private
78
+ def self.find_available
79
+ right_now = Time.now.utc
80
+
81
+ conditions = {
82
+ :run_at => {'$lte' => right_now},
83
+ :failed_at => nil,
84
+ :locked_at => nil
85
+ }
86
+
87
+ job_specs = JobSpec.where(conditions).limit(-1).sort([[:priority, 1], [:run_at, 1]]).all
88
+ job_specs
89
+ end
90
+
91
+ def self.lock_exclusively!(job_spec)
92
+ right_now = Time.now.utc
93
+
94
+ conditions = {
95
+ :_id => job_spec.id,
96
+ :run_at => {'$lte' => right_now}
97
+ }
98
+ job_spec.collection.update(conditions, {'$set' => {:locked_at => right_now, :locked_by => name}})
99
+ affected_rows = job_spec.collection.find({:_id => job_spec.id, :locked_by => name}).count
100
+ if affected_rows == 1
101
+ job_spec.locked_at = right_now
102
+ job_spec.locked_by = name
103
+ true
104
+ else
105
+ false
106
+ end
107
+ end
108
+ end
109
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: cascade
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.0
5
+ version: 0.0.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Andrew Timberlake
@@ -35,6 +35,11 @@ extra_rdoc_files:
35
35
  - README.rdoc
36
36
  files:
37
37
  - LICENSE
38
+ - lib/cascade/job/callbacks.rb
39
+ - lib/cascade/job.rb
40
+ - lib/cascade/job_spec.rb
41
+ - lib/cascade/worker.rb
42
+ - lib/cascade.rb
38
43
  - README.rdoc
39
44
  has_rdoc: true
40
45
  homepage: http://github.com/andrewtimberlake/cascade