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 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
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /log/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.3
7
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in scheduler.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/scheduler ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "scheduler"
5
+ Scheduler::CLI.new.call
@@ -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,13 @@
1
+ module Scheduler
2
+ module Examples
3
+ class ExecutableClass
4
+
5
+ def initialize(*args)
6
+ end
7
+
8
+ def call(job)
9
+ job.log :info, 'Example of execution.'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../schedulable'
2
+
3
+ module Scheduler
4
+ module Examples
5
+ class SchedulableModel
6
+ include Scheduler::Schedulable
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,3 @@
1
+ module Scheduler
2
+ VERSION = "0.1.0"
3
+ 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: []