ruby-clock 0.5.0 → 0.8.0.rc2

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: aecbbec2f68af72734fdb4daed9bbe35b47b3b012a2074915241eac7f8b6c40a
4
- data.tar.gz: 3bef0b2f87eb30ca32e5cd142249ef7a4ef1c34deb2714dc76cb2ecab3754600
3
+ metadata.gz: 2ec6ebc2ef5793e7d114920402b20ed5129e1f60a78cb444d208e45def25a36b
4
+ data.tar.gz: 3935724aaa45567dd2e769274f76fc1b5a3785f155d4885764a25635a8bf6c96
5
5
  SHA512:
6
- metadata.gz: 9cb10c6514f7e28394632e6fbd76b19e2666ede782cf344918aeea3eaa368e318b7ee3135b7c252208c8f0090657b629cd469eaa704b0b908ce34fa213735213
7
- data.tar.gz: 943997440dfb369138ad5084497dbdd76504d030d58c5508eced067930205171089518a752b42a3b7cf398dc5560a9ab8ecb70b0dc01f35188c3380853a23738
6
+ metadata.gz: cf58749b267218114e3ad235fa64d0c7e21cecc13c75f6a655b63cf16f056dcc174e6cd724be74c3a5c39087eec808e03d72829674cff52d85f0f831340efeb3
7
+ data.tar.gz: 04eefdc557205145fad64889dec7f7dcf6af4ead08dcc8795af58be4fffc2a54d97bb261048478c98aab58be30124e47f1d35c5115976cdd4119b95903e9b5ce
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ ## 0.8.0 RC2
2
+
3
+ * fix detection of Rails constant, for non-rails apps
4
+
5
+ ## 0.8.0 RC1
6
+
7
+ * automatically wrap jobs with rails reloader
8
+ * ability to run rake tasks
9
+ * ability to run shell commands
10
+ * nicer shutdown logging, indicating when shutdown process begins and ends
11
+ * fix approach for error fallbacks when when calculating job identifier (probably never encountered)
12
+
13
+ ## 0.7.0
14
+
15
+ * ability to specify the name of the file with job definitions, e.g. `bundle exec clock clocks/MyClockfile`
16
+ * ability to specify the amount of time ruby-clock will wait before forcing threads to shut down
17
+
18
+ ## 0.6.0
19
+
20
+ * 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.rc2)
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,6 +56,11 @@ 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
 
60
66
  Install the `clock` binstub and commit to your repo.
@@ -65,6 +71,12 @@ To run your clock process in your app's environment:
65
71
 
66
72
  bundle exec rails runner bin/clock
67
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
+
79
+
68
80
  ### Non-Rails
69
81
 
70
82
  Require your app's code at the top of Clockfile:
@@ -83,6 +95,28 @@ Add this line to your Procfile
83
95
  clock: bundle exec rails runner bin/clock
