ruby-clock 0.4.0 → 0.8.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e7cabf65e0ef766c40ec6fc04215eb1feed1ee46ee1686e7e945aabfb2b31e5
4
- data.tar.gz: dbb284e75da2dd6ffb3cd17475c7b9d759b7651ca659009b6bccd8516c775907
3
+ metadata.gz: 77de35a13a23a83386d574ebc2ff0e71777f65b3612a9a444afd4f9ea69286bf
4
+ data.tar.gz: 95cec8207417b487dfb81fb20b9cc8147bd90200efb009cc56a8c023773979bf
5
5
  SHA512:
6
- metadata.gz: e59fb32b2617359c1cc38dc527441023ca884fc19eb5bd5bfaf91aa9b8f2482642ff1c9fefaf9e3e8df9659c31579242dab41af023b8c070bcbedf1430f0b5d5
7
- data.tar.gz: 36582826edd5054d9a84afa1df660570fa761dbc7c588a87600e79a0f85fcaae2a3e419e6660a72f9a777daffb54380cf7c2a8222f98207565139777374abd43
6
+ metadata.gz: a5585b7af51d706ffcbac3b88881c9b9c189dbc80e08867feea9ecef690dfd04957593504415bffde2c2022f69c1afdb0b30472f2466baa6bfc0dfad1264855d
7
+ data.tar.gz: b1a9aa07dfcaaf28f8d953c88f9ccc467ac199376dce674bff1b3fac7aef3a01416dd9638a40d5fd28ae23932d56e1f56b74888fdb485c4dce3dd9fa6ed579aa
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ ## 0.8.0 RC1
2
+
3
+ * fixed rare error when calculating job identifier
4
+ * nicer shutdown logging, indicating when shutdown process begins and ends
5
+ * ability to run rake tasks
6
+ * ability to run shell commands
7
+ * automatically wrap jobs with rails reloader
8
+
9
+ ## 0.7.0
10
+
11
+ * ability to specify the name of the file with job definitions, e.g. `bundle exec clock clocks/MyClockfile`
12
+ * ability to specify the amount of time ruby-clock will wait before forcing threads to shut down
13
+
14
+ ## 0.6.0
15
+
16
+ * job identifiers
data/Gemfile.lock CHANGED
@@ -1,22 +1,30 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-clock (0.4.0)
5
- rufus-scheduler (~> 3.7)
4
+ ruby-clock (0.8.0.rc1)
5
+ method_source
6
+ posix-spawn (~> 0.3.15)
7
+ rufus-scheduler (~> 3.8)
8
+ terrapin (~> 0.6)
6
9
 
7
10
  GEM
8
11
  remote: https://rubygems.org/
9
12
  specs:
10
- concurrent-ruby (1.1.7)
11
- et-orbi (1.2.4)
13
+ climate_control (0.2.0)
14
+ concurrent-ruby (1.1.9)
15
+ et-orbi (1.2.5)
12
16
  tzinfo
13
- fugit (1.4.1)
17
+ fugit (1.5.2)
14
18
  et-orbi (~> 1.1, >= 1.1.8)
15
19
  raabro (~> 1.4)
20
+ method_source (1.0.0)
21
+ posix-spawn (0.3.15)
16
22
  raabro (1.4.0)
17
23
  rake (12.3.3)
18
- rufus-scheduler (3.7.0)
24
+ rufus-scheduler (3.8.0)
19
25
  fugit (~> 1.1, >= 1.1.6)
26
+ terrapin (0.6.0)
27
+ climate_control (>= 0.0.3, < 1.0)
20
28
  tzinfo (2.0.4)
21
29
  concurrent-ruby (~> 1.0)
22
30
 
@@ -28,4 +36,4 @@ DEPENDENCIES
28
36
  ruby-clock!
29
37
 
30
38
  BUNDLED WITH
