cascade 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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