rails-mongoid-scheduler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7400669410e9e4b5a7c85e4add4c6a6d0767f02ac4f52818e816c4dcc90391c8
4
+ data.tar.gz: de7abad7b8984bfbdb5512f69480eca81fc493d9c9b48582d1cab9e58eb31456
5
+ SHA512:
6
+ metadata.gz: ff83ba06f6652263118cfe78c0d2f869ef1ae1e13dee21049872988a3464a0cf6fce7843bfae2801c069be0d4523c3fd518d4c95d618f62a729952b9a8b33f2a
7
+ data.tar.gz: b3a7de098fc62ba744e425a5c4224fa9e590b12fec074d43db440882b7edc917a628a02c731236ee98993eacfbc5fadc716fda60cbe68d750c96d7da90e907d3
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2018 Francesco Ballardin
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,48 @@
1
+ # Scheduler
2
+ This gem aims to create a simple yet efficient framework to handle job scheduling and execution. Currently it supports only MongoDB as database.
3
+
4
+ ## Installation
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'scheduler'
9
+ ```
10
+
11
+ And then execute:
12
+ ```bash
13
+ $ bundle install
14
+ ```
15
+
16
+ And then install the config file with:
17
+ ```bash
18
+ $ rails generate scheduler:config
19
+ ```
20
+
21
+ ## Usage
22
+ This gem adds a `Scheduler` module which can be started, stopped or restarted with their corresponding rake task:
23
+ ```bash
24
+ $ rails scheduler:start
25
+ $ rails scheduler:stop
26
+ $ rails scheduler:restart
27
+ ```
28
+ A `Scheduler` is a process that keeps running looking for jobs to perform. The jobs are documents of a specific collection that you can specify in the scheduler configuration file. You can specify your own model to act as a schedulable entity, as long as it includes the `Schedulable` module. The other configuration options are explained in the generated `config/initializers/scheduler.rb` file.
29
+
30
+ This gem also gives you a base ActiveJob implementation, called `SchedulerJob`, which you can subclass in order to implement your jobs.
31
+
32
+ As an example, the gem comes with a `ExampleSchedulableModel` which is a bare class that just includes the `Schedulable` module, and also an `ExampleSchedulerJob` job which is a bare implementation of the `SchedulableJob` job that just sleeps for a given amount of time.
33
+
34
+ First start by running the scheduler:
35
+ ```bash
36
+ $ rails scheduler:start
37
+ ```
38
+
39
+ You can then queue or run jobs by calling:
40
+ ```ruby
41
+ ExampleSchedulableModel.perform_later('ExampleSchedulableJob', 10) # to queue
42
+ ExampleSchedulableModel.perform_now('ExampleSchedulableJob', 10) # to perform immediately
43
+ ```
44
+ Both methods create a document of `ExampleSchedulableModel` and put it in queue.
45
+ The __perform_now__ method skips the scheduler and performs the job immediately, instead the __perform_later__ leaves the performing task to the scheduler.
46
+
47
+ ## License
48
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,29 @@
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
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Scheduler'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ load 'rails/tasks/statistics.rake'
18
+
19
+ require 'bundler/gem_tasks'
20
+
21
+ require 'rake/testtask'
22
+
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << 'test'
25
+ t.pattern = 'test/**/*_test.rb'
26
+ t.verbose = false
27
+ end
28
+
29
+ task default: :test
File without changes
@@ -0,0 +1,18 @@
1
+ class ExampleSchedulerJob < SchedulerJob
2
+
3
+ ##
4
+ # Performs job with ActiveJob framework.
5
+ #
6
+ # @param [String] job_id the id of the corresponding Job.
7
+ # @param [Integer] work_time an example amount of time to simulate work.
8
+ #
9
+ # @return [nil]
10
+ def perform(job_class, job_id, *args, &block)
11
+ super do |job, work_time|
12
+ job.log :info, "Preparing to do some work for #{work_time} seconds."
13
+ sleep work_time
14
+ job.log :info, "Work done!"
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,55 @@
1
+ class SchedulerJob < ActiveJob::Base
2
+
3
+ queue_as :default
4
+
5
+ ##
6
+ # Performs job with ActiveJob framework.
7
+ #
8
+ # @param [String] job_class the class of the corresponding Job.
9
+ # @param [String] job_id the id of the corresponding Job.
10
+ # @param [Array] *args additional arguments.
11
+ # @param [Proc] &block extra block to define custom jobs.
12
+ #
13
+ # @return [nil]
14
+ def perform(job_class, job_id, *args, &block)
15
+ begin
16
+ @job = job_class.constantize.find(job_id)
17
+ @job.executed_at = Time.current
18
+ @job.status!(:running)
19
+ yield @job, *args if block_given?
20
+ @job.completed_at = Time.current
21
+ @job.progress!(100)
22
+ @job.status!(:completed)
23
+ rescue StandardError => error
24
+ handle_error(error, job_class, job_id)
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Method to handle any error raised when performing a job.
30
+ #
31
+ # @param [StandardError] error the raised error.
32
+ # @param [String] job_class the class of the corresponding Job.
33
+ # @param [String] job_id the id of the corresponding Job.
34
+ # @param [Proc] &block extra block to define custom error handling.
35
+ #
36
+ # @return [nil]
37
+ def handle_error(error, job_class, job_id, &block)
38
+ if @job.present?
39
+ @job.completed_at = Time.current
40
+ if block_given?
41
+ yield error
42
+ else
43
+ backtrace = error.backtrace.select { |line| line.include?('app') }.join("\n")
44
+ @job.log(:error, "#{error.class}: #{error.message}")
45
+ @job.log(:error, backtrace)
46
+ @job.backtrace = backtrace
47
+ @job.status!(:error)
48
+ end
49
+ @job.save
50
+ else
51
+ raise "Unable to find #{job_class} with id '#{job_id}'."
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,5 @@
1
+ class ExampleSchedulableModel
2
+
3
+ include Scheduler::Schedulable
4
+
5
+ end
@@ -0,0 +1,201 @@
1
+ module Scheduler
2
+ module Schedulable
3
+
4
+ ##
5
+ # Possible schedulable statuses.
6
+ STATUSES = [ :queued, :running, :completed, :error, :locked ]
7
+
8
+ ##
9
+ # Possible log levels.
10
+ LOG_LEVELS = [ :debug, :info, :warn, :error ]
11
+
12
+ def self.included(base)
13
+
14
+ base.class_eval do
15
+ include Mongoid::Document
16
+
17
+ field :class_name, type: String
18
+ field :args, type: Array, default: []
19
+ field :scheduled_at, type: DateTime
20
+ field :executed_at, type: DateTime
21
+ field :completed_at, type: DateTime
22
+ field :pid, type: Integer
23
+ field :status, type: Symbol, default: :queued
24
+ field :logs, type: Array, default: []
25
+ field :progress, type: Float, default: 0.0
26
+ field :error, type: String
27
+ field :backtrace, type: String
28
+
29
+ scope :queued, -> { where(status: :queued) }
30
+ scope :running, -> { where(status: :running) }
31
+ scope :completed, -> { where(status: :completed) }
32
+ scope :in_error, -> { where(status: :error) }
33
+ scope :locked, -> { where(status: :locked) }
34
+
35
+ validates_presence_of :class_name
36
+
37
+ after_save :broadcast
38
+
39
+ class << self
40
+
41
+ ##
42
+ # Returns possible statuses.
43
+ #
44
+ # @return [Array<Symbol>] possible statuses.
45
+ def statuses
46
+ Scheduler::Schedulable::STATUSES
47
+ end
48
+
49
+ ##
50
+ # Returns possible log levels.
51
+ #
52
+ # @return [Array<Symbol>] possible log levels.
53
+ def log_levels
54
+ Scheduler::Schedulable::LOG_LEVELS
55
+ end
56
+
57
+ ##
58
+ # Returns the corresponding log color if this level.
59
+ #
60
+ # @param [Symbol] level log level.
61
+ #
62
+ # @return [Symbol] the color.
63
+ def log_color(level)
64
+ case level
65
+ when :debug then :green
66
+ when :info then :cyan
67
+ when :warn then :yellow
68
+ when :error then :red
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Creates an instance of this class and calls :perform_later.
74
+ #
75
+ # @param [String] job_class the class of the job to run.
76
+ # @param [Array] *job_args job arguments
77
+ #
78
+ # @return [Object] the created job.
79
+ def perform_later(job_class, *job_args)
80
+ self.create(class_name: job_class, args: job_args).perform_later
81
+ end
82
+
83
+ ##
84
+ # Creates an instance of this class and calls :perform_now.
85
+ #
86
+ # @param [String] job_class the class of the job to run.
87
+ # @param [Array] *job_args job arguments
88
+ #
89
+ # @return [Object] the created job.
90
+ def perform_now(job_class, *job_args)
91
+ self.create(class_name: job_class, args: job_args).perform_now
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ ##
99
+ # Gets ActiveJob's job class.
100
+ #
101
+ # @return [Class] the ActiveJob's job class.
102
+ def job_class
103
+ self.class_name.constantize
104
+ end
105
+
106
+ ##
107
+ # Schedules the job.
108
+ #
109
+ # @return [Object] itself.
110
+ def schedule
111
+ self.scheduled_at = Time.current
112
+ self.status = :queued
113
+ self.logs = []
114
+ self.progress = 0.0
115
+ self.unset(:error)
116
+ self.unset(:backtrace)
117
+ self.unset(:completed_at)
118
+ self.unset(:executed_at)
119
+ self.save
120
+ if block_given?
121
+ yield self
122
+ else self end
123
+ end
124
+
125
+ ##
126
+ # Performs job when queue is available.
127
+ # On test or development env, the job is performed by ActiveJob queue,
128
+ # if configured on the Scheduler configuration.
129
+ # On production env, the job is performed only with a Scheduler::MainProcess.
130
+ #
131
+ # @return [Object] the job class.
132
+ def perform_later
133
+ self.schedule
134
+ if Rails.env.development? or Rails.env.test?
135
+ if Scheduler.configuration.perform_jobs_in_test_or_development
136
+ job_class.set(wait: 5.seconds).perform_later(self.class.name, self.id.to_s, *self.args)
137
+ end
138
+ end
139
+ self
140
+ end
141
+
142
+ ##
143
+ # Performs job when queue is available.
144
+ # On test or development env, the job is performed with ActiveJob.
145
+ # On production env, the job is performed with Scheduler.
146
+ #
147
+ # @return [Object] the job class.
148
+ def perform_now
149
+ self.schedule
150
+ job_class.perform_now(self.class.name, self.id.to_s, *self.args)
151
+ self
152
+ end
153
+
154
+ ##
155
+ # Immediately update the status to the given one.
156
+ #
157
+ # @param [Symbol] status the status to update.
158
+ #
159
+ # @return [nil]
160
+ def status!(status)
161
+ self.update(status: status)
162
+ end
163
+
164
+ ##
165
+ # Immediately increases progress to the given amount.
166
+ #
167
+ # @param [Float] amount the given progress amount.
168
+ def progress!(amount)
169
+ self.update(progress: amount.to_f) if amount.numeric?
170
+ end
171
+
172
+ ##
173
+ # Immediately increases progress by the given amount.
174
+ #
175
+ # @param [Float] amount the given progress amount.
176
+ def progress_by!(amount)
177
+ self.update(progress: progress + amount.to_f) if amount.numeric?
178
+ end
179
+
180
+ ##
181
+ # Registers a log message with the given level.
182
+ def log(level, message)
183
+ raise ArgumentError.new("The given log level '#{level}' is not valid. "\
184
+ "Valid log levels are: #{LOG_LEVELS.join(', ')}") unless level.in? LOG_LEVELS
185
+ Scheduler.configuration.logger.send level,
186
+ "[#{self.class}:#{self.id}] #{message}".send(self.class.log_color level)
187
+ self.update(logs: logs.push([level, message]))
188
+ end
189
+
190
+ ##
191
+ # Broadcasts a job updating event.
192
+ #
193
+ # @return [nil]
194
+ def broadcast
195
+ { status: status, logs: logs }
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,18 @@
1
+ require 'rails/generators'
2
+
3
+ module Scheduler
4
+ module Generators
5
+
6
+ class ConfigGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("../../templates", __FILE__)
8
+
9
+ desc "Creates a scheduler configuration file."
10
+
11
+ def copy_config
12
+ template "scheduler.rb", "config/initializers/scheduler.rb"
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ ##
2
+ # Example configuration file for rails-scheduler gem.
3
+ Scheduler.configure do |config|
4
+
5
+ ##
6
+ # A logger interface to save scheduler logs.
7
+ # Defaults to Rails.logger.
8
+ #
9
+ # config.logger = Rails.logger
10
+
11
+ ##
12
+ # A custom class to handle the execution of jobs.
13
+ # This class must include Scheduler::Schedulable module
14
+ # in order to work.
15
+ # Defaults to ExampleSchedulableModel, which is a bare class that
16
+ # just includes Scheduler::Schedulable module.
17
+ #
18
+ # config.job_class = ExampleSchedulableModel
19
+
20
+ ##
21
+ # How often the scheduler has to check for new jobs to run.
22
+ # Defaults to 5 seconds.
23
+ #
24
+ # config.polling_interval = 5
25
+
26
+ ##
27
+ # How many jobs can run at a given time.
28
+ # Defaults to the minimum value between the number of the current
29
+ # machine CPU cores or 24.
30
+ #
31
+ # config.max_concurrent_jobs = [ Etc.nprocessors, 24 ].min
32
+
33
+ ##
34
+ # Sets whether to perform jobs when in test or development env.
35
+ # Usually jobs are performed only when a Scheduler::MainProcess is running.
36
+ # But for convenience, you can set this parameter to true so you
37
+ # don't need to keep a Scheduler::MainProcess running.
38
+ # Defaults to false.
39
+ #
40
+ # config.perform_jobs_in_test_or_development = false
41
+
42
+ end
data/lib/scheduler.rb ADDED
@@ -0,0 +1,88 @@
1
+ require "scheduler/engine"
2
+ require "scheduler/configuration"
3
+ require "scheduler/main_process"
4
+
5
+ module Scheduler
6
+
7
+ class << self
8
+
9
+ # @return [Scheduler::Configuration] the configuration class for Scheduler.
10
+ attr_accessor :configuration
11
+
12
+ ##
13
+ # Initializes configuration.
14
+ #
15
+ # @return [Scheduler::Configuration] the configuration class for Scheduler.
16
+ def configuration
17
+ @configuration || Scheduler::Configuration.new
18
+ end
19
+
20
+ ##
21
+ # Method to configure various Scheduler options.
22
+ #
23
+ # @return [nil]
24
+ def configure
25
+ @configuration ||= Scheduler::Configuration.new
26
+ yield @configuration
27
+ end
28
+
29
+ ##
30
+ # Gets scheduler pid file.
31
+ #
32
+ # @return [String] the pid file.
33
+ def pid_file
34
+ '/tmp/rails-scheduler.pid'
35
+ end
36
+
37
+ ##
38
+ # Gets scheduler main process pid.
39
+ #
40
+ # @return [Integer] main process pid.
41
+ def pid
42
+ File.read(self.pid_file).to_i rescue nil
43
+ end
44
+
45
+ ##
46
+ # Starts a Scheduler::MainProcess in a separate process.
47
+ #
48
+ # @return [nil]
49
+ def start
50
+ scheduler_pid = Process.fork do
51
+ begin
52
+ Process.daemon(true)
53
+ File.open(self.pid_file, 'w+') do |pidfile|
54
+ pidfile.puts Process.pid
55
+ end
56
+ scheduler = Scheduler::MainProcess.new
57
+ rescue StandardError => e
58
+ Rails.logger.error "#{e.class}: #{e.message} (#{e.backtrace.first})".red
59
+ end
60
+ end
61
+ Process.detach(scheduler_pid)
62
+ scheduler_pid
63
+ end
64
+
65
+ ##
66
+ # Reschedules all running jobs and stops the scheduler main process.
67
+ #
68
+ # @return [nil]
69
+ def stop
70
+ begin
71
+ Process.kill :TERM, Scheduler.pid
72
+ FileUtils.rm(self.pid_file)
73
+ rescue Errno::ENOENT, Errno::ESRCH
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Restarts the scheduler.
79
+ #
80
+ # @return [nil]
81
+ def restart
82
+ self.stop
83
+ self.start
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,24 @@
1
+ module Scheduler
2
+ class Configuration
3
+
4
+ # @return [String] a logger file.
5
+ attr_accessor :logger
6
+ # @return [Class] the class of the main job model.
7
+ attr_accessor :job_class
8
+ # @return [Integer] how much time to wait before each iteration.
9
+ attr_accessor :polling_interval
10
+ # @return [Integer] maximum number of concurent jobs.
11
+ attr_accessor :max_concurrent_jobs
12
+ # @return [Boolean] whether to perform jobs when in test or development env.
13
+ attr_accessor :perform_jobs_in_test_or_development
14
+
15
+ def initialize
16
+ @logger = Rails.logger
17
+ @job_class = ExampleSchedulableModel
18
+ @polling_interval = 5
19
+ @max_concurrent_jobs = [ Etc.nprocessors, 24 ].min
20
+ @perform_jobs_in_test_or_development = false
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ module Scheduler
2
+ class Engine < ::Rails::Engine
3
+
4
+ # Loading required gems
5
+ require 'mongoid'
6
+
7
+ end
8
+ end
@@ -0,0 +1,136 @@
1
+ module Scheduler
2
+ class MainProcess
3
+
4
+ # @return [Integer] pid of the main process.
5
+ attr_accessor :pid
6
+ # @return [String] a logger file.
7
+ attr_accessor :logger
8
+ # @return [Class] the class of the main job model.
9
+ attr_accessor :job_class
10
+ # @return [Integer] how much time to wait before each iteration.
11
+ attr_accessor :polling_interval
12
+ # @return [Integer] maximum number of concurent jobs.
13
+ attr_accessor :max_concurrent_jobs
14
+
15
+ ##
16
+ # Creates a MainProcess which keeps running
17
+ # and continuously checks if new jobs are queued.
18
+ #
19
+ # @return [Scheduler::MainProcess] the created MainProcess.
20
+ def initialize
21
+ @pid = Process.pid
22
+ @logger = Scheduler.configuration.logger
23
+ @job_class = Scheduler.configuration.job_class
24
+ @polling_interval = Scheduler.configuration.polling_interval
25
+ @max_concurrent_jobs = Scheduler.configuration.max_concurrent_jobs
26
+
27
+ unless @logger.instance_of?(ActiveSupport::Logger) or @logger.instance_of?(Logger)
28
+ @logger = Logger.new(@logger)
29
+ end
30
+
31
+ if @polling_interval < 1
32
+ @logger.warn "[Scheduler:#{@pid}] Warning: specified a polling interval lesser than 1: "\
33
+ "it will be forced to 1.".yellow
34
+ @polling_interval = 1
35
+ end
36
+
37
+ unless @job_class.included_modules.include? Scheduler::Schedulable
38
+ raise "The given job class '#{@job_class}' is not a Schedulable class. "\
39
+ "Make sure to add 'include Scheduler::Schedulable' to your class."
40
+ end
41
+
42
+ @logger.info "[Scheduler:#{@pid}] Starting scheduler..".cyan
43
+ self.start_loop
44
+ end
45
+
46
+ ##
47
+ # Main loop.
48
+ #
49
+ # @return [nil]
50
+ def start_loop
51
+ loop do
52
+ begin
53
+ # Loads up a job queue.
54
+ queue = []
55
+
56
+ # Counts jobs to schedule.
57
+ running_jobs = @job_class.running.entries
58
+ schedulable_jobs = @job_class.queued.order_by(scheduled_at: :asc).entries
59
+ jobs_to_schedule = @max_concurrent_jobs - running_jobs.count
60
+ jobs_to_schedule = 0 if jobs_to_schedule < 0
61
+
62
+ # Finds out scheduled jobs waiting to be performed.
63
+ scheduled_jobs = []
64
+ schedulable_jobs.first(jobs_to_schedule).each do |job|
65
+ job_pid = Process.fork do
66
+ begin
67
+ job.perform_now
68
+ rescue StandardError => e
69
+ @logger.error "[Scheduler:#{@pid}] Error #{e.class}: #{e.message} "\
70
+ "(#{e.backtrace.select { |l| l.include?('app') }.first}).".red
71
+ end
72
+ end
73
+ Process.detach(job_pid)
74
+ job.update_attribute(:pid, job_pid)
75
+ scheduled_jobs << job
76
+ queue << job.id.to_s
77
+ end
78
+
79
+ # Logs launched jobs
80
+ if scheduled_jobs.any?
81
+ @logger.info "[Scheduler:#{@pid}] Launched #{scheduled_jobs.count} "\
82
+ "jobs: #{scheduled_jobs.map(&:id).map(&:to_s).join(', ')}.".cyan
83
+ else
84
+ if schedulable_jobs.count == 0
85
+ @logger.info "[Scheduler:#{@pid}] No jobs in queue.".cyan
86
+ else
87
+ @logger.warn "[Scheduler:#{@pid}] No jobs launched, reached maximum "\
88
+ "number of concurrent jobs. Jobs in queue: #{schedulable_jobs.count}.".yellow
89
+ end
90
+ end
91
+
92
+ # Checks for completed jobs: clears up queue and kills any zombie pid
93
+ queue.delete_if do |job_id|
94
+ job = @job_class.find(job_id)
95
+ if job.present? and job.status.in? [ :completed, :error ]
96
+ begin
97
+ @logger.info "[Scheduler:#{@pid}] Rimosso processo #{job.pid} per lavoro completato".cyan
98
+ Process.kill :QUIT, job.pid
99
+ rescue Errno::ENOENT, Errno::ESRCH
100
+ end
101
+ true
102
+ else false end
103
+ end
104
+
105
+ # Waits the specified amount of time before next iteration
106
+ sleep @polling_interval
107
+ rescue StandardError => error
108
+ @logger.error "[Scheduler:#{@pid}] Error #{error.message}".red
109
+ @logger.error error.backtrace.select { |line| line.include?('app') }.join("\n").red
110
+ rescue SignalException => signal
111
+ if signal.message.in? [ 'SIGINT', 'SIGTERM', 'SIGQUIT' ]
112
+ @logger.warn "[Scheduler:#{@pid}] Received interrupt, terminating scheduler..".yellow
113
+ reschedule_running_jobs
114
+ break
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ ##
121
+ # Reschedules currently running jobs.
122
+ #
123
+ # @return [nil]
124
+ def reschedule_running_jobs
125
+ @job_class.running.each do |job|
126
+ begin
127
+ Process.kill :QUIT, job.pid if job.pid.present?
128
+ rescue Errno::ESRCH, Errno::EPERM
129
+ ensure
130
+ job.schedule
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,3 @@
1
+ module Scheduler
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,18 @@
1
+ namespace :scheduler do
2
+
3
+ desc 'Scheduler Start'
4
+ task :start => :environment do |t, args|
5
+ Scheduler.start
6
+ end
7
+
8
+ desc 'Scheduler Stop'
9
+ task :stop => :environment do |t, args|
10
+ Scheduler.stop
11
+ end
12
+
13
+ desc 'Scheduler Restart'
14
+ task :restart => :environment do |t, args|
15
+ Scheduler.restart
16
+ end
17
+
18
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-mongoid-scheduler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Francesco Ballardin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: mongoid
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 7.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 7.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: bson_ext
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: whenever
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rails-dev-tools
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A Rails engine to schedule jobs, handle parallel execution and manage
84
+ the jobs queue.
85
+ email:
86
+ - francesco.ballardin@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - MIT-LICENSE
92
+ - README.md
93
+ - Rakefile
94
+ - app/assets/config/scheduler_manifest.js
95
+ - app/jobs/example_scheduler_job.rb
96
+ - app/jobs/scheduler_job.rb
97
+ - app/models/example_schedulable_model.rb
98
+ - app/models/scheduler/schedulable.rb
99
+ - config/routes.rb
100
+ - lib/generators/scheduler/config_generator.rb
101
+ - lib/generators/templates/scheduler.rb
102
+ - lib/scheduler.rb
103
+ - lib/scheduler/configuration.rb
104
+ - lib/scheduler/engine.rb
105
+ - lib/scheduler/main_process.rb
106
+ - lib/scheduler/version.rb
107
+ - lib/tasks/scheduler_tasks.rake
108
+ homepage: https://github.com/Pluvie/rails-scheduler
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.7.6
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: A Rails engine to schedule jobs, handle parallel execution and manage the
132
+ jobs queue.
133
+ test_files: []