31
- 2.1.4
39
+ 2.2.23
data/README.md CHANGED
@@ -16,6 +16,7 @@ The clock process will respond to signals INT (^c at the command line) and
16
16
  TERM (signal sent by environments such as Heroku and other PaaS's when shutting down).
17
17
  In both cases, the clock will stop running jobs and give existing jobs 29 seconds
18
18
  to stop before killing them.
19
+ You can change this number with `RUBY_CLOCK_SHUTDOWN_WAIT_SECONDS` in the environment.
19
20
 
20
21
  ## Installation
21
22
 
@@ -55,11 +56,26 @@ To start your clock process:
55
56
 
56
57
  bundle exec clock
57
58
 
59
+ To use a file other than Clockfile for job definitions, specify it.
60
+ This will ignore Clockfile and only read jobs from clocks/MyClockfile:
61
+
62
+ bundle exec clock clocks/MyClockfile
63
+
58
64
  ### Rails
59
65
 
66
+ Install the `clock` binstub and commit to your repo.
67
+
68
+ bundle binstubs ruby-clock
69
+
60
70
  To run your clock process in your app's environment:
61
71
 
62
- bundle exec rails runner clock
72
+ bundle exec rails runner bin/clock
73
+
74
+ To get smarter database connection management (such as in the case of a database restart or upgrade,
75
+ and maybe other benefits) and code reloading in dev (app code, not the code in Clockfile itself),
76
+ jobs are automatically wrapped in the
77
+ [rails app reloader](https://guides.rubyonrails.org/threading_and_code_execution.html).
78
+
63
79
 
64
80
  ### Non-Rails
65
81
 
@@ -76,9 +92,23 @@ schedule.every('5 minutes') do
76
92
  Add this line to your Procfile
77
93
 
78
94
  ```
79
- clock: bundle exec rails runer clock
95
+ clock: bundle exec rails runner bin/clock
80
96
  ```
81
97
 
98
+ You might have a main clock for general scheduled jobs, and then standalone ones
99
+ if your system has something where you want to monitor and adjust resources
100
+ for that work more precisely. Here, maybe the main clock needs a 2GB instance,
101
+ and the others each need 1GB all to themselves:
102
+
103
+ ```
104
+ clock: bundle exec rails runner bin/clock
105
+ thing_checker: bundle exec rails runner bin/clock clocks/thing_checker.rb
106
+ thing_reporter: bundle exec rails runner bin/clock clocks/thing_reporter.rb
107
+ ```
108
+
109
+ Because of this feature, do I regret using "Clockfile" instead of, say, "clock.rb"? Maybe.
110
+
111
+
82
112
  ## More Config and Capabilities
83
113
 
84
114
  ### Error Handling
@@ -98,6 +128,103 @@ You can define before, after, and around callbacks which will run for all jobs.
98
128
  Read [the rufus-scheduler documentation](https://github.com/jmettraux/rufus-scheduler/#callbacks)
99
129
  to learn how to do this. Where the documentation references `s`, you should use `schedule`.
100
130
 
131
+ ### Shell commands
132
+
133
+ You can run shell commands in your jobs. They are invoked using
134
+ [posix-spawn](https://github.com/rtomayko/posix-spawn), which means
135
+ the ruby process is not forked.
136
+
137
+ ```ruby
138
+ schedule.every '1 day' do
139
+ shell('sh scripts/process_stuff.sh')
140
+ end
141
+ ```
142
+
143
+ `shell` is a very simple convenience method which is implemented with
144
+ [terrapin](https://github.com/thoughtbot/terrapin). If you want to use other terrapin
145
+ features you can do so:
146
+
147
+ ```ruby
148
+ schedule.every '1 day' do
149
+ line = Terrapin::CommandLine.new('optimize_png', ":file")
150
+ Organization.with_new_logos.find_each do |o|
151
+ line.run(file: o.logo_file_path)
152
+ o.update!(logo_optimized: true)
153
+ end
154
+ end
155
+ ```
156
+
157
+ ### Rake tasks
158
+
159
+ You can run tasks from within the persistent runtime of ruby-clock, without
160
+ needing to shell out and start another process.
161
+
162
+ ```ruby
163
+ schedule.every '1 day' do
164
+ rake('reports:daily')
165
+ end
166
+ ```
167
+
168
+ There is also `rake_execute` and `rake_async`. See [the code](https://github.com/jjb/ruby-clock/blob/main/lib/ruby-clock.rb)
169
+ and [this article](https://code.jjb.cc/running-rake-tasks-from-within-ruby-on-rails-code) for more info.
170
+
171
+ ### Job Identifier
172
+
173
+ ruby-clock adds the `identifier` method to `Rufus::Scheduler::Job`. This method will return the job's
174
+ [name](https://github.com/jmettraux/rufus-scheduler/#name--string) if one was given.
175
+ If a name is not given, the last non-comment code line in the job's block
176
+ will be used instead. If for some reason an error is encountered while calculating this, the next
177
+ fallback is the line number of the job in Clockfile.
178
+
179
+ Some examples of jobs and their identifiers:
180
+
181
+ ```ruby
182
+ schedule.every '1 second', name: 'my job' do
183
+ Foo.bar
184
+ end
185
+ # => my job
186
+
187
+ schedule.every '1 day' do
188
+ daily_things = Foo.setup_daily
189
+ daily_things.process
190
+ # TODO: figure out best time of day
191
+ end
192
+ # => daily_things.process
193
+
194
+ # n.b. ruby-clock isn't yet smart enough to remove trailing comments
195
+ schedule.every '1 week' do
196
+ weekly_things = Foo.setup_weekly
197
+ weekly_things.process # does this work???!1~
198
+ end
199
+ # => weekly_things.process # does this work???!1~
200
+ ```
201
+
202
+ This can be used for keeping track of job behavior in logs or a
203
+ stats tracker. For example:
204
+
205
+ ```ruby
206
+ def schedule.on_post_trigger(job, trigger_time)
207
+ duration = Time.now-trigger_time.to_t
208
+ StatsTracker.value('Clock: Job Execution Time', duration.round(2))
209
+ StatsTracker.value("Clock: Job #{job.identifier} Execution Time", duration.round(2))
210
+ StatsTracker.increment('Clock: Job Executions')
211
+ end
212
+
213
+ schedule.every '10 seconds', name: 'thread stats' do
214
+ thread_usage = Hash.new(0)
215
+ schedule.work_threads(:active).each do |t|
216
+ thread_usage[t[:rufus_scheduler_job].identifier] += 1
217
+ end
218
+ thread_usage.each do |job, count|
219
+ StatsTracker.value("Clock: Job #{job} Active Threads", count)
220
+ end
221
+
222
+ StatsTracker.value("Clock: Active Threads", schedule.work_threads(:active).size)
223
+ StatsTracker.value("Clock: Vacant Threads", schedule.work_threads(:vacant).size)
224
+ StatsTracker.value("Clock: DB Pool Size", ActiveRecord::Base.connection_pool.connections.size)
225
+ end
226
+ ```
227
+
101
228
  ### other rufus-scheduler Options
102
229
 
103
230
  All rufus-scheduler options are set to defaults. The `schedule` variable
@@ -107,6 +234,14 @@ so anything you can do on this instance, you can do in your Clockfile.
107
234
  Perhaps in the future ruby-clock will add some easier specific configuration
108
235
  capabilities for some things. Let me know if you have a request!
109
236
 
237
+ ## Syntax highlighting for Clockfile
238
+
239
+ To tell github and maybe other systems to syntax highlight Clockfile, put this in a .gitattributes file:
240
+
241
+ ```gitattributes
242
+ Clockfile linguist-language=Ruby
243
+ ```
244
+
110
245
 
111
246
  ## License
112
247
 
data/exe/clock CHANGED
@@ -1,10 +1,39 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'ruby-clock'
4
+ require 'method_source'
5
+
6
+ class Rufus::Scheduler::Job
7
+ def identifier
8
+ @identifier ||= begin
9
+ name || handler.source.split("\n").reject(&:empty?).grep_v(/#.*/)[-2].strip
10
+ rescue
11
+ begin
12
+ source_location.join('-')
13
+ rescue
14
+ 'error-calculating-job-identifier'
15
+ end
16
+ end
17
+ end
18
+ end
19
+
4
20
  include RubyClock
5
21
 
6
22
  listen_to_signals
23
+ prepare_rake
24
+ schedule.pause
25
+
26
+ load ARGV[0] || 'Clockfile'
7
27
 
8
- load 'Clockfile'
28
+ if ::Rails
29
+ schedule.instance_eval do
30
+ @old_around_trigger = method :around_trigger
31
+ def around_trigger(job)
32
+ ::Rails.application.reloader.wrap do
33
+ @old_around_trigger.call(job){ yield }
34
+ end
35
+ end
36
+ end
37
+ end
9
38
 
10
39
  run_jobs
@@ -1,3 +1,3 @@
1
1
  module RubyClock
2
- VERSION = "0.4.0"
2
+ VERSION = "0.8.0.rc1"
3
3
  end
data/lib/ruby-clock.rb CHANGED
@@ -3,14 +3,24 @@ require 'rufus-scheduler'
3
3
 
4
4
  module RubyClock
5
5
  def shutdown
6
- puts "Shutting down 🐈️ 👋"
7
- puts Rufus::Scheduler.singleton.shutdown(wait: 29)
8
- exit
6
+ wait_seconds = ENV['RUBY_CLOCK_SHUTDOWN_WAIT_SECONDS']&.to_i || 29
7
+ puts "Shutting down ruby-clock. Waiting #{wait_seconds} seconds for jobs to finish..."
8
+ schedule.shutdown(wait: wait_seconds)
9
+ puts "...done 🐈️ 👋"
9
10
  end
10
11
 
11
12
  def listen_to_signals
12
- Signal.trap('INT') { shutdown }
13
- Signal.trap('TERM') { shutdown }
13
+ signals = %w[INT TERM]
14
+ signals.each do |signal|
15
+ old_handler = Signal.trap(signal) do
16
+ shutdown
17
+ if old_handler.respond_to?(:call)
18
+ old_handler.call
19
+ else
20
+ exit
21
+ end
22
+ end
23
+ end
14
24
  end
15
25
 
16
26
  def schedule
@@ -18,6 +28,49 @@ module RubyClock
18
28
  end
19
29
 
20
30
  def run_jobs
21
- Rufus::Scheduler.singleton.join
31
+ puts "Starting ruby-clock with #{schedule.jobs.size} jobs"
32
+ schedule.resume
33
+ schedule.join
22
34
  end
35
+
36
+ def prepare_rake
37
+ if defined?(::Rails) && Rails.application
38
+ Rails.application.load_tasks
39
+ Rake::Task.tasks.each{|t| t.prerequisites.delete 'environment' }
40
+ @rake_mutex = Mutex.new
41
+ else
42
+ puts <<~MESSAGE
43
+ Because this is not a rails application, we do not know how to load your
44
+ rake tasks. You can do this yourself at the top of your Clockfile if you want
45
+ to run rake tasks from ruby-cron.
46
+ MESSAGE
47
+ end
48
+ end
49
+
50
+ # See https://code.jjb.cc/running-rake-tasks-from-within-ruby-on-rails-code
51
+
52
+ # for tasks that don't have dependencies
53
+ def rake_execute(task)
54
+ Rake::Task[task].execute
55
+ end
56
+
57
+ # If the task doesn't share dependencies with another task,
58
+ # or if it does and you know you'll never run tasks such that any overlap
59
+ def rake_async(task)
60
+ Rake::Task[task].invoke
61
+ ensure
62
+ Rake::Task[task].reenable
63
+ Rake::Task[task].all_prerequisite_tasks.each(&:reenable)
64
+ end
65
+
66
+ # If the task has shared dependencies and you might run more than one at the same time
67
+ # This is the safest option and hence the default.
68
+ def rake(task)
69
+ @rake_mutex.synchronize { rake_async(task) }
70
+ end
71
+
72
+ def shell(command)
73
+ Terrapin::CommandLine.new(command).run
74
+ end
75
+
23
76
  end
data/release.md ADDED
@@ -0,0 +1,3 @@
1
+ 1. update lib/ruby-clock/version.rb
2
+ 2. update CHANGELOG.md
3
+ 3. `rake release`
data/ruby-clock.gemspec CHANGED
@@ -29,5 +29,8 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
- spec.add_dependency "rufus-scheduler", '~> 3.7'
32
+ spec.add_dependency "rufus-scheduler", '~> 3.8'
33
+ spec.add_dependency "method_source"
34
+ spec.add_dependency "terrapin", '~> 0.6'
35
+ spec.add_dependency "posix-spawn", '~> 0.3.15'
33
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-clock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.8.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Bachir
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-31 00:00:00.000000000 Z
11
+ date: 2021-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rufus-scheduler
@@ -16,14 +16,56 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.7'
19
+ version: '3.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.7'
26
+ version: '3.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: method_source
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: terrapin
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: posix-spawn
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.3.15
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.3.15
27
69
  description:
28
70
  email:
29
71
  - j@jjb.cc
@@ -33,6 +75,7 @@ extensions: []
33
75
  extra_rdoc_files: []
34
76
  files:
35
77
  - ".gitignore"
78
+ - CHANGELOG.md
36
79
  - Gemfile
37
80
  - Gemfile.lock
38
81
  - LICENSE.txt
@@ -43,6 +86,7 @@ files:
43
86
  - exe/clock
44
87
  - lib/ruby-clock.rb
45
88
  - lib/ruby-clock/version.rb
89
+ - release.md
46
90
  - ruby-clock.gemspec
47
91
  homepage: https://github.com/jjb/ruby-clock
48
92
  licenses:
@@ -61,11 +105,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
105
  version: 2.3.0
62
106
  required_rubygems_version: !ruby/object:Gem::Requirement
63
107
  requirements:
64
- - - ">="
108
+ - - ">"
65
109
  - !ruby/object:Gem::Version
66
- version: '0'
110
+ version: 1.3.1
67
111
  requirements: []
68
- rubygems_version: 3.1.2
112
+ rubygems_version: 3.2.23
69
113
  signing_key:
70
114
  specification_version: 4
71
115
  summary: A "clock" process for invoking ruby code within a persistent runtime