sidejobs 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +80 -0
- data/Rakefile +18 -0
- data/lib/generators/sidejobs/install_generator.rb +24 -0
- data/lib/generators/sidejobs/templates/configuration.rb +7 -0
- data/lib/generators/sidejobs/templates/migration.rb +25 -0
- data/lib/sidejobs.rb +34 -0
- data/lib/sidejobs/configuration.rb +7 -0
- data/lib/sidejobs/daemon.rb +104 -0
- data/lib/sidejobs/extensions/active_job/queue_adapter.rb +47 -0
- data/lib/sidejobs/job.rb +21 -0
- data/lib/sidejobs/processor.rb +23 -0
- data/lib/sidejobs/queue.rb +24 -0
- data/lib/sidejobs/railtie.rb +15 -0
- data/lib/sidejobs/version.rb +3 -0
- data/lib/tasks/sidejobs.rake +11 -0
- data/test/daemon_test.rb +71 -0
- data/test/dummy/Rakefile +5 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/charge_subscriptions_job.rb +8 -0
- data/test/dummy/app/jobs/clear_guest_users_job.rb +8 -0
- data/test/dummy/app/jobs/send_newsletters_job.rb +8 -0
- data/test/dummy/app/jobs/share_products_job.rb +8 -0
- data/test/dummy/app/jobs/update_exchanges_job.rb +8 -0
- data/test/dummy/app/mailers/user_mailer.rb +7 -0
- data/test/dummy/app/views/layouts/application.html.erb +12 -0
- data/test/dummy/app/views/user_mailer/invite.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +7 -0
- data/test/dummy/config/database.yml.travis +12 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +49 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +45 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/sidejobs.rb +7 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/migrate/20161022211612_create_sidejobs.rb +25 -0
- data/test/dummy/db/schema.rb +37 -0
- data/test/dummy/log/development.log +228 -0
- data/test/dummy/log/sidejobs.log +1782 -0
- data/test/dummy/log/test.log +14856 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/generator_test.rb +20 -0
- data/test/processor_test.rb +49 -0
- data/test/queue_test.rb +69 -0
- data/test/test_helper.rb +14 -0
- metadata +212 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13c5ef1d9d233c888d758b942da10a980b46d75d
|
4
|
+
data.tar.gz: 7efac020aee70f251f7ce5d2682899d078e5a2bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d50fda67d9726aa4bf94aabba4defcb04d7ec19e45b23060ab16e3b345b0e615183b10430d8ec68af4c240cc6af34c7f5087cbf7a3bee0f9ecdc72d7609931e8
|
7
|
+
data.tar.gz: d7a52d18d78bcfe948ab762399bcab78592209042ca9b0749398db947ac82100e76fd8813fc856a196bc0ff84ae5407ec5333dc508d57f404baae17ae5b71a4b
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Mathías Montossi
|
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.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
[](http://badge.fury.io/rb/sidejobs)
|
2
|
+
[](https://codeclimate.com/github/mmontossi/sidejobs)
|
3
|
+
[](https://travis-ci.org/mmontossi/sidejobs)
|
4
|
+
[](https://gemnasium.com/mmontossi/sidejobs)
|
5
|
+
|
6
|
+
# Sidejobs
|
7
|
+
|
8
|
+
Versatile async database based jobs for rails.
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
Put this line in your Gemfile:
|
13
|
+
```ruby
|
14
|
+
gem 'sidejobs'
|
15
|
+
```
|
16
|
+
|
17
|
+
Then bundle:
|
18
|
+
```
|
19
|
+
$ bundle
|
20
|
+
```
|
21
|
+
|
22
|
+
## Configuration
|
23
|
+
|
24
|
+
Generate the sidejobs configuration and migration file:
|
25
|
+
```
|
26
|
+
bundle exec rails g sidejobs:install
|
27
|
+
```
|
28
|
+
|
29
|
+
The default configuration options are:
|
30
|
+
```ruby
|
31
|
+
Sidejobs.configure do |config|
|
32
|
+
config.max_attempts = 3
|
33
|
+
config.sleep_delay = 15
|
34
|
+
config.batch_size = 20
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Run the migration to create the sidejobs table:
|
39
|
+
```
|
40
|
+
bundle exec rake db:migrate
|
41
|
+
```
|
42
|
+
|
43
|
+
Assign the sidejobs adapter to the environments:
|
44
|
+
```ruby
|
45
|
+
Rails.application.configure do
|
46
|
+
config.active_job.queue_adapter = :sidejobs
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Usage
|
51
|
+
|
52
|
+
Start the daemon using the rake task:
|
53
|
+
```
|
54
|
+
bundle exec rake sidejobs:start
|
55
|
+
```
|
56
|
+
|
57
|
+
Now you can deliver mails using deliver_later:
|
58
|
+
```ruby
|
59
|
+
UserMailer.invite('someone@mail.com').deliver_later
|
60
|
+
```
|
61
|
+
|
62
|
+
Or perform jobs using perform_later:
|
63
|
+
```ruby
|
64
|
+
SendNewsletterJob.perform_later
|
65
|
+
```
|
66
|
+
|
67
|
+
Management is done programtically using Sidejobs::Job model:
|
68
|
+
```ruby
|
69
|
+
Sidejobs::Job.failing.where('attempts > ?', 3).destroy_all
|
70
|
+
```
|
71
|
+
|
72
|
+
NOTE: Is better to do it this way to have the freedom to integrate the code anyway you want.
|
73
|
+
|
74
|
+
## Credits
|
75
|
+
|
76
|
+
This gem is maintained and funded by [mmontossi](https://github.com/mmontossi).
|
77
|
+
|
78
|
+
## License
|
79
|
+
|
80
|
+
It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
Bundler::GemHelper.install_tasks
|
8
|
+
|
9
|
+
require 'rake/testtask'
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = false
|
16
|
+
end
|
17
|
+
|
18
|
+
task default: :test
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Sidejobs
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
9
|
+
|
10
|
+
def create_configuration_file
|
11
|
+
copy_file 'configuration.rb', 'config/initializers/sidejobs.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_migration_file
|
15
|
+
migration_template 'migration.rb', 'db/migrate/create_sidejobs.rb'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.next_migration_number(path)
|
19
|
+
Time.now.utc.strftime '%Y%m%d%H%M%S'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class CreateSidejobs < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :sidejobs do |t|
|
4
|
+
if ActiveRecord::Base.connection_config[:adapter] == 'postgresql'
|
5
|
+
t.jsonb :data
|
6
|
+
else
|
7
|
+
t.string :data
|
8
|
+
end
|
9
|
+
t.string :queue
|
10
|
+
t.string :status, default: 'pending'
|
11
|
+
t.integer :priority, default: 0
|
12
|
+
t.integer :attempts, default: 0
|
13
|
+
t.text :error
|
14
|
+
t.datetime :failed_at
|
15
|
+
t.datetime :completed_at
|
16
|
+
t.datetime :processed_at
|
17
|
+
t.datetime :scheduled_at
|
18
|
+
|
19
|
+
t.timestamps null: false
|
20
|
+
end
|
21
|
+
|
22
|
+
add_index :sidejobs, %i(status scheduled_at attempts)
|
23
|
+
add_index :sidejobs, :priority
|
24
|
+
end
|
25
|
+
end
|
data/lib/sidejobs.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'sidejobs/extensions/active_job/queue_adapter'
|
2
|
+
require 'sidejobs/configuration'
|
3
|
+
require 'sidejobs/daemon'
|
4
|
+
require 'sidejobs/job'
|
5
|
+
require 'sidejobs/processor'
|
6
|
+
require 'sidejobs/queue'
|
7
|
+
require 'sidejobs/railtie'
|
8
|
+
require 'sidejobs/version'
|
9
|
+
|
10
|
+
module Sidejobs
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def daemon
|
14
|
+
@daemon ||= Daemon.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def queue
|
18
|
+
@queue ||= Queue.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def logger
|
22
|
+
@logger ||= Logger.new(Rails.root.join('log/sidejobs.log'))
|
23
|
+
end
|
24
|
+
|
25
|
+
def configuration
|
26
|
+
@configuration ||= Configuration.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def configure
|
30
|
+
yield configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Sidejobs
|
2
|
+
class Daemon
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@stopping = false
|
6
|
+
end
|
7
|
+
|
8
|
+
def running?
|
9
|
+
if pid
|
10
|
+
begin
|
11
|
+
Process.kill 0, pid
|
12
|
+
true
|
13
|
+
rescue Errno::ESRCH
|
14
|
+
false
|
15
|
+
end
|
16
|
+
else
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
unless running?
|
23
|
+
daemonize
|
24
|
+
write_pid
|
25
|
+
trap_signals
|
26
|
+
process
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
if running?
|
32
|
+
Process.kill :TERM, pid
|
33
|
+
delete_pid
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def restart
|
38
|
+
if running?
|
39
|
+
stop
|
40
|
+
start
|
41
|
+
else
|
42
|
+
start
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def pid
|
47
|
+
File.read(pid_path).to_i rescue nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def stopping?
|
51
|
+
@stopping == true
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def daemonize
|
57
|
+
exit if fork
|
58
|
+
Process.setsid
|
59
|
+
exit if fork
|
60
|
+
Dir.chdir '/'
|
61
|
+
File.umask 0000
|
62
|
+
$stdout.reopen log_path, 'a'
|
63
|
+
$stderr.reopen $stdout
|
64
|
+
$stdout.sync = true
|
65
|
+
end
|
66
|
+
|
67
|
+
def trap_signals
|
68
|
+
trap :TERM do
|
69
|
+
@stopping = true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_pid
|
74
|
+
File.unlink pid_path
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_pid
|
78
|
+
FileUtils.mkdir_p pid_path.dirname
|
79
|
+
File.write pid_path, Process.pid
|
80
|
+
end
|
81
|
+
|
82
|
+
def log_path
|
83
|
+
Rails.root.join 'log/sidejobs.log'
|
84
|
+
end
|
85
|
+
|
86
|
+
def pid_path
|
87
|
+
Rails.root.join 'tmp/pids/sidejobs.pid'
|
88
|
+
end
|
89
|
+
|
90
|
+
def processor
|
91
|
+
@processor ||= Processor.new
|
92
|
+
end
|
93
|
+
|
94
|
+
def process
|
95
|
+
Sidejobs.logger.info 'Starting'
|
96
|
+
until stopping? do
|
97
|
+
processor.process
|
98
|
+
sleep Sidejobs.configuration.sleep_delay
|
99
|
+
end
|
100
|
+
Sidejobs.logger.info 'Stopping'
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Sidejobs
|
2
|
+
module Extensions
|
3
|
+
module ActiveJob
|
4
|
+
module QueueAdapters
|
5
|
+
class SidejobsAdapter
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def enqueue(job)
|
9
|
+
Sidejobs.queue.add(
|
10
|
+
job.serialize,
|
11
|
+
queue: job.queue_name,
|
12
|
+
priority: calculate_priority(job.queue_name)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def enqueue_at(job, timestamp)
|
17
|
+
Sidejobs.queue.add(
|
18
|
+
job.serialize,
|
19
|
+
queue: job.queue_name,
|
20
|
+
priority: calculate_priority(job.queue_name),
|
21
|
+
schedule_at: Time.at(timestamp)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def calculate_priority(queue)
|
28
|
+
case queue
|
29
|
+
when 'mailers'
|
30
|
+
80
|
31
|
+
when 'high_priority'
|
32
|
+
60
|
33
|
+
when 'default'
|
34
|
+
40
|
35
|
+
when 'low_priority'
|
36
|
+
20
|
37
|
+
else
|
38
|
+
0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/sidejobs/job.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sidejobs
|
2
|
+
class Job < ActiveRecord::Base
|
3
|
+
self.table_name = 'sidejobs'
|
4
|
+
STATUS = %w(pending processing failing complete)
|
5
|
+
|
6
|
+
STATUS.each do |name|
|
7
|
+
scope name, -> { where(status: name) }
|
8
|
+
define_method "#{name}?" do
|
9
|
+
status == name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
validates_presence_of :queue, :data, :status
|
14
|
+
validates_presence_of :error, :failed_at, if: :failing?
|
15
|
+
validates_presence_of :completed_at, if: :complete?
|
16
|
+
validates_presence_of :processed_at, if: :processing?
|
17
|
+
validates_inclusion_of :status, within: STATUS
|
18
|
+
validates_numericality_of :priority, :attempts, only_integer: true
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sidejobs
|
2
|
+
class Processor
|
3
|
+
|
4
|
+
def process
|
5
|
+
original_logger = ActiveJob::Base.logger
|
6
|
+
ActiveJob::Base.logger = Sidejobs.logger
|
7
|
+
Sidejobs.queue.fetch.each do |job|
|
8
|
+
Sidejobs.logger.info "Processing ##{job.id} attempt #{job.attempts} of #{job.data['job_class']}"
|
9
|
+
job.update status: 'processing', processed_at: Time.now, attempts: job.attempts+1
|
10
|
+
begin
|
11
|
+
ActiveJob::Base.execute job.data
|
12
|
+
job.update status: 'complete', completed_at: Time.now
|
13
|
+
Sidejobs.logger.info 'Done'
|
14
|
+
rescue => exception
|
15
|
+
job.update status: 'failing', failed_at: Time.now, error: exception.message
|
16
|
+
Sidejobs.logger.info "Error: #{exception.message}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
ActiveJob::Base.logger = original_logger
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sidejobs
|
2
|
+
class Queue
|
3
|
+
|
4
|
+
def add(data, options={})
|
5
|
+
Job.create(
|
6
|
+
data: data.to_json,
|
7
|
+
queue: (options[:queue] || 'default'),
|
8
|
+
priority: (options[:priority] || 0),
|
9
|
+
scheduled_at: options[:schedule_at]
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch
|
14
|
+
Job.where(status: %w(pending failing)).where(
|
15
|
+
'attempts < ?',
|
16
|
+
Sidejobs.configuration.max_attempts
|
17
|
+
).where(
|
18
|
+
'scheduled_at <= ? OR scheduled_at IS NULL',
|
19
|
+
Time.now
|
20
|
+
).order(priority: :desc).limit(Sidejobs.configuration.batch_size)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|