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