84
96
  ```
85
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
+ #### Observing logs
112
+
113
+ Because STDOUT does not flush until a certain amount of data has gone into it,
114
+ you might not immediately see the ruby-clock startup message or job output if
115
+ viewing logs in a deployed environment such as Heroku where the logs are redirected
116
+ to another process or file. To change this behavior and have logs flush immediately,
117
+ add `$stdout.sync = true` to the top of your Clockfile.
118
+
119
+
86
120
  ## More Config and Capabilities
87
121
 
88
122
  ### Error Handling
@@ -102,6 +136,103 @@ You can define before, after, and around callbacks which will run for all jobs.
102
136
  Read [the rufus-scheduler documentation](https://github.com/jmettraux/rufus-scheduler/#callbacks)
103
137
  to learn how to do this. Where the documentation references `s`, you should use `schedule`.
104
138
 
139
+ ### Shell commands
140
+
141
+ You can run shell commands in your jobs. They are invoked using
142
+ [posix-spawn](https://github.com/rtomayko/posix-spawn), which means
143
+ the ruby process is not forked.
144
+
145
+ ```ruby
146
+ schedule.every '1 day' do
147
+ shell('sh scripts/process_stuff.sh')
148
+ end
149
+ ```
150
+
151
+ `shell` is a very simple convenience method which is implemented with
152
+ [terrapin](https://github.com/thoughtbot/terrapin). If you want to use other terrapin
153
+ features you can do so:
154
+
155
+ ```ruby
156
+ schedule.every '1 day' do
157
+ line = Terrapin::CommandLine.new('optimize_png', ":file")
158
+ Organization.with_new_logos.find_each do |o|
159
+ line.run(file: o.logo_file_path)
160
+ o.update!(logo_optimized: true)
161
+ end
162
+ end
163
+ ```
164
+
165
+ ### Rake tasks
166
+
167
+ You can run tasks from within the persistent runtime of ruby-clock, without
168
+ needing to shell out and start another process.
169
+
170
+ ```ruby
171
+ schedule.every '1 day' do
172
+ rake('reports:daily')
173
+ end
174
+ ```
175
+
176
+ There is also `rake_execute` and `rake_async`. See [the code](https://github.com/jjb/ruby-clock/blob/main/lib/ruby-clock.rb)
177
+ and [this article](https://code.jjb.cc/running-rake-tasks-from-within-ruby-on-rails-code) for more info.
178
+
179
+ ### Job Identifier
180
+
181
+ ruby-clock adds the `identifier` method to `Rufus::Scheduler::Job`. This method will return the job's
182
+ [name](https://github.com/jmettraux/rufus-scheduler/#name--string) if one was given.
183
+ If a name is not given, the last non-comment code line in the job's block
184
+ will be used instead. If for some reason an error is encountered while calculating this, the next
185
+ fallback is the line number of the job in Clockfile.
186
+
187
+ Some examples of jobs and their identifiers:
188
+
189
+ ```ruby
190
+ schedule.every '1 second', name: 'my job' do
191
+ Foo.bar
192
+ end
193
+ # => my job
194
+
195
+ schedule.every '1 day' do
196
+ daily_things = Foo.setup_daily
197
+ daily_things.process
198
+ # TODO: figure out best time of day
199
+ end
200
+ # => daily_things.process
201
+
202
+ # n.b. ruby-clock isn't yet smart enough to remove trailing comments
203
+ schedule.every '1 week' do
204
+ weekly_things = Foo.setup_weekly
205
+ weekly_things.process # does this work???!1~
206
+ end
207
+ # => weekly_things.process # does this work???!1~
208
+ ```
209
+
210
+ This can be used for keeping track of job behavior in logs or a
211
+ stats tracker. For example:
212
+
213
+ ```ruby
214
+ def schedule.on_post_trigger(job, trigger_time)
215
+ duration = Time.now-trigger_time.to_t
216
+ StatsTracker.value('Clock: Job Execution Time', duration.round(2))
217
+ StatsTracker.value("Clock: Job #{job.identifier} Execution Time", duration.round(2))
218
+ StatsTracker.increment('Clock: Job Executions')
219
+ end
220
+
221
+ schedule.every '10 seconds', name: 'thread stats' do
222
+ thread_usage = Hash.new(0)
223
+ schedule.work_threads(:active).each do |t|
224
+ thread_usage[t[:rufus_scheduler_job].identifier] += 1
225
+ end
226
+ thread_usage.each do |job, count|
227
+ StatsTracker.value("Clock: Job #{job} Active Threads", count)
228
+ end
229
+
230
+ StatsTracker.value("Clock: Active Threads", schedule.work_threads(:active).size)
231
+ StatsTracker.value("Clock: Vacant Threads", schedule.work_threads(:vacant).size)
232
+ StatsTracker.value("Clock: DB Pool Size", ActiveRecord::Base.connection_pool.connections.size)
233
+ end
234
+ ```
235
+
105
236
  ### other rufus-scheduler Options
106
237
 
107
238
  All rufus-scheduler options are set to defaults. The `schedule` variable
@@ -111,6 +242,14 @@ so anything you can do on this instance, you can do in your Clockfile.
111
242
  Perhaps in the future ruby-clock will add some easier specific configuration
112
243
  capabilities for some things. Let me know if you have a request!
113
244
 
245
+ ## Syntax highlighting for Clockfile
246
+
247
+ To tell github and maybe other systems to syntax highlight Clockfile, put this in a .gitattributes file:
248
+
249
+ ```gitattributes
250
+ Clockfile linguist-language=Ruby
251
+ ```
252
+
114
253
 
115
254
  ## License
116
255
 
@@ -0,0 +1 @@
1
+ Clockfile linguist-language=Ruby
@@ -0,0 +1,3 @@
1
+ schedule.every('2 seconds') do
2
+ puts "hello from a ruby-clock job"
3
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem 'ruby-clock', path: '../'
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
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)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ climate_control (0.2.0)
14
+ concurrent-ruby (1.1.9)
15
+ et-orbi (1.2.5)
16
+ tzinfo
17
+ fugit (1.5.2)
18
+ et-orbi (~> 1.1, >= 1.1.8)
19
+ raabro (~> 1.4)
20
+ method_source (1.0.0)
21
+ posix-spawn (0.3.15)
22
+ raabro (1.4.0)
23
+ rufus-scheduler (3.8.0)
24
+ fugit (~> 1.1, >= 1.1.6)
25
+ terrapin (0.6.0)
26
+ climate_control (>= 0.0.3, < 1.0)
27
+ tzinfo (2.0.4)
28
+ concurrent-ruby (~> 1.0)
29
+
30
+ PLATFORMS
31
+ arm64-darwin-20
32
+
33
+ DEPENDENCIES
34
+ ruby-clock!
35
+
36
+ BUNDLED WITH
37
+ 2.2.28
@@ -0,0 +1,9 @@
1
+ This is a bare-bones example non-rails app to
2
+ help with dev and also as an example for users.
3
+
4
+ To run:
5
+
6
+ ```
7
+ bundle
8
+ bundle exe clock
9
+ ```
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 defined?(::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.5.0"
2
+ VERSION = "0.8.0.rc2"
3
3
  end
data/lib/ruby-clock.rb CHANGED
@@ -3,8 +3,10 @@ require 'rufus-scheduler'
3
3
 
4
4
  module RubyClock
5
5
  def shutdown
6
- puts "Shutting down ruby-clock 🐈️ 👋"
7
- Rufus::Scheduler.singleton.shutdown(wait: 29)
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 🐈️ 👋"
8
10
  end
9
11
 
10
12
  def listen_to_signals
@@ -27,6 +29,48 @@ module RubyClock
27
29
 
28
30
  def run_jobs
29
31
  puts "Starting ruby-clock with #{schedule.jobs.size} jobs"
30
- Rufus::Scheduler.singleton.join
32
+ schedule.resume
33
+ schedule.join
31
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
+
32
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.5.0
4
+ version: 0.8.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Bachir
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-04 00:00:00.000000000 Z
11
+ date: 2021-10-12 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
@@ -40,9 +83,15 @@ files:
40
83
  - Rakefile
41
84
  - bin/console
42
85
  - bin/setup
86
+ - example-app/.gitattributes
87
+ - example-app/Clockfile
88
+ - example-app/Gemfile
89
+ - example-app/Gemfile.lock
90
+ - example-app/README.md
43
91
  - exe/clock
44
92
  - lib/ruby-clock.rb
45
93
  - lib/ruby-clock/version.rb
94
+ - release.md
46
95
  - ruby-clock.gemspec
47
96
  homepage: https://github.com/jjb/ruby-clock
48
97
  licenses:
@@ -61,11 +110,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
110
  version: 2.3.0
62
111
  required_rubygems_version: !ruby/object:Gem::Requirement
63
112
  requirements:
64
- - - ">="
113
+ - - ">"
65
114
  - !ruby/object:Gem::Version
66
- version: '0'
115
+ version: 1.3.1
67
116
  requirements: []
68
- rubygems_version: 3.1.2
117
+ rubygems_version: 3.2.22
69
118
  signing_key:
70
119
  specification_version: 4
71
120
  summary: A "clock" process for invoking ruby code within a persistent runtime