sidejobs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +80 -0
  4. data/Rakefile +18 -0
  5. data/lib/generators/sidejobs/install_generator.rb +24 -0
  6. data/lib/generators/sidejobs/templates/configuration.rb +7 -0
  7. data/lib/generators/sidejobs/templates/migration.rb +25 -0
  8. data/lib/sidejobs.rb +34 -0
  9. data/lib/sidejobs/configuration.rb +7 -0
  10. data/lib/sidejobs/daemon.rb +104 -0
  11. data/lib/sidejobs/extensions/active_job/queue_adapter.rb +47 -0
  12. data/lib/sidejobs/job.rb +21 -0
  13. data/lib/sidejobs/processor.rb +23 -0
  14. data/lib/sidejobs/queue.rb +24 -0
  15. data/lib/sidejobs/railtie.rb +15 -0
  16. data/lib/sidejobs/version.rb +3 -0
  17. data/lib/tasks/sidejobs.rake +11 -0
  18. data/test/daemon_test.rb +71 -0
  19. data/test/dummy/Rakefile +5 -0
  20. data/test/dummy/app/assets/javascripts/application.js +13 -0
  21. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  22. data/test/dummy/app/controllers/application_controller.rb +5 -0
  23. data/test/dummy/app/helpers/application_helper.rb +2 -0
  24. data/test/dummy/app/jobs/charge_subscriptions_job.rb +8 -0
  25. data/test/dummy/app/jobs/clear_guest_users_job.rb +8 -0
  26. data/test/dummy/app/jobs/send_newsletters_job.rb +8 -0
  27. data/test/dummy/app/jobs/share_products_job.rb +8 -0
  28. data/test/dummy/app/jobs/update_exchanges_job.rb +8 -0
  29. data/test/dummy/app/mailers/user_mailer.rb +7 -0
  30. data/test/dummy/app/views/layouts/application.html.erb +12 -0
  31. data/test/dummy/app/views/user_mailer/invite.text.erb +1 -0
  32. data/test/dummy/bin/bundle +3 -0
  33. data/test/dummy/bin/rails +4 -0
  34. data/test/dummy/bin/rake +4 -0
  35. data/test/dummy/bin/setup +29 -0
  36. data/test/dummy/config.ru +4 -0
  37. data/test/dummy/config/application.rb +25 -0
  38. data/test/dummy/config/boot.rb +5 -0
  39. data/test/dummy/config/database.yml +7 -0
  40. data/test/dummy/config/database.yml.travis +12 -0
  41. data/test/dummy/config/environment.rb +5 -0
  42. data/test/dummy/config/environments/development.rb +49 -0
  43. data/test/dummy/config/environments/production.rb +79 -0
  44. data/test/dummy/config/environments/test.rb +45 -0
  45. data/test/dummy/config/initializers/assets.rb +11 -0
  46. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  47. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  48. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  49. data/test/dummy/config/initializers/inflections.rb +16 -0
  50. data/test/dummy/config/initializers/mime_types.rb +4 -0
  51. data/test/dummy/config/initializers/session_store.rb +3 -0
  52. data/test/dummy/config/initializers/sidejobs.rb +7 -0
  53. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  54. data/test/dummy/config/locales/en.yml +23 -0
  55. data/test/dummy/config/routes.rb +56 -0
  56. data/test/dummy/config/secrets.yml +22 -0
  57. data/test/dummy/db/migrate/20161022211612_create_sidejobs.rb +25 -0
  58. data/test/dummy/db/schema.rb +37 -0
  59. data/test/dummy/log/development.log +228 -0
  60. data/test/dummy/log/sidejobs.log +1782 -0
  61. data/test/dummy/log/test.log +14856 -0
  62. data/test/dummy/public/404.html +67 -0
  63. data/test/dummy/public/422.html +67 -0
  64. data/test/dummy/public/500.html +66 -0
  65. data/test/dummy/public/favicon.ico +0 -0
  66. data/test/generator_test.rb +20 -0
  67. data/test/processor_test.rb +49 -0
  68. data/test/queue_test.rb +69 -0
  69. data/test/test_helper.rb +14 -0
  70. 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
+ [![Gem Version](https://badge.fury.io/rb/sidejobs.svg)](http://badge.fury.io/rb/sidejobs)
2
+ [![Code Climate](https://codeclimate.com/github/mmontossi/sidejobs/badges/gpa.svg)](https://codeclimate.com/github/mmontossi/sidejobs)
3
+ [![Build Status](https://travis-ci.org/mmontossi/sidejobs.svg)](https://travis-ci.org/mmontossi/sidejobs)
4
+ [![Dependency Status](https://gemnasium.com/mmontossi/sidejobs.svg)](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,7 @@
1
+ Sidejobs.configure do |config|
2
+
3
+ config.max_attempts = 3
4
+ config.sleep_delay = 15
5
+ config.batch_size = 20
6
+
7
+ 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,7 @@
1
+ module Sidejobs
2
+ class Configuration
3
+
4
+ attr_accessor :max_attempts, :sleep_delay, :batch_size
5
+
6
+ end
7
+ 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
@@ -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