rufus-runner 0.0.3

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
+ SHA1:
3
+ metadata.gz: 026ca77e2c60d3b31c2930dd9c8bf03e5fb08478
4
+ data.tar.gz: f20bf4a2f524d4700765805266a6592436655c59
5
+ SHA512:
6
+ metadata.gz: c3ad75069a777e751f8d57b6b440061b69cc3bccf85d31580d1e85cfd4b44258ee6a3d864ed974c437d521e979d0a00f7e59d7be30f6d1f35f6b6eee1bef7811
7
+ data.tar.gz: 5cbd8a66992378eea954e891845c585368b401f641d9b0e8384158f7e8d50b40200e6821821ba815205445641aa0694711f9e60bea8bc875406b83af5e1bbbf5
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 HouseTrip
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # rufus-runner
2
+
3
+ [![Build Status](https://travis-ci.org/HouseTrip/rufus-runner.png)](https://travis-ci.org/HouseTrip/rufus-runner)
4
+
5
+ Wraps [rufus-scheduler](http://github.com/jmettraux/rufus-scheduler) in a
6
+ standalone process, with job timing, logging, and safe defaults.
7
+
8
+ **Note: I did not write this gem, only deployed it to RubyGems. The project appears to be dead and I'll provide
9
+ support the best I can. If the original authors decide they want to take it back over, just let me know.**
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'rufus-runner'
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install rufus-runner
21
+
22
+ Create a configuration file:
23
+
24
+ # config/schedule.rb
25
+ Rufus::TrackingScheduler.start do |scheduler|
26
+ # sentinel job that keeps running to prove Rufus is still alive
27
+ scheduler.run :name => 'no-op', :every => '60s' do
28
+ Kernel.sleep(1e-3)
29
+ end
30
+ end
31
+
32
+ The `#run` method simply takes the same options as Rufus's `#every` method,
33
+ with these safe defaults:
34
+
35
+ :mutex => <m> # because Rails is not thread-safe
36
+ :timeout => 60 # we don't want no long-running jobs, they should be DJ'd (seconds)
37
+ :discard_past => true # don't catch up with past jobs
38
+ :allow_overlapping => false # don't try to run the same job twice simultaneously
39
+
40
+ Where `<m>` is a shared, global instance of `Mutex`.
41
+
42
+ Finally, run it:
43
+
44
+ $ rufus-runner config/schedule.rb
45
+
46
+
47
+ ### Rails
48
+
49
+ If you're running Rails, you might want to have an environment loaded in the
50
+ scheduler. Simply add on top of your schedule:
51
+
52
+ # Rails 2
53
+ require 'config/boot'
54
+ require 'config/environment'
55
+
56
+ # Rails 3
57
+ require 'config/application'
58
+
59
+ When Rails is present, `rufus-runner` will call `ActiveRecord::Base.clear_active_connections!` after each job for you.
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create new Pull Request
data/bin/rufus-runner ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Driver for Rufus-scheduler.
4
+ # Pass it a schedule file as a command-line argument
5
+ #
6
+ $PROGRAM_NAME = 'rufus'
7
+
8
+ require 'rubygems'
9
+ require 'pathname'
10
+ require 'bundler/setup'
11
+ require 'rufus-runner'
12
+
13
+ if ARGV.empty?
14
+ puts 'Sorry, but I need a schedule. Pass it to me as the first command-line argument.'
15
+ Process.exit! 1
16
+ end
17
+
18
+ load File.expand_path(ARGV[0])
@@ -0,0 +1,37 @@
1
+ require 'rufus-runner/tracking_scheduler/job_runner'
2
+
3
+ class Rufus::TrackingScheduler::ForkingJobRunner < Rufus::TrackingScheduler::JobRunner
4
+
5
+ class UnexpectedExitStatus < StandardError; end
6
+
7
+ def run_block
8
+ @pid = fork do
9
+ $PROGRAM_NAME = "rufus: #{@name}"
10
+ @block.call
11
+ exit 0
12
+ end
13
+ status = Process.wait2(@pid)[1]
14
+ unless status.success?
15
+ raise UnexpectedExitStatus.new(status.exitstatus)
16
+ end
17
+ rescue Rufus::Scheduler::TimeOutError
18
+ log("timed out, killing")
19
+ kill
20
+ raise
21
+ end
22
+
23
+ def shutdown
24
+ kill
25
+ end
26
+
27
+
28
+ private
29
+
30
+ def kill
31
+ Process.kill("KILL", @pid)
32
+ Process.wait(@pid)
33
+ rescue Errno::ESRCH, Errno::ECHILD
34
+ # process already dead, which is fine
35
+ end
36
+
37
+ end
@@ -0,0 +1,56 @@
1
+ class Rufus::Scheduler::Job
2
+ attr_accessor :job_runner
3
+ end
4
+
5
+ class Rufus::TrackingScheduler::JobRunner
6
+
7
+ def initialize(options)
8
+ @name = options.fetch(:name)
9
+ @job = options.fetch(:job)
10
+ @block = options.fetch(:block)
11
+ @scheduler = options.fetch(:scheduler)
12
+ end
13
+
14
+ def run
15
+ return if @scheduler.shutting_down?
16
+ @job.job_runner = self
17
+ start_time = Time.now
18
+ log("starting")
19
+
20
+ begin
21
+ run_block
22
+ rescue Exception => exception
23
+ log("failed with #{exception.class.name} (#{exception.message})")
24
+ if defined?(ActiveRecord::ConnectionTimeoutError) && exception.kind_of?(ActiveRecord::ConnectionTimeoutError)
25
+ log("connection broken, exiting scheduler")
26
+ exit 0
27
+ end
28
+ else
29
+ total_time = Time.now - start_time
30
+ log("completed in %.3f s" % total_time)
31
+ end
32
+
33
+ if defined?(ActiveRecord::Base)
34
+ ActiveRecord::Base.clear_active_connections!
35
+ end
36
+ end
37
+
38
+ def shutdown
39
+ raise NotImplementedError.new
40
+ end
41
+
42
+ private
43
+
44
+ def run_block
45
+ raise NotImplementedError.new
46
+ end
47
+
48
+ def job_id
49
+ @job_id ||= '%08x' % @job.job_id.gsub(/\D/,'')
50
+ end
51
+
52
+ def log(message)
53
+ @scheduler.log("#{@name}(#{job_id}): #{message}")
54
+ end
55
+
56
+ end
@@ -0,0 +1,16 @@
1
+ require 'rufus-runner/tracking_scheduler/job_runner'
2
+
3
+ class Rufus::TrackingScheduler::ThreadingJobRunner < Rufus::TrackingScheduler::JobRunner
4
+
5
+ def shutdown
6
+ # nothing to do, threads will die automatically
7
+ end
8
+
9
+
10
+ private
11
+
12
+ def run_block
13
+ @block.call
14
+ end
15
+
16
+ end
@@ -0,0 +1,159 @@
1
+ class Rufus::TrackingScheduler
2
+ end
3
+
4
+ require 'rufus/scheduler'
5
+ require 'eventmachine'
6
+ require 'rufus-runner/tracking_scheduler/threading_job_runner'
7
+ require 'rufus-runner/tracking_scheduler/forking_job_runner'
8
+
9
+ #
10
+ # Wraps Rufus's scheduler class with signal handling,
11
+ # cancellation of jobs, and logging.
12
+ #
13
+ class Rufus::TrackingScheduler
14
+ # wait that long for jobs to complete before quitting (seconds)
15
+ GRACE_DELAY = 10
16
+
17
+ def initialize(options = {})
18
+ @options = DefaultOptions.merge(options)
19
+ @scheduler = Rufus::Scheduler::EmScheduler.start_new
20
+ log('scheduler started')
21
+ end
22
+
23
+ def run(options={}, &block)
24
+ return unless rails_environment_matches?(options.delete(:environments))
25
+ options = @options.merge(options)
26
+
27
+ name = options.delete(:name) || 'noname'
28
+ case options.delete(:fork) || :thread
29
+ when :thread
30
+ job_runner_class = ThreadingJobRunner
31
+ when :process
32
+ job_runner_class = ForkingJobRunner
33
+ else
34
+ fail ArgumentError.new("option :fork needs to be either :thread or :process")
35
+ end
36
+
37
+ schedule(options) do |job|
38
+ job_runner = job_runner_class.new(
39
+ :name => name,
40
+ :job => job,
41
+ :block => block,
42
+ :scheduler => self
43
+ )
44
+ job_runner.run
45
+ end
46
+
47
+ log("scheduled '#{name}'")
48
+ nil
49
+ end
50
+
51
+ def self.start(options = {})
52
+ EM.run do
53
+ scheduler = new(options)
54
+ scheduler.send :setup_traps
55
+ yield scheduler
56
+ end
57
+ end
58
+
59
+ def shutting_down!
60
+ @shutting_down = true
61
+ end
62
+
63
+ def shutting_down?
64
+ !!@shutting_down
65
+ end
66
+
67
+
68
+ def log(string)
69
+ $stdout.write "[#{$PROGRAM_NAME} #{format_time Time.now}] #{string}\n"
70
+ $stdout.flush
71
+ end
72
+
73
+ private
74
+
75
+ def schedule(options, &block)
76
+ if frequency = options.delete(:every)
77
+ scheduling_method = :every
78
+ elsif frequency = options.delete(:cron)
79
+ scheduling_method = :cron
80
+ else
81
+ raise ArgumentError.new('You need to specify either :every or :cron')
82
+ end
83
+
84
+ @scheduler.send(scheduling_method, frequency, options, &block)
85
+ end
86
+
87
+ def setup_traps
88
+ %w(INT TERM).each do |signal|
89
+ Signal.trap(signal) do
90
+ log "SIG#{signal} received"
91
+ # EM uses mutexes, which cannot be used in the context of a trap.
92
+ # Just do the cleanup in a thread
93
+ Thread.new {
94
+ stop_all_jobs
95
+ EM.stop_event_loop
96
+ }.join
97
+ end
98
+ end
99
+ end
100
+
101
+ def stop_all_jobs
102
+ shutting_down!
103
+ @scheduler.jobs.each_pair do |job_id, job|
104
+ job.unschedule
105
+ end
106
+ log "all jobs unscheduled"
107
+ if running_jobs_count == 0
108
+ log "no more jobs running"
109
+ return
110
+ end
111
+
112
+ log "waiting for #{running_jobs_count} still running jobs"
113
+ start_time = Time.now
114
+ while (Time.now <= start_time + GRACE_DELAY) && (running_jobs_count > 0)
115
+ Kernel.sleep(100e-3)
116
+ end
117
+
118
+ if running_jobs_count > 0
119
+ log "#{running_jobs_count} jobs did not complete"
120
+ @scheduler.running_jobs.collect(&:job_runner).each(&:shutdown)
121
+ else
122
+ log "all jobs completed"
123
+ end
124
+ end
125
+
126
+ def running_jobs_count
127
+ @scheduler.running_jobs.length
128
+ end
129
+
130
+ def format_time(time)
131
+ "%s.%03d" % [time.strftime('%F %T'), ((time.to_f - time.to_i) * 1e3).to_i]
132
+ end
133
+
134
+ def rails_environment_matches?(environments)
135
+ return true unless rails_environment
136
+ return true if environments.nil?
137
+
138
+ Array(environments).any? do |environment|
139
+ # environment can be a string or a regexp
140
+ environment === rails_environment
141
+ end
142
+ end
143
+
144
+ def rails_environment
145
+ if defined?(Rails)
146
+ Rails.env
147
+ else
148
+ ENV['RAILS_ENV'] || ENV['RACK_ENV']
149
+ end
150
+ end
151
+
152
+ DefaultOptions = {
153
+ :fork => :process, # safety first
154
+ :mutex => Mutex.new, # because Rails is not thread-safe
155
+ :timeout => 60, # we don't want no long-running jobs, they should be DJ'd (seconds)
156
+ :discard_past => true, # don't catch up with past jobs
157
+ :allow_overlapping => false, # don't try to run the same job twice simultaneously
158
+ }
159
+ end
@@ -0,0 +1,5 @@
1
+ module Rufus
2
+ module Runner
3
+ VERSION = "0.0.3"
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require "rufus-runner/version"
2
+ require "rufus-runner/tracking_scheduler"
@@ -0,0 +1,45 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $:.unshift(lib) unless $:.include?(lib)
5
+ require 'rufus-runner/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = "rufus-runner"
9
+ gem.version = Rufus::Runner::VERSION
10
+ gem.license = 'MIT'
11
+ gem.authors = ["Julien Letessier", "Chuck Callebs"]
12
+ gem.email = ["julien.letessier@gmail.com", "chuck@callebs.io"]
13
+ gem.description = %q{Wrapper process around rufus-scheduler}
14
+ gem.summary = %q{Wrapper process around rufus-scheduler}
15
+ gem.homepage = "http://github.com/ccallebs/rufus-runner"
16
+
17
+ gem.add_runtime_dependency 'eventmachine'
18
+ gem.add_runtime_dependency 'rufus-scheduler', '~> 2.0.23'
19
+
20
+ gem.add_development_dependency 'rake'
21
+ gem.add_development_dependency 'rspec', '~> 2'
22
+ gem.add_development_dependency 'rb-inotify'
23
+ gem.add_development_dependency 'rb-fsevent'
24
+ gem.add_development_dependency 'rb-fchange'
25
+ gem.add_development_dependency 'terminal-notifier-guard'
26
+ gem.add_development_dependency 'pry'
27
+ gem.add_development_dependency 'pry-nav'
28
+ gem.add_development_dependency 'rspec-instafail'
29
+
30
+ gem.files = %w(
31
+ LICENSE.txt
32
+ README.md
33
+ bin/rufus-runner
34
+ lib/rufus-runner.rb
35
+ lib/rufus-runner/tracking_scheduler.rb
36
+ lib/rufus-runner/tracking_scheduler/job_runner.rb
37
+ lib/rufus-runner/tracking_scheduler/threading_job_runner.rb
38
+ lib/rufus-runner/tracking_scheduler/forking_job_runner.rb
39
+ lib/rufus-runner/version.rb
40
+ rufus-runner.gemspec
41
+ )
42
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
43
+ gem.test_files = gem.files.grep(%r{^spec/})
44
+ gem.require_paths = ["lib"]
45
+ end
metadata ADDED
@@ -0,0 +1,211 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rufus-runner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Julien Letessier
8
+ - Chuck Callebs
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-08-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rufus-scheduler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 2.0.23
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 2.0.23
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '2'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '2'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rb-inotify
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rb-fsevent
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rb-fchange
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: terminal-notifier-guard
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: pry-nav
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: rspec-instafail
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ description: Wrapper process around rufus-scheduler
169
+ email:
170
+ - julien.letessier@gmail.com
171
+ - chuck@callebs.io
172
+ executables:
173
+ - rufus-runner
174
+ extensions: []
175
+ extra_rdoc_files: []
176
+ files:
177
+ - LICENSE.txt
178
+ - README.md
179
+ - bin/rufus-runner
180
+ - lib/rufus-runner.rb
181
+ - lib/rufus-runner/tracking_scheduler.rb
182
+ - lib/rufus-runner/tracking_scheduler/forking_job_runner.rb
183
+ - lib/rufus-runner/tracking_scheduler/job_runner.rb
184
+ - lib/rufus-runner/tracking_scheduler/threading_job_runner.rb
185
+ - lib/rufus-runner/version.rb
186
+ - rufus-runner.gemspec
187
+ homepage: http://github.com/ccallebs/rufus-runner
188
+ licenses:
189
+ - MIT
190
+ metadata: {}
191
+ post_install_message:
192
+ rdoc_options: []
193
+ require_paths:
194
+ - lib
195
+ required_ruby_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ required_rubygems_version: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - ">="
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ requirements: []
206
+ rubyforge_project:
207
+ rubygems_version: 2.4.8
208
+ signing_key:
209
+ specification_version: 4
210
+ summary: Wrapper process around rufus-scheduler
211
+ test_files: []