easy_job 1.0.0.alpha → 1.0.0.beta
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/.gitignore +2 -0
- data/Gemfile +3 -0
- data/README.md +54 -9
- data/Rakefile +9 -3
- data/easy_job.gemspec +1 -3
- data/lib/easy_job.rb +74 -2
- data/lib/easy_job/attribute.rb +45 -0
- data/lib/easy_job/behavior.rb +109 -0
- data/lib/easy_job/delay_task.rb +48 -0
- data/lib/easy_job/ext/object.rb +23 -0
- data/lib/easy_job/logger.rb +13 -0
- data/lib/easy_job/logging.rb +48 -0
- data/lib/easy_job/mailer_task.rb +14 -0
- data/lib/easy_job/queue.rb +43 -0
- data/lib/easy_job/rails/message_delivery_patch.rb +54 -0
- data/lib/easy_job/redmine_task.rb +7 -0
- data/lib/easy_job/task.rb +38 -0
- data/lib/easy_job/task_wrapper.rb +86 -0
- data/lib/easy_job/version.rb +2 -2
- data/test_prepare.sh +42 -0
- metadata +16 -4
- data/lib/easy_job/easy_job.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06d362b4970f9a75202dfaac478eed36b453edad
|
4
|
+
data.tar.gz: 40a4a9a711c617f7b76476098e3611aab9b82ec9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a1a9111d2ebae0034052e5b2a3dbf7ec79b12df9017296c04ddaaca502b4a6ef1afc48ac5d5c98e78edb1d7b2d630c31bb04d46a84920b765256b06f887174d
|
7
|
+
data.tar.gz: 790f2b27b1053aa0a1458363973d43675241c5c08fb5bde1b92c41f6d9c51097b93526adabfe3c2cd8e5cc6300e47c0001a9f0e6ca3d3ed4b566c1690017e29a
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# EasyJob
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Asynchronous job for Redmine, EasyRedmine and EasyProject.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -22,15 +20,62 @@ Or install it yourself as:
|
|
22
20
|
|
23
21
|
## Usage
|
24
22
|
|
25
|
-
|
23
|
+
### Delay jobs
|
24
|
+
|
25
|
+
Every methods called after `.easy_delay` will be delayed and executed on other thread. This method could be used for any ruby Object.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Reschedule first issue to today
|
29
|
+
Issue.first.easy_delay.reschedule_on(Date.today)
|
26
30
|
|
27
|
-
|
31
|
+
# Save ORM object with lot of callbacks
|
32
|
+
[Issue.new, Issue.new].map { |i| i.easy_delay.save }
|
33
|
+
```
|
28
34
|
|
29
|
-
|
35
|
+
### Mailer jobs
|
30
36
|
|
31
|
-
|
37
|
+
Deliver email later.
|
32
38
|
|
33
|
-
|
39
|
+
```ruby
|
40
|
+
# Generating and sending will be done later
|
41
|
+
Mailer.issue_add(issue, ['test@example.net'], []).easy_deliver
|
42
|
+
|
43
|
+
# Email is generated now but send later
|
44
|
+
Mailer.issue_add(issue, ['test@example.net'], []).easy_safe_deliver
|
45
|
+
```
|
34
46
|
|
35
|
-
|
47
|
+
### Custom jobs
|
48
|
+
|
49
|
+
You can also create custom task with own exceptions capturing.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class PDFJob < EasyJob::RedmineTask
|
53
|
+
|
54
|
+
include IssuesHelper
|
55
|
+
|
56
|
+
def perform(issue_ids, project_id)
|
57
|
+
issues = Issue.where(id: issue_ids)
|
58
|
+
project = Project.find(project_id)
|
59
|
+
query = IssueQuery.new
|
60
|
+
|
61
|
+
result = issues_to_pdf(issues, project, query)
|
62
|
+
|
63
|
+
path = Rails.root.join('public', 'issues.pdf')
|
64
|
+
File.open(path, 'wb') {|f| f.write(result) }
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
PDFJob.perform_async(Issue.ids, Project.first.id)
|
70
|
+
```
|
71
|
+
|
72
|
+
## Ideas for next version
|
73
|
+
|
74
|
+
- Behaviour model.
|
75
|
+
- Repeat after failing.
|
76
|
+
- Dashboard.
|
77
|
+
- Queue defining.
|
78
|
+
|
79
|
+
## Contributing
|
36
80
|
|
81
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ondra-m/easy_job.
|
data/Rakefile
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
3
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
|
-
task :
|
6
|
+
task default: :spec
|
7
|
+
|
8
|
+
namespace :test do
|
9
|
+
task :prepare do
|
10
|
+
Kernel.system('./test_prepare.sh')
|
11
|
+
end
|
12
|
+
end
|
data/easy_job.gemspec
CHANGED
@@ -11,14 +11,12 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.authors = ['Ondřej Moravčík']
|
12
12
|
spec.email = ['moravcik.ondrej@gmail.com']
|
13
13
|
|
14
|
-
spec.summary = %q{Asynchronous job for
|
14
|
+
spec.summary = %q{Asynchronous job for Redmine, EasyRedmine and EasyProject}
|
15
15
|
spec.description = %q{}
|
16
16
|
spec.homepage = 'https://github.com/ondra-m/easy_job'
|
17
17
|
spec.license = 'MIT'
|
18
18
|
|
19
19
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
-
# spec.bindir = 'exe'
|
21
|
-
# spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
20
|
spec.require_paths = ['lib']
|
23
21
|
|
24
22
|
spec.required_ruby_version = '>= 2.1'
|
data/lib/easy_job.rb
CHANGED
@@ -1,5 +1,77 @@
|
|
1
|
-
require
|
1
|
+
require 'monitor'
|
2
|
+
require 'concurrent'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
require 'pry'
|
2
6
|
|
3
7
|
module EasyJob
|
4
|
-
|
8
|
+
extend MonitorMixin
|
9
|
+
|
10
|
+
autoload :Task, 'easy_job/task'
|
11
|
+
autoload :RedmineTask, 'easy_job/redmine_task'
|
12
|
+
autoload :DelayTask, 'easy_job/delay_task'
|
13
|
+
autoload :DelayTaskProxy, 'easy_job/delay_task'
|
14
|
+
autoload :MailerTask, 'easy_job/mailer_task'
|
15
|
+
autoload :TaskWrapper, 'easy_job/task_wrapper'
|
16
|
+
autoload :Logger, 'easy_job/logger'
|
17
|
+
autoload :Queue, 'easy_job/queue'
|
18
|
+
autoload :Attribute, 'easy_job/attribute'
|
19
|
+
autoload :Logging, 'easy_job/logging'
|
20
|
+
|
21
|
+
def self.get_queue(name)
|
22
|
+
synchronize do
|
23
|
+
@@queues ||= Concurrent::Map.new
|
24
|
+
@@queues.fetch_or_store(name) { EasyJob::Queue.new(name) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Block `all_done?` method for `interval` seconds.
|
29
|
+
# Method `all_done?` is checking `scheduled_task_count`
|
30
|
+
# but `ScheduledTask` is added to executor queue after delay time.
|
31
|
+
def self.block_all_done_for(interval)
|
32
|
+
synchronize do
|
33
|
+
@@block_all_done_until ||= Time.now
|
34
|
+
|
35
|
+
new_time = Time.now + interval.to_f
|
36
|
+
if @@block_all_done_until < new_time
|
37
|
+
@@block_all_done_until = new_time
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# One time, non-blocking.
|
43
|
+
def self.all_done?
|
44
|
+
synchronize do
|
45
|
+
return true if @@queues.nil?
|
46
|
+
|
47
|
+
@@block_all_done_until ||= Time.now
|
48
|
+
if @@block_all_done_until > Time.now
|
49
|
+
false
|
50
|
+
else
|
51
|
+
@@queues.values.map(&:all_done?).all?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Blocking passive waiting.
|
57
|
+
def self.wait_for_all(wait_delay: 5)
|
58
|
+
loop {
|
59
|
+
if all_done?
|
60
|
+
return
|
61
|
+
else
|
62
|
+
sleep wait_delay
|
63
|
+
end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.logger
|
68
|
+
synchronize do
|
69
|
+
@@loger ||= Logger.new(Rails.root.join('log', 'easy_jobs.log'))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
5
73
|
end
|
74
|
+
|
75
|
+
require 'easy_job/version'
|
76
|
+
require 'easy_job/ext/object'
|
77
|
+
require 'easy_job/rails/message_delivery_patch'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module EasyJob
|
2
|
+
module Attribute
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(Methods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module Methods
|
9
|
+
|
10
|
+
def attribute(name, defaults=nil)
|
11
|
+
class_eval <<-METHODS
|
12
|
+
def self.#{name}=(value)
|
13
|
+
singleton_class.class_eval do
|
14
|
+
define_method(:#{name}) do |new_value=nil|
|
15
|
+
if new_value.nil?
|
16
|
+
value
|
17
|
+
else
|
18
|
+
self.#{name} = new_value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
value
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.#{name}?
|
27
|
+
!!#{name}
|
28
|
+
end
|
29
|
+
|
30
|
+
def #{name}
|
31
|
+
self.class.#{name}
|
32
|
+
end
|
33
|
+
|
34
|
+
def #{name}?
|
35
|
+
self.class.#{name}?
|
36
|
+
end
|
37
|
+
METHODS
|
38
|
+
|
39
|
+
self.send("#{name}=", defaults)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# Behavior model
|
2
|
+
#
|
3
|
+
# NOT used in current version
|
4
|
+
# Just and idea
|
5
|
+
|
6
|
+
##
|
7
|
+
# EasyJob::Behavior
|
8
|
+
#
|
9
|
+
# Container for behavior definition
|
10
|
+
#
|
11
|
+
module EasyJob
|
12
|
+
class Behavior
|
13
|
+
|
14
|
+
@@behaviors = {}
|
15
|
+
|
16
|
+
def self.define(name, &block)
|
17
|
+
@@behaviors[name] = new(name, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get(name)
|
21
|
+
@@behaviors[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(name, &block)
|
25
|
+
@name = name
|
26
|
+
instance_eval(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_create
|
30
|
+
if block_given?
|
31
|
+
@on_create = Proc.new
|
32
|
+
else
|
33
|
+
@on_create || proc{}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# EasyJob::TaskWrapper
|
42
|
+
#
|
43
|
+
# Modified wrapper
|
44
|
+
#
|
45
|
+
module EasyJob
|
46
|
+
class TaskWrapper
|
47
|
+
|
48
|
+
def initialize(task_class, args)
|
49
|
+
@behaviors = Array(task_class.behaviors)
|
50
|
+
@behaviors.map!{|b| Behavior.get(b) }
|
51
|
+
@behaviors.compact!
|
52
|
+
|
53
|
+
@behaviors.each{|b| instance_eval(&b.on_initialize) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def perform
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
##
|
64
|
+
# Definition of behavior
|
65
|
+
#
|
66
|
+
EasyJob::Behavior.define 'Database' do
|
67
|
+
|
68
|
+
on_create do
|
69
|
+
@connection_attempt = 0
|
70
|
+
end
|
71
|
+
|
72
|
+
on_perform do |task|
|
73
|
+
begin
|
74
|
+
ActiveRecord::Base.connection_pool.with_connection { task.run }
|
75
|
+
rescue ActiveRecord::ConnectionTimeoutError
|
76
|
+
@connection_attempt += 1
|
77
|
+
if @connection_attempt > max_db_connection_attempts
|
78
|
+
log_error 'Max ConnectionTimeoutError'
|
79
|
+
return
|
80
|
+
else
|
81
|
+
log_warn "ConnectionTimeoutError attempt=#{@connection_attempt}"
|
82
|
+
retry
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
EasyJob::Behavior.define 'RedmineEnv' do
|
90
|
+
|
91
|
+
on_create do
|
92
|
+
@current_user = User.current
|
93
|
+
@current_locale = I18n.locale
|
94
|
+
end
|
95
|
+
|
96
|
+
on_create do |task|
|
97
|
+
begin
|
98
|
+
orig_user = User.current
|
99
|
+
orig_locale = I18n.locale
|
100
|
+
User.current = @current_user
|
101
|
+
I18n.locale = @current_locale
|
102
|
+
super
|
103
|
+
ensure
|
104
|
+
User.current = orig_user
|
105
|
+
I18n.locale = orig_locale
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class EasyJob::DelayTaskProxy < BasicObject
|
2
|
+
|
3
|
+
def initialize(object)
|
4
|
+
@object = object
|
5
|
+
@chains = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def __object
|
9
|
+
@object
|
10
|
+
end
|
11
|
+
|
12
|
+
def __chains
|
13
|
+
@chains
|
14
|
+
end
|
15
|
+
|
16
|
+
def easy_delay
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(name, *args, &block)
|
21
|
+
@chains << [name, args, block]
|
22
|
+
if @chains.size == 1
|
23
|
+
::EasyJob::DelayTask.perform_in(1, self)
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# EasyJob::DelayTask
|
32
|
+
#
|
33
|
+
# Run chains of commands deleyed
|
34
|
+
#
|
35
|
+
# Issue.first.easy_delay.reschedule_on(Date.today)
|
36
|
+
#
|
37
|
+
class EasyJob::DelayTask < EasyJob::RedmineTask
|
38
|
+
|
39
|
+
def perform(proxy)
|
40
|
+
object = proxy.__object
|
41
|
+
chains = proxy.__chains
|
42
|
+
|
43
|
+
chains.each do |name, args, block|
|
44
|
+
object = object.send(name, *args, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Object
|
2
|
+
|
3
|
+
# Be carefull with nested calling. Parameters could be generated some times.
|
4
|
+
#
|
5
|
+
# # It's OK
|
6
|
+
# Issue.first.easy_delay.reschedule_on(Date.today)
|
7
|
+
#
|
8
|
+
# # This could be a problem
|
9
|
+
# def calc_date
|
10
|
+
# sleep 100
|
11
|
+
# Date.today
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Issue.easy_delay.first.reschedule_on(calc_date)
|
15
|
+
#
|
16
|
+
# On second example only `first` method will be executed because Job does not know
|
17
|
+
# how many calling be triggered. Small workaround: method is delayed for 1s.
|
18
|
+
#
|
19
|
+
def easy_delay
|
20
|
+
EasyJob::DelayTaskProxy.new(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module EasyJob
|
2
|
+
class Logger < ::Logger
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
super $stdout
|
6
|
+
self.formatter = proc do |severity, datetime, progname, msg|
|
7
|
+
time = datetime.strftime('%H:%M:%S')
|
8
|
+
"#{severity.first}, [#{time}] #{msg}\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module EasyJob
|
2
|
+
module Logging
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.include(InstanceMethods)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
|
11
|
+
def log_info(message)
|
12
|
+
message = "#{self.class.name}:#{job_id} - #{message}"
|
13
|
+
EasyJob.logger.info(message)
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_warn(message)
|
17
|
+
message = "#{self.class.name}:#{job_id} - #{message}"
|
18
|
+
EasyJob.logger.warn(message)
|
19
|
+
end
|
20
|
+
|
21
|
+
def log_error(message)
|
22
|
+
message = "#{self.class.name}:#{job_id} - #{message}"
|
23
|
+
EasyJob.logger.error(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
|
30
|
+
def log_info(message)
|
31
|
+
message = "#{self.name} - #{message}"
|
32
|
+
EasyJob.logger.info(message)
|
33
|
+
end
|
34
|
+
|
35
|
+
def log_warn(message)
|
36
|
+
message = "#{self.name} - #{message}"
|
37
|
+
EasyJob.logger.warn(message)
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_error(message)
|
41
|
+
message = "#{self.name} - #{message}"
|
42
|
+
EasyJob.logger.error(message)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class EasyJob::MailerTask < EasyJob::RedmineTask
|
2
|
+
|
3
|
+
def perform(message)
|
4
|
+
case message
|
5
|
+
when ActionMailer::MessageDelivery
|
6
|
+
# Generate mail and send it
|
7
|
+
message.deliver_now
|
8
|
+
when Mail::Message
|
9
|
+
# Already generated, just send it
|
10
|
+
message.deliver
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module EasyJob
|
2
|
+
class Queue < Concurrent::Synchronization::LockableObject
|
3
|
+
|
4
|
+
attr_reader :pool
|
5
|
+
|
6
|
+
# == ThreadPoolExecutor options:
|
7
|
+
# idletime::
|
8
|
+
# The number of seconds that a thread may be idle before being reclaimed
|
9
|
+
#
|
10
|
+
# auto_terminate::
|
11
|
+
# When true (default) an at_exit handler will be registered which
|
12
|
+
# will stop the thread pool when the application exits
|
13
|
+
#
|
14
|
+
# max_queue::
|
15
|
+
# 0 is unlimited
|
16
|
+
#
|
17
|
+
DEFAULT_EXECUTOR_OPTIONS = {
|
18
|
+
min_threads: 3,
|
19
|
+
max_threads: 3,
|
20
|
+
idletime: 60,
|
21
|
+
max_queue: 0,
|
22
|
+
auto_terminate: false
|
23
|
+
}
|
24
|
+
|
25
|
+
def initialize(name)
|
26
|
+
super()
|
27
|
+
@name = name
|
28
|
+
@pool = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS)
|
29
|
+
end
|
30
|
+
|
31
|
+
def post(*args, &block)
|
32
|
+
synchronize {
|
33
|
+
@pool.post(*args, &block)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
# It will work only if perform method was wrapped by begin rescue end
|
38
|
+
def all_done?
|
39
|
+
pool.scheduled_task_count == pool.completed_task_count
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module EasyJob
|
2
|
+
##
|
3
|
+
# ActionMailer::MessageDelivery
|
4
|
+
#
|
5
|
+
# It's a delegator which is created on method_missing.
|
6
|
+
#
|
7
|
+
module MessageDeliveryPatch
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
if Rails.env.test?
|
11
|
+
base.send(:include, TestInstanceMethods)
|
12
|
+
else
|
13
|
+
base.send(:include, InstanceMethods)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
|
19
|
+
# Mail will be generated and sent later
|
20
|
+
#
|
21
|
+
# Mailer.issue_add(issue, [], []).easy_deliver
|
22
|
+
#
|
23
|
+
def easy_deliver
|
24
|
+
EasyJob::MailerTask.perform_async(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Mail is generated now and sent later
|
28
|
+
#
|
29
|
+
# Mailer.issue_add(issue, [], []).easy_deliver
|
30
|
+
#
|
31
|
+
def easy_safe_deliver
|
32
|
+
EasyJob::MailerTask.perform_async(message)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
module TestInstanceMethods
|
38
|
+
|
39
|
+
def easy_deliver
|
40
|
+
deliver
|
41
|
+
end
|
42
|
+
|
43
|
+
def easy_safe_deliver
|
44
|
+
deliver
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if defined?(ActionMailer)
|
53
|
+
ActionMailer::MessageDelivery.include(EasyJob::MessageDeliveryPatch)
|
54
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module EasyJob
|
2
|
+
class Task
|
3
|
+
include Attribute
|
4
|
+
include Logging
|
5
|
+
|
6
|
+
attribute :queue_name, 'default'
|
7
|
+
|
8
|
+
attr_accessor :job_id
|
9
|
+
|
10
|
+
def self.perform_async(*args)
|
11
|
+
wrapper = TaskWrapper.new(self, args)
|
12
|
+
queue = EasyJob.get_queue(queue_name)
|
13
|
+
queue.post(wrapper, &:perform)
|
14
|
+
wrapper
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.perform_in(interval, *args)
|
18
|
+
wrapper = TaskWrapper.new(self, args)
|
19
|
+
queue = EasyJob.get_queue(queue_name)
|
20
|
+
concurrent_job = Concurrent::ScheduledTask.execute(interval.to_f, args: wrapper, executor: queue.pool, &:perform)
|
21
|
+
EasyJob.block_all_done_for(interval)
|
22
|
+
wrapper
|
23
|
+
end
|
24
|
+
|
25
|
+
def perform(*)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_error(ex)
|
30
|
+
log_error ex.message
|
31
|
+
ex.backtrace.each do |line|
|
32
|
+
log_error line
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module EasyJob
|
2
|
+
class TaskWrapper
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
STATE_PENDING = 0
|
6
|
+
STATE_RUNNING = 1
|
7
|
+
STATE_FINISHED = 2
|
8
|
+
|
9
|
+
def initialize(task_class, args)
|
10
|
+
@task_class = task_class
|
11
|
+
@args = args
|
12
|
+
@state = STATE_PENDING
|
13
|
+
|
14
|
+
@connection_attempt = 0
|
15
|
+
@current_user = User.current
|
16
|
+
@current_locale = I18n.locale
|
17
|
+
end
|
18
|
+
|
19
|
+
def job_id
|
20
|
+
@job && @job.job_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def pending?
|
24
|
+
@state == STATE_PENDING
|
25
|
+
end
|
26
|
+
|
27
|
+
def running?
|
28
|
+
@state == STATE_RUNNING
|
29
|
+
end
|
30
|
+
|
31
|
+
def finished?
|
32
|
+
@state == STATE_FINISHED
|
33
|
+
end
|
34
|
+
|
35
|
+
def perform
|
36
|
+
@job = @task_class.new
|
37
|
+
@job.job_id = SecureRandom.uuid
|
38
|
+
|
39
|
+
ensure_connection {
|
40
|
+
ensure_redmine_env {
|
41
|
+
begin
|
42
|
+
@state = STATE_RUNNING
|
43
|
+
@job.perform(*@args)
|
44
|
+
rescue => ex
|
45
|
+
@job.handle_error(ex)
|
46
|
+
ensure
|
47
|
+
@state = STATE_FINISHED
|
48
|
+
log_info 'Job ended'
|
49
|
+
end
|
50
|
+
}
|
51
|
+
}
|
52
|
+
rescue => e
|
53
|
+
# Perform method must end successfully.
|
54
|
+
# Otherwise `all_done?` end on deadlock.
|
55
|
+
end
|
56
|
+
|
57
|
+
def ensure_connection
|
58
|
+
ActiveRecord::Base.connection_pool.with_connection { yield }
|
59
|
+
rescue ActiveRecord::ConnectionTimeoutError
|
60
|
+
@connection_attempt += 1
|
61
|
+
if @connection_attempt > max_db_connection_attempts
|
62
|
+
log_error 'Max ConnectionTimeoutError'
|
63
|
+
return
|
64
|
+
else
|
65
|
+
log_warn "ConnectionTimeoutError attempt=#{@connection_attempt}"
|
66
|
+
retry
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def ensure_redmine_env
|
71
|
+
orig_user = User.current
|
72
|
+
orig_locale = I18n.locale
|
73
|
+
User.current = @current_user
|
74
|
+
I18n.locale = @current_locale
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
User.current = orig_user
|
78
|
+
I18n.locale = orig_locale
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
%{#<EasyJob::TaskWrapper(#{@task_class}) id="#{job_id}">}
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/lib/easy_job/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = '1.0.0.
|
1
|
+
module EasyJob
|
2
|
+
VERSION = '1.0.0.beta'
|
3
3
|
end
|
data/test_prepare.sh
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# -v print lines as they are read
|
4
|
+
# -x print lines as they are executed
|
5
|
+
# -e abort script at first error
|
6
|
+
set -e
|
7
|
+
|
8
|
+
if [[ ! -d ".redmine" ]]; then
|
9
|
+
mkdir .redmine
|
10
|
+
pushd .redmine
|
11
|
+
# Download and extract redmine
|
12
|
+
wget "http://www.redmine.org/releases/redmine-3.3.0.tar.gz" -O redmine.tar.gz
|
13
|
+
tar xzf redmine.tar.gz --strip 1
|
14
|
+
|
15
|
+
# Init gems
|
16
|
+
echo "
|
17
|
+
gem 'pry'
|
18
|
+
gem 'pry-rails'
|
19
|
+
gem 'puma'
|
20
|
+
gem 'easy_job', path: '../'
|
21
|
+
" > Gemfile.local
|
22
|
+
|
23
|
+
# Init database
|
24
|
+
ruby -ryaml -e "
|
25
|
+
config = {
|
26
|
+
'adapter' => 'postgresql',
|
27
|
+
'database' => 'easy_job_redmine',
|
28
|
+
'host' => '127.0.0.1',
|
29
|
+
'encoding' => 'utf8'
|
30
|
+
}
|
31
|
+
config = { 'development' => config, 'production' => config }.to_yaml
|
32
|
+
File.write('config/database.yml', config)
|
33
|
+
"
|
34
|
+
|
35
|
+
# Install
|
36
|
+
bundle --local
|
37
|
+
bundle exec rake db:create RAILS_ENV=production
|
38
|
+
bundle exec rake db:migrate RAILS_ENV=production
|
39
|
+
bundle exec rake generate_secret_token RAILS_ENV=production
|
40
|
+
bundle exec rake redmine:load_default_data REDMINE_LANG=en RAILS_ENV=production
|
41
|
+
popd
|
42
|
+
fi
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: easy_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ondřej Moravčík
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -81,8 +81,20 @@ files:
|
|
81
81
|
- Rakefile
|
82
82
|
- easy_job.gemspec
|
83
83
|
- lib/easy_job.rb
|
84
|
-
- lib/easy_job/
|
84
|
+
- lib/easy_job/attribute.rb
|
85
|
+
- lib/easy_job/behavior.rb
|
86
|
+
- lib/easy_job/delay_task.rb
|
87
|
+
- lib/easy_job/ext/object.rb
|
88
|
+
- lib/easy_job/logger.rb
|
89
|
+
- lib/easy_job/logging.rb
|
90
|
+
- lib/easy_job/mailer_task.rb
|
91
|
+
- lib/easy_job/queue.rb
|
92
|
+
- lib/easy_job/rails/message_delivery_patch.rb
|
93
|
+
- lib/easy_job/redmine_task.rb
|
94
|
+
- lib/easy_job/task.rb
|
95
|
+
- lib/easy_job/task_wrapper.rb
|
85
96
|
- lib/easy_job/version.rb
|
97
|
+
- test_prepare.sh
|
86
98
|
homepage: https://github.com/ondra-m/easy_job
|
87
99
|
licenses:
|
88
100
|
- MIT
|
@@ -106,5 +118,5 @@ rubyforge_project:
|
|
106
118
|
rubygems_version: 2.5.1
|
107
119
|
signing_key:
|
108
120
|
specification_version: 4
|
109
|
-
summary: Asynchronous job for
|
121
|
+
summary: Asynchronous job for Redmine, EasyRedmine and EasyProject
|
110
122
|
test_files: []
|
data/lib/easy_job/easy_job.rb
DELETED