mongodb-scheduler 0.1.0
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/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +73 -0
- data/README.md +46 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/scheduler +5 -0
- data/lib/scheduler/cli.rb +33 -0
- data/lib/scheduler/configuration.rb +30 -0
- data/lib/scheduler/examples/executable_class.rb +13 -0
- data/lib/scheduler/examples/schedulable_model.rb +9 -0
- data/lib/scheduler/main_process.rb +134 -0
- data/lib/scheduler/schedulable.rb +181 -0
- data/lib/scheduler/templates/scheduler.rb +54 -0
- data/lib/scheduler/version.rb +3 -0
- data/lib/scheduler.rb +131 -0
- data/lib/tasks/scheduler_tasks.rake +18 -0
- data/scheduler.gemspec +35 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1f5897d6a1c9c893e29684eeda0caf7df9483e5be15189394fd8ed0d38364e9d
|
4
|
+
data.tar.gz: 882ed7f055f70e508384ae99290c89b9192d6ba8970b8fd78dfb152148aec256
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cb04a2d1a3b86b7fddfaf18216a74b41e1616461fd8a823140126b9a8e8d7345951318b4413632d0789f60dbca06d5122ce22835f6b38d838f7f2e25666ce4fb
|
7
|
+
data.tar.gz: 51b1fc7b25deb36af0ca733dc836a719b641e8d1418242ee2fcb49dba23550bc9294b65491787534227b13c817e8bd5ada33c2b316718db7efde092b86e97e16
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
scheduler (0.1.0)
|
5
|
+
bson_ext
|
6
|
+
hanami-cli
|
7
|
+
hanami-mongoid
|
8
|
+
logger
|
9
|
+
mongoid
|
10
|
+
rainbow
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: https://rubygems.org/
|
14
|
+
specs:
|
15
|
+
activemodel (5.2.3)
|
16
|
+
activesupport (= 5.2.3)
|
17
|
+
activesupport (5.2.3)
|
18
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
|
+
i18n (>= 0.7, < 2)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
tzinfo (~> 1.1)
|
22
|
+
bson (4.4.2)
|
23
|
+
bson_ext (1.5.1)
|
24
|
+
concurrent-ruby (1.1.5)
|
25
|
+
diff-lcs (1.3)
|
26
|
+
hanami-cli (0.3.1)
|
27
|
+
concurrent-ruby (~> 1.0)
|
28
|
+
hanami-utils (~> 1.3)
|
29
|
+
hanami-mongoid (0.1.7)
|
30
|
+
mongoid
|
31
|
+
hanami-utils (1.3.1)
|
32
|
+
concurrent-ruby (~> 1.0)
|
33
|
+
transproc (~> 1.0)
|
34
|
+
i18n (1.6.0)
|
35
|
+
concurrent-ruby (~> 1.0)
|
36
|
+
logger (1.3.0)
|
37
|
+
minitest (5.11.3)
|
38
|
+
mongo (2.8.0)
|
39
|
+
bson (>= 4.4.2, < 5.0.0)
|
40
|
+
mongoid (7.0.2)
|
41
|
+
activemodel (>= 5.1, < 6.0.0)
|
42
|
+
mongo (>= 2.5.1, < 3.0.0)
|
43
|
+
rainbow (3.0.0)
|
44
|
+
rake (10.5.0)
|
45
|
+
rspec (3.8.0)
|
46
|
+
rspec-core (~> 3.8.0)
|
47
|
+
rspec-expectations (~> 3.8.0)
|
48
|
+
rspec-mocks (~> 3.8.0)
|
49
|
+
rspec-core (3.8.0)
|
50
|
+
rspec-support (~> 3.8.0)
|
51
|
+
rspec-expectations (3.8.2)
|
52
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
53
|
+
rspec-support (~> 3.8.0)
|
54
|
+
rspec-mocks (3.8.0)
|
55
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
56
|
+
rspec-support (~> 3.8.0)
|
57
|
+
rspec-support (3.8.0)
|
58
|
+
thread_safe (0.3.6)
|
59
|
+
transproc (1.0.3)
|
60
|
+
tzinfo (1.2.5)
|
61
|
+
thread_safe (~> 0.1)
|
62
|
+
|
63
|
+
PLATFORMS
|
64
|
+
ruby
|
65
|
+
|
66
|
+
DEPENDENCIES
|
67
|
+
bundler (~> 2.0)
|
68
|
+
rake (~> 10.0)
|
69
|
+
rspec (~> 3.0)
|
70
|
+
scheduler!
|
71
|
+
|
72
|
+
BUNDLED WITH
|
73
|
+
2.0.1
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Scheduler
|
2
|
+
This gem aims to create a simple yet efficient framework to handle job scheduling and execution. It is targeted for MongoDB database.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'mongodb-scheduler'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
```bash
|
13
|
+
$ bundle install
|
14
|
+
```
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
This gem adds a `Scheduler` module which can be started, stopped or restarted with their corresponding command:
|
18
|
+
```bash
|
19
|
+
$ scheduler start
|
20
|
+
$ scheduler stop
|
21
|
+
$ scheduler restart
|
22
|
+
```
|
23
|
+
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 template file `lib/scheduler/templates/scheduler.rb` file.
|
24
|
+
|
25
|
+
As an example, the gem comes with a `Scheduler::Examples::SchedulableModel` which is a bare class that just includes the `Scheduler::Schedulable` module, and also an `Scheduler::Examples::ExecutableClass` class which is a bare implementation of an executable class.
|
26
|
+
An executable class is just a plain Ruby class which must implement a `call` method which accepts one argument. This argument is an instance of the schedulable model that you configured.
|
27
|
+
|
28
|
+
First start by running the scheduler:
|
29
|
+
```bash
|
30
|
+
$ scheduler start
|
31
|
+
```
|
32
|
+
|
33
|
+
You can then queue jobs by calling:
|
34
|
+
```ruby
|
35
|
+
YourSchedulableModel.schedule('YourExecutableClass', args...) # to queue
|
36
|
+
```
|
37
|
+
Both methods create a document of `YourSchedulableModel` and put it in queue.
|
38
|
+
The __perform_now__ method skips the scheduler and performs the job immediately, instead the __perform_later__ leaves the performing task to the scheduler.
|
39
|
+
|
40
|
+
If you want to stop the scheduler, just run:
|
41
|
+
```bash
|
42
|
+
$ scheduler stop
|
43
|
+
```
|
44
|
+
|
45
|
+
## License
|
46
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "scheduler"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/scheduler
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "hanami/cli"
|
2
|
+
|
3
|
+
module Scheduler
|
4
|
+
class CLI
|
5
|
+
def call(*args)
|
6
|
+
Hanami::CLI.new(Commands).call(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
module Commands
|
10
|
+
extend Hanami::CLI::Registry
|
11
|
+
|
12
|
+
class Start < Hanami::CLI::Command
|
13
|
+
def call(*)
|
14
|
+
Scheduler.start
|
15
|
+
end
|
16
|
+
end
|
17
|
+
class Stop < Hanami::CLI::Command
|
18
|
+
def call(*)
|
19
|
+
Scheduler.stop
|
20
|
+
end
|
21
|
+
end
|
22
|
+
class Restart < Hanami::CLI::Command
|
23
|
+
def call(*)
|
24
|
+
Scheduler.restart
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
register "start", Start
|
29
|
+
register "stop", Stop
|
30
|
+
register "restart", Restart
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Scheduler
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
# @return [String] envinronment of your app
|
5
|
+
attr_accessor :environment
|
6
|
+
# @return [String] a path to Mongoid config file.
|
7
|
+
attr_accessor :mongoid_config_file
|
8
|
+
# @return [String] a path to the specified log file.
|
9
|
+
attr_accessor :log_file
|
10
|
+
# @return [Class] the class of the main job model.
|
11
|
+
attr_accessor :job_class
|
12
|
+
# @return [Integer] how much time to wait before each iteration.
|
13
|
+
attr_accessor :polling_interval
|
14
|
+
# @return [Integer] maximum number of concurent jobs.
|
15
|
+
attr_accessor :max_concurrent_jobs
|
16
|
+
# @return [Proc] whether to perform jobs when in test or development env.
|
17
|
+
attr_accessor :perform_jobs_in_test_or_development
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@environment = 'test'
|
21
|
+
@mongoid_config_file = File.join(Scheduler.root, 'spec/mongoid.yml')
|
22
|
+
@log_file = STDOUT
|
23
|
+
@job_class = Scheduler::Examples::SchedulableModel
|
24
|
+
@polling_interval = 5
|
25
|
+
@max_concurrent_jobs = [ Etc.nprocessors, 24 ].min
|
26
|
+
@perform_jobs_in_test_or_development = false
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,134 @@
|
|
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
|
+
@logger = Scheduler.logger
|
22
|
+
@pid = Process.pid
|
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
|
+
if @polling_interval < 1
|
28
|
+
logger.warn Rainbow("[Scheduler:#{@pid}] Warning: specified a polling interval lesser than 1: "\
|
29
|
+
"it will be forced to 1.").yellow
|
30
|
+
@polling_interval = 1
|
31
|
+
end
|
32
|
+
|
33
|
+
unless @job_class.included_modules.include? Scheduler::Schedulable
|
34
|
+
raise "The given job class '#{@job_class}' is not a Schedulable class. "\
|
35
|
+
"Make sure to add 'include Scheduler::Schedulable' to your class."
|
36
|
+
end
|
37
|
+
|
38
|
+
# Loads up a job queue.
|
39
|
+
@queue = []
|
40
|
+
|
41
|
+
@logger.info Rainbow("[Scheduler:#{@pid}] Starting main loop..").cyan
|
42
|
+
self.start_loop
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Main loop.
|
47
|
+
#
|
48
|
+
# @return [nil]
|
49
|
+
def start_loop
|
50
|
+
loop do
|
51
|
+
begin
|
52
|
+
# Counts jobs to schedule.
|
53
|
+
running_jobs = @job_class.running.entries
|
54
|
+
schedulable_jobs = @job_class.queued.order_by(scheduled_at: :asc).entries
|
55
|
+
jobs_to_schedule = @max_concurrent_jobs - running_jobs.count
|
56
|
+
jobs_to_schedule = 0 if jobs_to_schedule < 0
|
57
|
+
|
58
|
+
# Finds out scheduled jobs waiting to be performed.
|
59
|
+
scheduled_jobs = []
|
60
|
+
schedulable_jobs.first(jobs_to_schedule).each do |job|
|
61
|
+
job_pid = Process.fork do
|
62
|
+
begin
|
63
|
+
job.perform(Process.pid)
|
64
|
+
rescue StandardError => e
|
65
|
+
@logger.error Rainbow("[Scheduler:#{@pid}] Error #{e.class}: #{e.message}.").red
|
66
|
+
@logger.error Rainbow(e.backtrace.join("\n")).red
|
67
|
+
end
|
68
|
+
end
|
69
|
+
Process.detach(job_pid)
|
70
|
+
scheduled_jobs << job
|
71
|
+
@queue << job.id.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
# Logs launched jobs
|
75
|
+
if scheduled_jobs.any?
|
76
|
+
@logger.info Rainbow("[Scheduler:#{@pid}] Launched #{scheduled_jobs.count} "\
|
77
|
+
"jobs: #{scheduled_jobs.map(&:id).map(&:to_s).join(', ')}.").cyan
|
78
|
+
else
|
79
|
+
if schedulable_jobs.count == 0
|
80
|
+
@logger.info Rainbow("[Scheduler:#{@pid}] No jobs in queue.").cyan
|
81
|
+
else
|
82
|
+
@logger.warn Rainbow("[Scheduler:#{@pid}] No jobs launched, reached maximum "\
|
83
|
+
"number of concurrent jobs. Jobs in queue: #{schedulable_jobs.count}.").yellow
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Checks for completed jobs: clears up queue and kills any zombie pid
|
88
|
+
@queue.delete_if do |job_id|
|
89
|
+
job = @job_class.find(job_id)
|
90
|
+
if job.present?
|
91
|
+
unless job.status.in? [ :queued, :running ]
|
92
|
+
begin
|
93
|
+
@logger.info Rainbow("[Scheduler:#{@pid}] Removed process #{job.pid}, job is completed.").cyan
|
94
|
+
Process.kill :QUIT, job.pid
|
95
|
+
rescue Errno::ENOENT, Errno::ESRCH
|
96
|
+
end
|
97
|
+
return true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
# Waits the specified amount of time before next iteration
|
104
|
+
sleep @polling_interval
|
105
|
+
rescue StandardError => error
|
106
|
+
@logger.error Rainbow("[Scheduler:#{@pid}] Error #{e.class}: #{error.message}").red
|
107
|
+
@logger.error Rainbow(error.backtrace.join("\n")).red
|
108
|
+
rescue SignalException => signal
|
109
|
+
if signal.message.in? [ 'SIGINT', 'SIGTERM', 'SIGQUIT' ]
|
110
|
+
@logger.warn Rainbow("[Scheduler:#{@pid}] Received interrupt, terminating scheduler..").yellow
|
111
|
+
reschedule_running_jobs
|
112
|
+
break
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Reschedules currently running jobs.
|
120
|
+
#
|
121
|
+
# @return [nil]
|
122
|
+
def reschedule_running_jobs
|
123
|
+
@job_class.running.each do |job|
|
124
|
+
begin
|
125
|
+
Process.kill :QUIT, job.pid if job.pid.present?
|
126
|
+
rescue Errno::ESRCH, Errno::EPERM
|
127
|
+
ensure
|
128
|
+
job.schedule
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module Scheduler
|
2
|
+
module Schedulable
|
3
|
+
|
4
|
+
##
|
5
|
+
# Possible schedulable statuses.
|
6
|
+
STATUSES = [ :queued, :running, :completed, :warning, :error, :locked ]
|
7
|
+
|
8
|
+
##
|
9
|
+
# Possible log levels.
|
10
|
+
LOG_LEVELS = [ :debug, :info, :warn, :error ]
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
include Mongoid::Document
|
15
|
+
|
16
|
+
field :executable_class, type: String
|
17
|
+
field :args, type: Array, default: []
|
18
|
+
field :scheduled_at, type: DateTime
|
19
|
+
field :executed_at, type: DateTime
|
20
|
+
field :completed_at, type: DateTime
|
21
|
+
field :pid, type: Integer
|
22
|
+
field :status, type: Symbol, default: :queued
|
23
|
+
field :logs, type: Array, default: []
|
24
|
+
field :progress, type: Float, default: 0.0
|
25
|
+
field :error, type: String
|
26
|
+
field :backtrace, type: String
|
27
|
+
|
28
|
+
scope :queued, -> { where(status: :queued) }
|
29
|
+
scope :running, -> { where(status: :running) }
|
30
|
+
scope :completed, -> { where(status: :completed) }
|
31
|
+
scope :to_check, -> { where(status: :warning) }
|
32
|
+
scope :in_error, -> { where(status: :error) }
|
33
|
+
scope :locked, -> { where(status: :locked) }
|
34
|
+
|
35
|
+
class << self
|
36
|
+
|
37
|
+
##
|
38
|
+
# Returns possible statuses.
|
39
|
+
#
|
40
|
+
# @return [Array<Symbol>] possible statuses.
|
41
|
+
def statuses
|
42
|
+
Scheduler::Schedulable::STATUSES
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Returns possible log levels.
|
47
|
+
#
|
48
|
+
# @return [Array<Symbol>] possible log levels.
|
49
|
+
def log_levels
|
50
|
+
Scheduler::Schedulable::LOG_LEVELS
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Returns the corresponding log color if this level.
|
55
|
+
#
|
56
|
+
# @param [Symbol] level log level.
|
57
|
+
#
|
58
|
+
# @return [Symbol] the color.
|
59
|
+
def log_color(level)
|
60
|
+
case level
|
61
|
+
when :debug then :green
|
62
|
+
when :info then :cyan
|
63
|
+
when :warn then :yellow
|
64
|
+
when :error then :red
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Creates an instance of this class and schedules the job.
|
70
|
+
#
|
71
|
+
# @param [String] executable_class the class of the job to run.
|
72
|
+
# @param [Array] *job_args job arguments
|
73
|
+
#
|
74
|
+
# @return [Object] the created job.
|
75
|
+
def schedule(executable_class, *job_args)
|
76
|
+
self.create(executable_class: executable_class, args: job_args).schedule
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Creates an instance of this class and performs the job.
|
81
|
+
#
|
82
|
+
# @param [String] executable_class the class of the job to run.
|
83
|
+
# @param [Array] *job_args job arguments
|
84
|
+
#
|
85
|
+
# @return [Object] the created job.
|
86
|
+
def perform(executable_class, *job_args)
|
87
|
+
self.create(executable_class: executable_class, args: job_args).perform
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Gets executor job class.
|
94
|
+
#
|
95
|
+
# @return [Class] the executor job class.
|
96
|
+
def executable_class
|
97
|
+
Object.const_get self[:executable_class]
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Schedules the job.
|
102
|
+
#
|
103
|
+
# @return [Object] itself.
|
104
|
+
def schedule
|
105
|
+
self.scheduled_at = Time.current
|
106
|
+
self.status = :queued
|
107
|
+
self.logs = []
|
108
|
+
self.progress = 0.0
|
109
|
+
self.unset(:error)
|
110
|
+
self.unset(:backtrace)
|
111
|
+
self.unset(:completed_at)
|
112
|
+
self.unset(:executed_at)
|
113
|
+
self.save
|
114
|
+
if block_given?
|
115
|
+
yield self
|
116
|
+
else self end
|
117
|
+
if Scheduler.perform_jobs_in_test_or_development?
|
118
|
+
self.perform
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Performs the job.
|
124
|
+
#
|
125
|
+
# @param [Integer] pid the executing pid.
|
126
|
+
#
|
127
|
+
# @return [Object] the instanced executable_class.
|
128
|
+
def perform(pid = nil)
|
129
|
+
job = self.executable_class.new(*self.args)
|
130
|
+
raise Scheduler::Error.new "#{self.executable_class} does not implement method 'call'. Please make "\
|
131
|
+
"sure to implement it before performing the job." unless job.respond_to? :call
|
132
|
+
self.status!(:running)
|
133
|
+
self.update(pid: pid) if pid.present?
|
134
|
+
job.call(self)
|
135
|
+
self.completed_at = Time.current
|
136
|
+
if self.status == :running
|
137
|
+
self.progress!(100)
|
138
|
+
self.status!(:completed)
|
139
|
+
end
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Immediately update the status to the given one.
|
145
|
+
#
|
146
|
+
# @param [Symbol] status the status to update.
|
147
|
+
#
|
148
|
+
# @return [nil]
|
149
|
+
def status!(status)
|
150
|
+
self.update(status: status)
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Immediately increases progress to the given amount.
|
155
|
+
#
|
156
|
+
# @param [Float] amount the given progress amount.
|
157
|
+
def progress!(amount)
|
158
|
+
self.update(progress: amount.to_f) if amount.numeric?
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Immediately increases progress by the given amount.
|
163
|
+
#
|
164
|
+
# @param [Float] amount the given progress amount.
|
165
|
+
def progress_by!(amount)
|
166
|
+
self.update(progress: progress + amount.to_f) if amount.numeric?
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Registers a log message with the given level.
|
171
|
+
def log(level, message)
|
172
|
+
raise Scheduler::Error.new "The given log level '#{level}' is not valid. "\
|
173
|
+
"Valid log levels are: #{LOG_LEVELS.join(', ')}" unless level.in? LOG_LEVELS
|
174
|
+
Scheduler.logger.send level, Rainbow("[#{self.class}:#{self.id}] #{message}").send(self.class.log_color level)
|
175
|
+
self.update(logs: logs.push([level, message]))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
##
|
2
|
+
# Example configuration file for rails-scheduler gem.
|
3
|
+
Scheduler.configure do |config|
|
4
|
+
|
5
|
+
##
|
6
|
+
# Current running environment.
|
7
|
+
# Defaults to 'test'.
|
8
|
+
#
|
9
|
+
# config.environment = 'test'
|
10
|
+
|
11
|
+
##
|
12
|
+
# A path to Mongoid config file.
|
13
|
+
# Defaults to Scheduler gem test Mongoid config.
|
14
|
+
#
|
15
|
+
# config.mongoid_config_file = File.join(Scheduler.root, "spec/mongoid.yml")
|
16
|
+
|
17
|
+
##
|
18
|
+
# A path to a specific log file.
|
19
|
+
# Defaults to STDOUT.
|
20
|
+
#
|
21
|
+
# config.log_file = STDOUT
|
22
|
+
|
23
|
+
##
|
24
|
+
# A custom class to handle the execution of jobs.
|
25
|
+
# This class must include Scheduler::Schedulable module
|
26
|
+
# in order to work.
|
27
|
+
# Defaults to ExampleSchedulableModel, which is a bare class that
|
28
|
+
# just includes Scheduler::Schedulable module.
|
29
|
+
#
|
30
|
+
# config.job_class = ExampleSchedulableModel
|
31
|
+
|
32
|
+
##
|
33
|
+
# How often the scheduler has to check for new jobs to run.
|
34
|
+
# Defaults to 5 seconds.
|
35
|
+
#
|
36
|
+
# config.polling_interval = 5
|
37
|
+
|
38
|
+
##
|
39
|
+
# How many jobs can run at a given time.
|
40
|
+
# Defaults to the minimum value between the number of the current
|
41
|
+
# machine CPU cores or 24.
|
42
|
+
#
|
43
|
+
# config.max_concurrent_jobs = [ Etc.nprocessors, 24 ].min
|
44
|
+
|
45
|
+
##
|
46
|
+
# Sets whether to perform jobs when in test or development env.
|
47
|
+
# Usually jobs are performed only when a Scheduler::MainProcess is running.
|
48
|
+
# But for convenience, you can set this parameter to true so you
|
49
|
+
# don't need to keep a Scheduler::MainProcess running.
|
50
|
+
# Defaults to false.
|
51
|
+
#
|
52
|
+
# config.perform_jobs_in_test_or_development = false
|
53
|
+
|
54
|
+
end
|
data/lib/scheduler.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require "rainbow"
|
2
|
+
require "logger"
|
3
|
+
require "mongoid"
|
4
|
+
require_relative "scheduler/version"
|
5
|
+
require_relative "scheduler/schedulable"
|
6
|
+
require_relative "scheduler/examples/schedulable_model"
|
7
|
+
require_relative "scheduler/examples/executable_class"
|
8
|
+
require_relative "scheduler/cli"
|
9
|
+
require_relative "scheduler/configuration"
|
10
|
+
require_relative "scheduler/main_process"
|
11
|
+
|
12
|
+
module Scheduler
|
13
|
+
class Error < StandardError; end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# @return [Scheduler::Configuration] the configuration class for Scheduler.
|
18
|
+
attr_accessor :configuration
|
19
|
+
|
20
|
+
##
|
21
|
+
# Initializes configuration.
|
22
|
+
#
|
23
|
+
# @return [Scheduler::Configuration] the configuration class for Scheduler.
|
24
|
+
def configuration
|
25
|
+
@configuration || Scheduler::Configuration.new
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Method to configure various Scheduler options.
|
30
|
+
#
|
31
|
+
# @return [nil]
|
32
|
+
def configure
|
33
|
+
@configuration ||= Scheduler::Configuration.new
|
34
|
+
yield @configuration
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Returns Scheduler gem root path.
|
39
|
+
#
|
40
|
+
# @return [String] Scheduler gem root path.
|
41
|
+
def root
|
42
|
+
File.dirname __dir__
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Returns current environment.
|
47
|
+
#
|
48
|
+
# @return [String] Scheduler environment.
|
49
|
+
def env
|
50
|
+
Scheduler.configuration.environment
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Gets scheduler pid file.
|
55
|
+
#
|
56
|
+
# @return [String] the pid file.
|
57
|
+
def pid_file
|
58
|
+
'/tmp/scheduler.pid'
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Gets scheduler main process pid.
|
63
|
+
#
|
64
|
+
# @return [Integer] main process pid.
|
65
|
+
def pid
|
66
|
+
File.read(self.pid_file).to_i rescue nil
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Checks whether to run jobs in test or development.
|
71
|
+
#
|
72
|
+
# @return [Boolean] to run jobs.
|
73
|
+
def perform_jobs_in_test_or_development?
|
74
|
+
Scheduler.configuration.perform_jobs_in_test_or_development
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Return the Scheduler logger.
|
79
|
+
#
|
80
|
+
# @return [Logger] the configured logger.
|
81
|
+
def logger
|
82
|
+
@@logger ||= Logger.new Scheduler.configuration.log_file
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Starts a Scheduler::MainProcess in a separate process.
|
87
|
+
#
|
88
|
+
# @return [nil]
|
89
|
+
def start
|
90
|
+
logger.info Rainbow("[Scheduler:#{Process.pid}] Starting..").cyan
|
91
|
+
scheduler_pid = Process.fork do
|
92
|
+
begin
|
93
|
+
logger.info Rainbow("[Scheduler:#{Process.pid}] Forked.").cyan
|
94
|
+
Process.daemon(true)
|
95
|
+
logger.info Rainbow("[Scheduler:#{Process.pid}] Going into background..").cyan
|
96
|
+
File.open(self.pid_file, 'w+') do |pidfile|
|
97
|
+
pidfile.puts Process.pid
|
98
|
+
end
|
99
|
+
scheduler = Scheduler::MainProcess.new
|
100
|
+
rescue StandardError => e
|
101
|
+
puts Rainbow("#{e.class}: #{e.message} (#{e.backtrace.first})").red
|
102
|
+
end
|
103
|
+
end
|
104
|
+
Process.detach(scheduler_pid)
|
105
|
+
scheduler_pid
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Reschedules all running jobs and stops the scheduler main process.
|
110
|
+
#
|
111
|
+
# @return [nil]
|
112
|
+
def stop
|
113
|
+
begin
|
114
|
+
Process.kill :TERM, Scheduler.pid
|
115
|
+
FileUtils.rm(self.pid_file)
|
116
|
+
rescue TypeError, Errno::ENOENT, Errno::ESRCH
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Restarts the scheduler.
|
122
|
+
#
|
123
|
+
# @return [nil]
|
124
|
+
def restart
|
125
|
+
self.stop
|
126
|
+
self.start
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
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
|
data/scheduler.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "scheduler/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mongodb-scheduler"
|
8
|
+
spec.version = Scheduler::VERSION
|
9
|
+
spec.authors = ["Francesco Ballardin"]
|
10
|
+
spec.email = ["francesco.ballardin@develonproject.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A gem to schedule jobs, handle parallel execution and manage the jobs queue.}
|
13
|
+
spec.description = %q{A gem to schedule jobs, handle parallel execution and manage the jobs queue.}
|
14
|
+
spec.homepage = "https://github.com/Pluvie/mongodb-scheduler"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_dependency "mongoid"
|
26
|
+
spec.add_dependency "bson_ext"
|
27
|
+
spec.add_dependency "rainbow"
|
28
|
+
spec.add_dependency "hanami-cli"
|
29
|
+
spec.add_dependency "hanami-mongoid"
|
30
|
+
spec.add_dependency "logger"
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongodb-scheduler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Francesco Ballardin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mongoid
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bson_ext
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rainbow
|
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: hanami-cli
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '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'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: hanami-mongoid
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: logger
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '10.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '10.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.0'
|
139
|
+
description: A gem to schedule jobs, handle parallel execution and manage the jobs
|
140
|
+
queue.
|
141
|
+
email:
|
142
|
+
- francesco.ballardin@develonproject.com
|
143
|
+
executables:
|
144
|
+
- scheduler
|
145
|
+
extensions: []
|
146
|
+
extra_rdoc_files: []
|
147
|
+
files:
|
148
|
+
- ".gitignore"
|
149
|
+
- ".rspec"
|
150
|
+
- ".travis.yml"
|
151
|
+
- Gemfile
|
152
|
+
- Gemfile.lock
|
153
|
+
- README.md
|
154
|
+
- Rakefile
|
155
|
+
- bin/console
|
156
|
+
- bin/setup
|
157
|
+
- exe/scheduler
|
158
|
+
- lib/scheduler.rb
|
159
|
+
- lib/scheduler/cli.rb
|
160
|
+
- lib/scheduler/configuration.rb
|
161
|
+
- lib/scheduler/examples/executable_class.rb
|
162
|
+
- lib/scheduler/examples/schedulable_model.rb
|
163
|
+
- lib/scheduler/main_process.rb
|
164
|
+
- lib/scheduler/schedulable.rb
|
165
|
+
- lib/scheduler/templates/scheduler.rb
|
166
|
+
- lib/scheduler/version.rb
|
167
|
+
- lib/tasks/scheduler_tasks.rake
|
168
|
+
- scheduler.gemspec
|
169
|
+
homepage: https://github.com/Pluvie/mongodb-scheduler
|
170
|
+
licenses: []
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
requirements: []
|
187
|
+
rubyforge_project:
|
188
|
+
rubygems_version: 2.7.6
|
189
|
+
signing_key:
|
190
|
+
specification_version: 4
|
191
|
+
summary: A gem to schedule jobs, handle parallel execution and manage the jobs queue.
|
192
|
+
test_files: []
|