clockwork 1.3.1 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/tests.yml +36 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +5 -0
- data/README.md +105 -24
- data/bin/clockwork +1 -6
- data/bin/clockworkd +5 -4
- data/clockwork.gemspec +3 -3
- data/lib/clockwork/database_events/event.rb +3 -2
- data/lib/clockwork/database_events/event_collection.rb +11 -27
- data/lib/clockwork/database_events/event_store.rb +11 -1
- data/lib/clockwork/database_events/manager.rb +5 -1
- data/lib/clockwork/database_events/synchronizer.rb +0 -2
- data/lib/clockwork/event.rb +24 -3
- data/lib/clockwork/manager.rb +79 -6
- data/lib/clockwork.rb +1 -0
- data/test/at_test.rb +1 -1
- data/test/clockwork_test.rb +24 -7
- data/test/database_events/event_store_test.rb +1 -0
- data/test/database_events/support/active_record_fake.rb +6 -2
- data/test/database_events/synchronizer_test.rb +35 -3
- data/test/database_events/test_helpers.rb +41 -4
- data/test/event_test.rb +37 -0
- data/test/manager_test.rb +1 -1
- data/test/samples/signal_test.rb +20 -0
- data/test/signal_test.rb +34 -0
- metadata +24 -22
- data/.travis.yml +0 -13
- data/gemfiles/activesupport3.gemfile +0 -10
- data/gemfiles/activesupport4.gemfile +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4e5a8d520eb475e3eb5b3e75b3b175a4a66c3804584912afc9d4392c1d82f3d7
|
4
|
+
data.tar.gz: 558bc615e123ceaf146c2107b71202fef3eaf307215c93aa8f421826e25a8d4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d60240815eee8a5001004f854ccfd1fe94f01023ca5099799100bcc04a99ae982bc96f14b746b4965c4afc2aba77d2138ebae96c41da075bb7b8c4f15b863493
|
7
|
+
data.tar.gz: 4beb2d7f2cd09940e9159e5d4e1d1e62db2268f82b75ff86c344e7b11076a2e6d57865039fe19c361208c1fee8e5af8c3f72657880f6183107e8f90f33129e47
|
@@ -0,0 +1,36 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
ruby: [ "2.7", "3.0", "3.1", "3.2", jruby ]
|
15
|
+
active_support: [ "6.0", "7.0" ]
|
16
|
+
exclude:
|
17
|
+
- ruby: "3.0"
|
18
|
+
active_support: "6.0"
|
19
|
+
- ruby: "3.1"
|
20
|
+
active_support: "6.0"
|
21
|
+
- ruby: "3.2"
|
22
|
+
active_support: "6.0"
|
23
|
+
name: Ruby ${{ matrix.ruby }} - ActiveSupport ${{ matrix.active_support }}
|
24
|
+
env:
|
25
|
+
ACTIVE_SUPPORT_VERSION: ${{ matrix.active_support }}
|
26
|
+
steps:
|
27
|
+
- uses: actions/checkout@v3
|
28
|
+
- uses: ruby/setup-ruby@v1
|
29
|
+
with:
|
30
|
+
ruby-version: ${{ matrix.ruby }}
|
31
|
+
bundler-cache: true
|
32
|
+
- name: Run Tests
|
33
|
+
env:
|
34
|
+
RUBYOPT: "-W:deprecated" # Show Ruby's deprecation warnings
|
35
|
+
run: |
|
36
|
+
bundle exec rake
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
## 3.0.2 ##
|
2
|
+
|
3
|
+
* Fixing clockworkd on Ruby 3.2 (a73a9d5)
|
4
|
+
|
5
|
+
## 3.0.1 ##
|
6
|
+
|
7
|
+
* Notify error using error handler if configured (49075c2)
|
8
|
+
|
9
|
+
## 3.0.0 ##
|
10
|
+
|
11
|
+
* BREAKING CHANGE: drop Rubinius support
|
12
|
+
* add Ruby 3.0 support (65d71f)
|
13
|
+
* fix a bug with OpenStruct (15eb030)
|
14
|
+
* allow `:skip_first_run` in database events (429bc0a)
|
15
|
+
* add rescue to prevent hung up by exception (cc1b7c9)
|
16
|
+
* fix CI errors (16b4e19 & e4480ea)
|
17
|
+
* fix a compatibility bug with Rails 7 (5907bc7)
|
18
|
+
* Add "Finished" log with duration and error summary (66419ab)
|
19
|
+
|
20
|
+
## 2.0.4 ##
|
21
|
+
|
22
|
+
* Reverts the breaking changes in PR #18 that went out in patch 2.0.3
|
23
|
+
|
24
|
+
*Javier Julio*
|
25
|
+
|
26
|
+
## 2.0.3 (February 15, 2018) ##
|
27
|
+
|
28
|
+
* [See the version release](https://github.com/Rykian/clockwork/releases) for the commits that were included.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
Clockwork - a clock process to replace cron [![Build Status](https://
|
1
|
+
Clockwork - a clock process to replace cron [![Build Status](https://api.travis-ci.com/Rykian/clockwork.svg?branch=master)](https://travis-ci.org/Rykian/clockwork)
|
2
2
|
===========================================
|
3
3
|
|
4
4
|
Cron is non-ideal for running scheduled application tasks, especially in an app
|
5
|
-
deployed to multiple machines. [More details.](http://adam.
|
5
|
+
deployed to multiple machines. [More details.](http://adam.herokuapp.com/past/2010/4/13/rethinking_cron/)
|
6
6
|
|
7
7
|
Clockwork is a cron replacement. It runs as a lightweight, long-running Ruby
|
8
8
|
process which sits alongside your web processes (Mongrel/Thin) and your worker
|
@@ -18,6 +18,8 @@ Create clock.rb:
|
|
18
18
|
|
19
19
|
```ruby
|
20
20
|
require 'clockwork'
|
21
|
+
require 'active_support/time' # Allow numeric durations (eg: 1.minutes)
|
22
|
+
|
21
23
|
module Clockwork
|
22
24
|
handler do |job|
|
23
25
|
puts "Running #{job}"
|
@@ -58,8 +60,8 @@ Quickstart for Heroku
|
|
58
60
|
|
59
61
|
Clockwork fits well with heroku's cedar stack.
|
60
62
|
|
61
|
-
Consider
|
62
|
-
a new project for
|
63
|
+
Consider using [clockwork-init.sh](https://gist.github.com/tomykaira/1312172) to create
|
64
|
+
a new project for Heroku.
|
63
65
|
|
64
66
|
Use with queueing
|
65
67
|
-----------------
|
@@ -69,8 +71,8 @@ to do the work. It avoids locking by running as a single process, but this
|
|
69
71
|
makes it impossible to parallelize. For doing the work, you should be using a
|
70
72
|
job queueing system, such as
|
71
73
|
[Delayed Job](http://www.therailsway.com/2009/7/22/do-it-later-with-delayed-job),
|
72
|
-
[Beanstalk/Stalker](http://adam.
|
73
|
-
[RabbitMQ/Minion](http://adam.
|
74
|
+
[Beanstalk/Stalker](http://adam.herokuapp.com/past/2010/4/24/beanstalk_a_simple_and_fast_queueing_backend/),
|
75
|
+
[RabbitMQ/Minion](http://adam.herokuapp.com/past/2009/9/28/background_jobs_with_rabbitmq_and_minion/),
|
74
76
|
[Resque](http://github.com/blog/542-introducing-resque), or
|
75
77
|
[Sidekiq](https://github.com/mperham/sidekiq). This design allows a
|
76
78
|
simple clock process with no locks, but also offers near infinite horizontal
|
@@ -80,6 +82,7 @@ For example, if you're using Beanstalk/Stalker:
|
|
80
82
|
|
81
83
|
```ruby
|
82
84
|
require 'stalker'
|
85
|
+
require 'active_support/time'
|
83
86
|
|
84
87
|
module Clockwork
|
85
88
|
handler { |job| Stalker.enqueue(job) }
|
@@ -92,7 +95,7 @@ end
|
|
92
95
|
Using a queueing system which doesn't require that your full application be
|
93
96
|
loaded is preferable, because the clock process can keep a tiny memory
|
94
97
|
footprint. If you're using DJ or Resque, however, you can go ahead and load
|
95
|
-
your full application
|
98
|
+
your full application environment, and use per-event blocks to call DJ or Resque
|
96
99
|
enqueue methods. For example, with DJ/Rails:
|
97
100
|
|
98
101
|
```ruby
|
@@ -142,27 +145,33 @@ module Clockwork
|
|
142
145
|
end
|
143
146
|
```
|
144
147
|
|
145
|
-
This tells clockwork to fetch all `ClockworkDatabaseEvent` instances from the database,
|
148
|
+
This tells clockwork to fetch all `ClockworkDatabaseEvent` instances from the database, and create an internal clockwork event for each one. Each clockwork event will be configured based on the instance's `frequency` and, optionally, its `at`, `if?`, `ignored_attributes`, `name`, and, `tz` methods. The code above also says to reload the events from the database every `1.minute`; we need pick up any changes in the database frequently (choose a sensible reload frequency by changing the `every:` option).
|
146
149
|
|
147
|
-
When one of the events is ready to be run (based on it's `frequency`, `at` and
|
150
|
+
When one of the events is ready to be run (based on it's `frequency`, and possible `at`, `if?`, `ignored attributes`, and `tz` methods), clockwork arranges for the block passed to `sync_database_events` to be run. The above example shows how you could use either DelayedJob or Sidekiq to kick off a worker job. This approach is good because the ideal is to use clockwork as a simple scheduler, and avoid making it carry out any long-running tasks.
|
148
151
|
|
149
152
|
### Your Model Classes
|
150
153
|
|
151
154
|
`ActiveRecord` models are a perfect candidate for the model class. Having said that, the only requirements are:
|
152
155
|
|
153
|
-
|
156
|
+
1. the class responds to `all` returning an array of instances from the database
|
157
|
+
|
158
|
+
2. the instances returned respond to:
|
159
|
+
- `id` returning a unique identifier (this is needed to track changes to event settings)
|
160
|
+
- `frequency` returning the how frequently (in seconds) the database event should be run
|
161
|
+
|
162
|
+
- `attributes` returning a hash of [attribute name] => [attribute value] values (or really anything that we can use store on registering the event, and then compare again to see if the state has changed later)
|
154
163
|
|
155
|
-
|
164
|
+
- `at` *(optional)* return any acceptable clockwork `:at` string
|
156
165
|
|
157
|
-
|
166
|
+
- `name` *(optional)* returning the name for the event (used to identify it in the Clockwork output)
|
158
167
|
|
159
|
-
|
168
|
+
- `if?`*(optional)* returning either true or false, depending on whether the database event should run at the given time (this method will be passed the time as a parameter, much like the standard clockwork `:if`)
|
160
169
|
|
161
|
-
|
170
|
+
- `ignored_attributes` *(optional)* returning an array of model attributes (as symbols) to ignore when determining whether the database event has been modified since our last run
|
162
171
|
|
163
|
-
|
172
|
+
- `tz` *(optional)* returning the timezone to use (default is the local timezone)
|
164
173
|
|
165
|
-
|
174
|
+
- `skip_first_run` *(optional)* self explanatory, see [dedicated section](#skip_first_run)
|
166
175
|
|
167
176
|
#### Example Setup
|
168
177
|
|
@@ -220,6 +229,50 @@ end
|
|
220
229
|
...
|
221
230
|
```
|
222
231
|
|
232
|
+
#### Example use of `if?`
|
233
|
+
|
234
|
+
Database events support the ability to run events if certain conditions are met. This can be used to only run events on a given day, week, or month, or really any criteria you could conceive. Best of all, these criteria e.g. which day to
|
235
|
+
run it on can be attributes on your Model, and therefore change dynamically as you change the Model in the database.
|
236
|
+
|
237
|
+
So for example, if you had a Model that had a `day` and `month` integer attribute, you could specify that the Database event should only run on a particular day of a particular month as follows:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
# app/models/clockwork_database_event.rb
|
241
|
+
class ClockworkDatabaseEvent < ActiveRecord::Base
|
242
|
+
|
243
|
+
...
|
244
|
+
|
245
|
+
def if?(time)
|
246
|
+
time.day == day && time.month == month
|
247
|
+
end
|
248
|
+
|
249
|
+
...
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
#### Example use of `ignored_attributes`
|
254
|
+
|
255
|
+
Clockwork compares all attributes of the model between runs to determine if the model has changed, and if it has, it runs the event if all other conditions are met.
|
256
|
+
|
257
|
+
However, in certain cases, you may want to store additional attributes in your model that you don't want to affect whether a database event is executed prior to its next interval.
|
258
|
+
|
259
|
+
So for example, you may update an attribute of your model named `last_scheduled_at` on each run to track the last time it was successfully scheduled. You can tell Clockwork to ignore that attribute in its comparison as follows:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
# app/models/clockwork_database_event.rb
|
263
|
+
class ClockworkDatabaseEvent < ActiveRecord::Base
|
264
|
+
|
265
|
+
...
|
266
|
+
|
267
|
+
def ignored_attributes
|
268
|
+
[ :last_scheduled_at ]
|
269
|
+
end
|
270
|
+
|
271
|
+
...
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
|
223
276
|
Event Parameters
|
224
277
|
----------
|
225
278
|
|
@@ -279,7 +332,8 @@ If this is a problem, please use the `:thread` option to prevent the long runnin
|
|
279
332
|
```ruby
|
280
333
|
every(1.day, 'reminders.send', :at => '00:00', :tz => 'UTC')
|
281
334
|
# Runs the job each day at midnight, UTC.
|
282
|
-
# The value for :tz can be anything supported by [TZInfo](
|
335
|
+
# The value for :tz can be anything supported by [TZInfo](https://github.com/tzinfo/tzinfo)
|
336
|
+
# Using the 'tzinfo' gem, run TZInfo::Timezone.all_identifiers to get a list of acceptable identifiers.
|
283
337
|
```
|
284
338
|
|
285
339
|
### :if
|
@@ -315,6 +369,19 @@ Clockwork.every(1.day, 'run.me.in.new.thread', :thread => true)
|
|
315
369
|
|
316
370
|
If a job is long-running or IO-intensive, this option helps keep the clock precise.
|
317
371
|
|
372
|
+
### :skip_first_run
|
373
|
+
|
374
|
+
Normally, a clockwork process that is defined to run in a specified period will run at startup.
|
375
|
+
This is sometimes undesired behaviour, if the action being run relies on other processes booting which may be slower than clock.
|
376
|
+
To avoid this problem, `:skip_first_run` can be used.
|
377
|
+
|
378
|
+
```ruby
|
379
|
+
Clockwork.every(5.minutes, 'myjob', :skip_first_run => true)
|
380
|
+
```
|
381
|
+
|
382
|
+
The above job will not run at initial boot, and instead run every 5 minutes after boot.
|
383
|
+
|
384
|
+
|
318
385
|
Configuration
|
319
386
|
-----------------------
|
320
387
|
|
@@ -331,7 +398,6 @@ Clockwork wakes up once a second and performs its duties. To change the number o
|
|
331
398
|
sleeps, set the `sleep_timeout` configuration option as shown below in the example.
|
332
399
|
|
333
400
|
From 1.1.0, Clockwork does not accept `sleep_timeout` less than 1 seconds.
|
334
|
-
This restriction is introduced to solve more severe bug [#135](https://github.com/tomykaira/clockwork/pull/135).
|
335
401
|
|
336
402
|
### :tz
|
337
403
|
|
@@ -437,8 +503,8 @@ every(1.day, 'check.leap.year') do
|
|
437
503
|
end
|
438
504
|
```
|
439
505
|
|
440
|
-
In addition, Clockwork also supports `:before_tick
|
441
|
-
|
506
|
+
In addition, Clockwork also supports `:before_tick`, `:after_tick`, `:before_run`, and `:after_run` callbacks.
|
507
|
+
All callbacks are optional. All `before` callbacks must return a truthy value otherwise the job will not run. The `tick` callbacks run every tick (a tick being whatever your `:sleep_timeout`
|
442
508
|
is set to, default is 1 second):
|
443
509
|
|
444
510
|
```ruby
|
@@ -449,6 +515,14 @@ end
|
|
449
515
|
on(:after_tick) do
|
450
516
|
puts "tock"
|
451
517
|
end
|
518
|
+
|
519
|
+
on(:before_run) do |event, t|
|
520
|
+
puts "job_started: #{event}"
|
521
|
+
end
|
522
|
+
|
523
|
+
on(:after_run) do |event, t|
|
524
|
+
puts "job_finished: #{event}"
|
525
|
+
end
|
452
526
|
```
|
453
527
|
|
454
528
|
Finally, you can use tasks synchronised from a database as described in detail above:
|
@@ -490,10 +564,17 @@ clockworkd -c YOUR_CLOCK.rb start
|
|
490
564
|
|
491
565
|
For more details, you can run `clockworkd -h`.
|
492
566
|
|
567
|
+
Integration Testing
|
568
|
+
-------------------
|
569
|
+
|
570
|
+
You could take a look at:
|
571
|
+
* [clockwork-mocks](https://github.com/dpoetzsch/clockwork-mocks) that helps with running scheduled tasks during integration testing.
|
572
|
+
* [clockwork-test](https://github.com/kevin-j-m/clockwork-test) which ensures that tasks are triggered at the right time
|
573
|
+
|
493
574
|
Issues and Pull requests
|
494
575
|
------------------------
|
495
576
|
|
496
|
-
If you find a bug, please create an issue - [Issues ·
|
577
|
+
If you find a bug, please create an issue - [Issues · Rykian/clockwork](https://github.com/Rykian/clockwork/issues).
|
497
578
|
|
498
579
|
For a bug fix or a feature request, please send a pull-request.
|
499
580
|
Do not forget to add tests to show how your feature works, or what bug is fixed.
|
@@ -509,8 +590,8 @@ Use cases
|
|
509
590
|
|
510
591
|
Feel free to add your idea or experience and send a pull-request.
|
511
592
|
|
512
|
-
-
|
513
|
-
-
|
593
|
+
- Sending errors to Airbrake
|
594
|
+
- Read events from a database
|
514
595
|
|
515
596
|
Meta
|
516
597
|
----
|
@@ -525,4 +606,4 @@ Patches contributed by Mark McGranaghan and Lukáš Konarovský
|
|
525
606
|
|
526
607
|
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
|
527
608
|
|
528
|
-
|
609
|
+
https://github.com/Rykian/clockwork
|
data/bin/clockwork
CHANGED
@@ -4,16 +4,11 @@ STDERR.sync = STDOUT.sync = true
|
|
4
4
|
|
5
5
|
require 'clockwork'
|
6
6
|
|
7
|
-
usage = 'clockwork <clock.rb>'
|
7
|
+
usage = 'Usage: clockwork <clock.rb>'
|
8
8
|
file = ARGV.shift or abort usage
|
9
9
|
|
10
10
|
file = "./#{file}" unless file.match(/^[\/.]/)
|
11
11
|
|
12
12
|
require file
|
13
13
|
|
14
|
-
trap('INT') do
|
15
|
-
puts "\rExiting"
|
16
|
-
exit
|
17
|
-
end
|
18
|
-
|
19
14
|
Clockwork::run
|
data/bin/clockworkd
CHANGED
@@ -4,6 +4,7 @@ STDERR.sync = STDOUT.sync = true
|
|
4
4
|
|
5
5
|
require 'clockwork'
|
6
6
|
require 'optparse'
|
7
|
+
require 'pathname'
|
7
8
|
|
8
9
|
begin
|
9
10
|
require 'daemons'
|
@@ -45,7 +46,7 @@ opts = OptionParser.new do |opts|
|
|
45
46
|
end
|
46
47
|
opts.on('-c', '--clock=FILE',"Clock .rb file. Default is #{@options[:file]}.") do |clock_file|
|
47
48
|
@options[:file] = clock_file
|
48
|
-
@options[:file] = "./#{@options[:file]}" unless @options[:file].
|
49
|
+
@options[:file] = "./#{@options[:file]}" unless Pathname.new(@options[:file]).absolute?
|
49
50
|
@options[:file] = File.expand_path(@options[:file])
|
50
51
|
end
|
51
52
|
opts.on('-d', '--dir=DIR', 'Directory to change to once the process starts') do |dir|
|
@@ -62,7 +63,7 @@ def optparser_abort(opts,message)
|
|
62
63
|
end
|
63
64
|
|
64
65
|
optparser_abort opts, "ERROR: --clock=FILE is required." unless @options[:file]
|
65
|
-
optparser_abort opts, "ERROR: clock file #{@options[:file]} does not exist." unless File.
|
66
|
+
optparser_abort opts, "ERROR: clock file #{@options[:file]} does not exist." unless File.exist?(@options[:file])
|
66
67
|
optparser_abort opts, "ERROR: File extension specified in --clock must be '.rb'" unless File.extname(@options[:file]) == ".rb"
|
67
68
|
|
68
69
|
@options[:identifier] ||= "#{File.basename(@options[:file],'.*')}"
|
@@ -70,8 +71,8 @@ process_name = "#{bin_basename}.#{@options[:identifier]}"
|
|
70
71
|
|
71
72
|
@options[:log_dir] ||= @options[:pid_dir]
|
72
73
|
|
73
|
-
Dir.mkdir(@options[:pid_dir]) unless File.
|
74
|
-
Dir.mkdir(@options[:log_dir]) unless File.
|
74
|
+
Dir.mkdir(@options[:pid_dir]) unless File.exist?(@options[:pid_dir])
|
75
|
+
Dir.mkdir(@options[:log_dir]) unless File.exist?(@options[:log_dir])
|
75
76
|
|
76
77
|
puts "#{process_name}: pid file: #{File.expand_path(File.join(@options[:pid_dir],process_name + '.pid'))}"
|
77
78
|
|
data/clockwork.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "clockwork"
|
3
|
-
s.version = "
|
3
|
+
s.version = "3.0.2"
|
4
4
|
|
5
5
|
s.authors = ["Adam Wiggins", "tomykaira"]
|
6
6
|
s.license = 'MIT'
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.extra_rdoc_files = [
|
10
10
|
"README.md"
|
11
11
|
]
|
12
|
-
s.homepage = "http://github.com/
|
12
|
+
s.homepage = "http://github.com/Rykian/clockwork"
|
13
13
|
s.summary = "A scheduler process to replace cron."
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split($/)
|
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.add_dependency(%q<tzinfo>)
|
21
21
|
s.add_dependency(%q<activesupport>)
|
22
22
|
|
23
|
-
s.add_development_dependency "bundler", "~> 1.3"
|
24
23
|
s.add_development_dependency "rake"
|
25
24
|
s.add_development_dependency "daemons"
|
26
25
|
s.add_development_dependency "minitest", "~> 5.8"
|
27
26
|
s.add_development_dependency "mocha"
|
27
|
+
s.add_development_dependency "test-unit"
|
28
28
|
end
|
@@ -4,12 +4,13 @@ module Clockwork
|
|
4
4
|
|
5
5
|
class Event < Clockwork::Event
|
6
6
|
|
7
|
-
attr_accessor :event_store, :
|
7
|
+
attr_accessor :event_store, :model_attributes
|
8
8
|
|
9
|
-
def initialize(manager, period, job, block, event_store, options={})
|
9
|
+
def initialize(manager, period, job, block, event_store, model_attributes, options={})
|
10
10
|
super(manager, period, job, block, options)
|
11
11
|
@event_store = event_store
|
12
12
|
@event_store.register(self, job)
|
13
|
+
@model_attributes = model_attributes
|
13
14
|
end
|
14
15
|
|
15
16
|
def name
|
@@ -14,9 +14,14 @@ module Clockwork
|
|
14
14
|
def has_changed?(model)
|
15
15
|
return true if event.nil?
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
ignored_attributes = model.ignored_attributes if model.respond_to?(:ignored_attributes)
|
18
|
+
ignored_attributes ||= []
|
19
|
+
|
20
|
+
model_attributes = model.attributes.select do |k, _|
|
21
|
+
not ignored_attributes.include?(k.to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
event.model_attributes != model_attributes
|
20
25
|
end
|
21
26
|
|
22
27
|
def unregister
|
@@ -27,33 +32,12 @@ module Clockwork
|
|
27
32
|
|
28
33
|
attr_reader :events, :manager
|
29
34
|
|
35
|
+
# All events in the same collection (for a model instance) are equivalent
|
36
|
+
# so we can use any of them. Only their @at variable will be different,
|
37
|
+
# but we don't care about that here.
|
30
38
|
def event
|
31
39
|
events.first
|
32
40
|
end
|
33
|
-
|
34
|
-
def has_name?
|
35
|
-
event.job_has_name?
|
36
|
-
end
|
37
|
-
|
38
|
-
def name
|
39
|
-
event.name
|
40
|
-
end
|
41
|
-
|
42
|
-
def frequency
|
43
|
-
event.frequency
|
44
|
-
end
|
45
|
-
|
46
|
-
def ats
|
47
|
-
events.collect(&:at).compact
|
48
|
-
end
|
49
|
-
|
50
|
-
def model_ats(model)
|
51
|
-
at_strings_for(model).collect{|at| At.parse(at) }
|
52
|
-
end
|
53
|
-
|
54
|
-
def at_strings_for(model)
|
55
|
-
model.at.to_s.split(',').map(&:strip)
|
56
|
-
end
|
57
41
|
end
|
58
42
|
end
|
59
43
|
end
|
@@ -112,10 +112,20 @@ module Clockwork
|
|
112
112
|
options = {
|
113
113
|
:from_database => true,
|
114
114
|
:synchronizer => self,
|
115
|
-
:
|
115
|
+
:ignored_attributes => [],
|
116
116
|
}
|
117
117
|
|
118
|
+
options[:at] = at_strings_for(model) if model.respond_to?(:at)
|
119
|
+
options[:if] = ->(time){ model.if?(time) } if model.respond_to?(:if?)
|
118
120
|
options[:tz] = model.tz if model.respond_to?(:tz)
|
121
|
+
options[:ignored_attributes] = model.ignored_attributes if model.respond_to?(:ignored_attributes)
|
122
|
+
options[:skip_first_run] = model.skip_first_run if model.respond_to?(:skip_first_run)
|
123
|
+
|
124
|
+
# store the state of the model at time of registering so we can
|
125
|
+
# easily compare and determine if state has changed later
|
126
|
+
options[:model_attributes] = model.attributes.select do |k, v|
|
127
|
+
not options[:ignored_attributes].include?(k.to_sym)
|
128
|
+
end
|
119
129
|
|
120
130
|
options
|
121
131
|
end
|
@@ -10,7 +10,11 @@ module Clockwork
|
|
10
10
|
|
11
11
|
def register(period, job, block, options)
|
12
12
|
@events << if options[:from_database]
|
13
|
-
|
13
|
+
synchronizer = options.fetch(:synchronizer)
|
14
|
+
model_attributes = options.fetch(:model_attributes)
|
15
|
+
|
16
|
+
Clockwork::DatabaseEvents::Event.
|
17
|
+
new(self, period, job, (block || handler), synchronizer, model_attributes, options)
|
14
18
|
else
|
15
19
|
Clockwork::Event.new(self, period, job, block || handler, options)
|
16
20
|
end
|
data/lib/clockwork/event.rb
CHANGED
@@ -8,11 +8,12 @@ module Clockwork
|
|
8
8
|
@period = period
|
9
9
|
@job = job
|
10
10
|
@at = At.parse(options[:at])
|
11
|
-
@last = nil
|
12
11
|
@block = block
|
13
12
|
@if = options[:if]
|
14
13
|
@thread = options.fetch(:thread, @manager.config[:thread])
|
15
14
|
@timezone = options.fetch(:tz, @manager.config[:tz])
|
15
|
+
@skip_first_run = options[:skip_first_run]
|
16
|
+
@last = @skip_first_run ? convert_timezone(Time.now) : nil
|
16
17
|
end
|
17
18
|
|
18
19
|
def convert_timezone(t)
|
@@ -21,7 +22,10 @@ module Clockwork
|
|
21
22
|
|
22
23
|
def run_now?(t)
|
23
24
|
t = convert_timezone(t)
|
24
|
-
|
25
|
+
return false unless elapsed_ready?(t)
|
26
|
+
return false unless run_at?(t)
|
27
|
+
return false unless run_if?(t)
|
28
|
+
true
|
25
29
|
end
|
26
30
|
|
27
31
|
def thread?
|
@@ -51,16 +55,33 @@ module Clockwork
|
|
51
55
|
|
52
56
|
private
|
53
57
|
def execute
|
58
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
59
|
+
error = nil
|
60
|
+
|
54
61
|
@block.call(@job, @last)
|
55
62
|
rescue => e
|
63
|
+
error = e
|
56
64
|
@manager.log_error e
|
57
65
|
@manager.handle_error e
|
66
|
+
ensure
|
67
|
+
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
68
|
+
duration = ((finish - start) * 1000).round # milliseconds
|
69
|
+
|
70
|
+
@manager.log "Finished '#{self}' duration_ms=#{duration} error=#{error.inspect}"
|
58
71
|
end
|
59
72
|
|
60
|
-
def elapsed_ready(t)
|
73
|
+
def elapsed_ready?(t)
|
61
74
|
@last.nil? || (t - @last.to_i).to_i >= @period
|
62
75
|
end
|
63
76
|
|
77
|
+
def run_at?(t)
|
78
|
+
@at.nil? || @at.ready?(t)
|
79
|
+
end
|
80
|
+
|
81
|
+
def run_if?(t)
|
82
|
+
@if.nil? || @if.call(t)
|
83
|
+
end
|
84
|
+
|
64
85
|
def validate_if_option(if_option)
|
65
86
|
if if_option && !if_option.respond_to?(:call)
|
66
87
|
raise ArgumentError.new(':if expects a callable object, but #{if_option} does not respond to call')
|
data/lib/clockwork/manager.rb
CHANGED
@@ -9,6 +9,9 @@ module Clockwork
|
|
9
9
|
@callbacks = {}
|
10
10
|
@config = default_configuration
|
11
11
|
@handler = nil
|
12
|
+
@mutex = Mutex.new
|
13
|
+
@condvar = ConditionVariable.new
|
14
|
+
@finish = false
|
12
15
|
end
|
13
16
|
|
14
17
|
def thread_available?
|
@@ -34,7 +37,7 @@ module Clockwork
|
|
34
37
|
|
35
38
|
def error_handler(&block)
|
36
39
|
@error_handler = block if block_given?
|
37
|
-
@error_handler
|
40
|
+
@error_handler if instance_variable_defined?("@error_handler")
|
38
41
|
end
|
39
42
|
|
40
43
|
def on(event, options={}, &block)
|
@@ -60,10 +63,68 @@ module Clockwork
|
|
60
63
|
|
61
64
|
def run
|
62
65
|
log "Starting clock for #{@events.size} events: [ #{@events.map(&:to_s).join(' ')} ]"
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
66
|
+
|
67
|
+
sig_read, sig_write = IO.pipe
|
68
|
+
|
69
|
+
(%w[INT TERM HUP] & Signal.list.keys).each do |sig|
|
70
|
+
trap sig do
|
71
|
+
sig_write.puts(sig)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
run_tick_loop
|
76
|
+
|
77
|
+
while io = IO.select([sig_read])
|
78
|
+
sig = io.first[0].gets.chomp
|
79
|
+
handle_signal(sig)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_signal(sig)
|
84
|
+
logger.debug "Got #{sig} signal"
|
85
|
+
case sig
|
86
|
+
when 'INT'
|
87
|
+
shutdown
|
88
|
+
when 'TERM'
|
89
|
+
# Heroku sends TERM signal, and waits 10 seconds before exit
|
90
|
+
graceful_shutdown
|
91
|
+
when 'HUP'
|
92
|
+
graceful_shutdown
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def shutdown
|
97
|
+
logger.info 'Shutting down'
|
98
|
+
stop_tick_loop
|
99
|
+
exit(0)
|
100
|
+
end
|
101
|
+
|
102
|
+
def graceful_shutdown
|
103
|
+
logger.info 'Gracefully shutting down'
|
104
|
+
stop_tick_loop
|
105
|
+
wait_tick_loop_finishes
|
106
|
+
exit(0)
|
107
|
+
end
|
108
|
+
|
109
|
+
def stop_tick_loop
|
110
|
+
@finish = true
|
111
|
+
end
|
112
|
+
|
113
|
+
def wait_tick_loop_finishes
|
114
|
+
@mutex.synchronize do # wait by synchronize
|
115
|
+
@condvar.signal
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def run_tick_loop
|
120
|
+
Thread.new do
|
121
|
+
@mutex.synchronize do
|
122
|
+
until @finish
|
123
|
+
tick
|
124
|
+
interval = config[:sleep_timeout] - Time.now.subsec + 0.001
|
125
|
+
@condvar.wait(@mutex, interval) if interval > 0
|
126
|
+
end
|
127
|
+
end
|
67
128
|
end
|
68
129
|
end
|
69
130
|
|
@@ -81,6 +142,10 @@ module Clockwork
|
|
81
142
|
events
|
82
143
|
end
|
83
144
|
|
145
|
+
def logger
|
146
|
+
config[:logger]
|
147
|
+
end
|
148
|
+
|
84
149
|
def log_error(e)
|
85
150
|
config[:logger].error(e)
|
86
151
|
end
|
@@ -95,7 +160,15 @@ module Clockwork
|
|
95
160
|
|
96
161
|
private
|
97
162
|
def events_to_run(t)
|
98
|
-
@events.select
|
163
|
+
@events.select do |event|
|
164
|
+
begin
|
165
|
+
event.run_now?(t)
|
166
|
+
rescue => e
|
167
|
+
log_error(e)
|
168
|
+
handle_error(e)
|
169
|
+
false
|
170
|
+
end
|
171
|
+
end
|
99
172
|
end
|
100
173
|
|
101
174
|
def register(period, job, block, options)
|
data/lib/clockwork.rb
CHANGED
data/test/at_test.rb
CHANGED
data/test/clockwork_test.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path('../../lib/clockwork', __FILE__)
|
2
2
|
require 'minitest/autorun'
|
3
|
-
require 'mocha/
|
3
|
+
require 'mocha/minitest'
|
4
4
|
|
5
5
|
describe Clockwork do
|
6
6
|
before do
|
@@ -9,6 +9,7 @@ describe Clockwork do
|
|
9
9
|
config[:sleep_timeout] = 0
|
10
10
|
config[:logger] = Logger.new(@log_output)
|
11
11
|
end
|
12
|
+
IO.stubs(:select)
|
12
13
|
end
|
13
14
|
|
14
15
|
after do
|
@@ -21,11 +22,12 @@ describe Clockwork do
|
|
21
22
|
run = job == 'myjob'
|
22
23
|
end
|
23
24
|
Clockwork.every(1.minute, 'myjob')
|
24
|
-
Clockwork.manager.
|
25
|
+
Clockwork.manager.stubs(:run_tick_loop).returns(Clockwork.manager.tick)
|
25
26
|
Clockwork.run
|
26
27
|
|
27
28
|
assert run
|
28
29
|
assert @log_output.string.include?('Triggering')
|
30
|
+
assert @log_output.string.include?('Finished')
|
29
31
|
end
|
30
32
|
|
31
33
|
it 'should log event correctly' do
|
@@ -34,10 +36,25 @@ describe Clockwork do
|
|
34
36
|
run = job == 'an event'
|
35
37
|
end
|
36
38
|
Clockwork.every(1.minute, 'an event')
|
37
|
-
Clockwork.manager.
|
39
|
+
Clockwork.manager.stubs(:run_tick_loop).returns(Clockwork.manager.tick)
|
38
40
|
Clockwork.run
|
39
41
|
assert run
|
40
42
|
assert @log_output.string.include?("Triggering 'an event'")
|
43
|
+
assert_match(/Finished 'an event' duration_ms=\d+ error=nil/, @log_output.string)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should log exceptions' do
|
47
|
+
run = false
|
48
|
+
Clockwork.handler do |job|
|
49
|
+
run = job == 'an event'
|
50
|
+
raise 'boom'
|
51
|
+
end
|
52
|
+
Clockwork.every(1.minute, 'an event')
|
53
|
+
Clockwork.manager.stubs(:run_tick_loop).returns(Clockwork.manager.tick)
|
54
|
+
Clockwork.run
|
55
|
+
assert run
|
56
|
+
assert @log_output.string.include?("Triggering 'an event'")
|
57
|
+
assert_match(/Finished 'an event' duration_ms=\d+ error=#<RuntimeError: boom>/, @log_output.string)
|
41
58
|
end
|
42
59
|
|
43
60
|
it 'should pass event without modification to handler' do
|
@@ -47,7 +64,7 @@ describe Clockwork do
|
|
47
64
|
run = job == event_object
|
48
65
|
end
|
49
66
|
Clockwork.every(1.minute, event_object)
|
50
|
-
Clockwork.manager.
|
67
|
+
Clockwork.manager.stubs(:run_tick_loop).returns(Clockwork.manager.tick)
|
51
68
|
Clockwork.run
|
52
69
|
assert run
|
53
70
|
end
|
@@ -59,14 +76,14 @@ describe Clockwork do
|
|
59
76
|
config[:sleep_timeout] = 0
|
60
77
|
config[:logger] = Logger.new(@log_output)
|
61
78
|
end
|
62
|
-
Clockwork.manager.
|
79
|
+
Clockwork.manager.stubs(:run_tick_loop).returns(Clockwork.manager.tick)
|
63
80
|
Clockwork.run
|
64
81
|
assert @log_output.string.include?('0 events')
|
65
82
|
end
|
66
83
|
|
67
84
|
it 'should pass all arguments to every' do
|
68
85
|
Clockwork.every(1.second, 'myjob', if: lambda { |_| false }) { }
|
69
|
-
Clockwork.manager.
|
86
|
+
Clockwork.manager.stubs(:run_tick_loop).returns(Clockwork.manager.tick)
|
70
87
|
Clockwork.run
|
71
88
|
assert @log_output.string.include?('1 events')
|
72
89
|
assert !@log_output.string.include?('Triggering')
|
@@ -77,7 +94,7 @@ describe Clockwork do
|
|
77
94
|
module ::Clockwork
|
78
95
|
every(1.second, 'myjob') { $called = true }
|
79
96
|
end
|
80
|
-
Clockwork.manager.
|
97
|
+
Clockwork.manager.stubs(:run_tick_loop).returns(Clockwork.manager.tick)
|
81
98
|
Clockwork.run
|
82
99
|
assert $called
|
83
100
|
end
|
@@ -23,9 +23,13 @@ module ActiveRecordFake
|
|
23
23
|
set_attribute_values_from_options options
|
24
24
|
end
|
25
25
|
|
26
|
+
def attributes
|
27
|
+
Hash[instance_variables.map { |name| [name, instance_variable_get(name)] } ]
|
28
|
+
end
|
29
|
+
|
26
30
|
module ClassMethods
|
27
|
-
def create
|
28
|
-
new
|
31
|
+
def create(*args)
|
32
|
+
new(*args)
|
29
33
|
end
|
30
34
|
|
31
35
|
def delete_all
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require "minitest/autorun"
|
2
|
-
require 'mocha/
|
2
|
+
require 'mocha/minitest'
|
3
3
|
require 'time'
|
4
4
|
require 'active_support/time'
|
5
5
|
|
@@ -10,8 +10,6 @@ require_relative 'test_helpers'
|
|
10
10
|
describe Clockwork::DatabaseEvents::Synchronizer do
|
11
11
|
before do
|
12
12
|
@now = Time.now
|
13
|
-
DatabaseEventModel.delete_all
|
14
|
-
DatabaseEventModel2.delete_all
|
15
13
|
|
16
14
|
Clockwork.manager = @manager = Clockwork::DatabaseEvents::Manager.new
|
17
15
|
class << @manager
|
@@ -21,6 +19,9 @@ describe Clockwork::DatabaseEvents::Synchronizer do
|
|
21
19
|
|
22
20
|
after do
|
23
21
|
Clockwork.clear!
|
22
|
+
DatabaseEventModel.delete_all
|
23
|
+
DatabaseEventModel2.delete_all
|
24
|
+
DatabaseEventModelWithIf.delete_all
|
24
25
|
end
|
25
26
|
|
26
27
|
describe "setup" do
|
@@ -254,6 +255,37 @@ describe Clockwork::DatabaseEvents::Synchronizer do
|
|
254
255
|
end
|
255
256
|
end
|
256
257
|
|
258
|
+
describe "with model that responds to `if?`" do
|
259
|
+
|
260
|
+
before do
|
261
|
+
@events_run = []
|
262
|
+
end
|
263
|
+
|
264
|
+
describe "when model.if? is true" do
|
265
|
+
it 'runs' do
|
266
|
+
DatabaseEventModelWithIf.create(:if_state => true, :frequency => 10)
|
267
|
+
setup_sync(model: DatabaseEventModelWithIf, :every => 1.minute, :events_run => @events_run)
|
268
|
+
|
269
|
+
tick_at(@now, :and_every_second_for => 9.seconds)
|
270
|
+
|
271
|
+
assert_equal 1, @events_run.length
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "when model.if? is false" do
|
276
|
+
it 'does not run' do
|
277
|
+
DatabaseEventModelWithIf.create(:if_state => false, :frequency => 10, :name => 'model with if?')
|
278
|
+
setup_sync(model: DatabaseEventModelWithIf, :every => 1.minute, :events_run => @events_run)
|
279
|
+
|
280
|
+
tick_at(@now, :and_every_second_for => 1.minute)
|
281
|
+
|
282
|
+
# require 'byebug'
|
283
|
+
# byebug if events_run.length > 0
|
284
|
+
assert_equal 0, @events_run.length
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
257
289
|
describe "with task that responds to `tz`" do
|
258
290
|
before do
|
259
291
|
@events_run = []
|
@@ -36,23 +36,60 @@ end
|
|
36
36
|
|
37
37
|
class DatabaseEventModel
|
38
38
|
include ActiveRecordFake
|
39
|
-
attr_accessor :
|
39
|
+
attr_accessor :frequency, :at, :tz
|
40
40
|
|
41
41
|
def name
|
42
|
-
@name
|
42
|
+
if instance_variable_defined?("@name")
|
43
|
+
@name
|
44
|
+
else
|
45
|
+
"#{self.class}:#{id}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def name=(name)
|
50
|
+
@name = name
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
46
54
|
class DatabaseEventModel2
|
47
55
|
include ActiveRecordFake
|
48
|
-
attr_accessor :
|
56
|
+
attr_accessor :frequency, :at, :tz
|
49
57
|
|
50
58
|
def name
|
51
|
-
@name
|
59
|
+
if instance_variable_defined?("@name")
|
60
|
+
@name
|
61
|
+
else
|
62
|
+
"#{self.class}:#{id}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def name=(name)
|
67
|
+
@name = name
|
52
68
|
end
|
53
69
|
end
|
54
70
|
|
55
71
|
class DatabaseEventModelWithoutName
|
56
72
|
include ActiveRecordFake
|
57
73
|
attr_accessor :frequency, :at
|
74
|
+
end
|
75
|
+
|
76
|
+
class DatabaseEventModelWithIf
|
77
|
+
include ActiveRecordFake
|
78
|
+
attr_accessor :frequency, :at, :tz, :if_state
|
79
|
+
|
80
|
+
def name
|
81
|
+
if instance_variable_defined?("@name")
|
82
|
+
@name
|
83
|
+
else
|
84
|
+
"#{self.class}:#{id}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def name=(name)
|
89
|
+
@name = name
|
90
|
+
end
|
91
|
+
|
92
|
+
def if?(time)
|
93
|
+
@if_state
|
94
|
+
end
|
58
95
|
end
|
data/test/event_test.rb
CHANGED
@@ -34,4 +34,41 @@ describe Clockwork::Event do
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
|
+
|
38
|
+
describe '#run_now?' do
|
39
|
+
before do
|
40
|
+
@manager = Class.new
|
41
|
+
@manager.stubs(:config).returns({})
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'event skip_first_run option set to true' do
|
45
|
+
it 'returns false on first attempt' do
|
46
|
+
event = Clockwork::Event.new(@manager, 1, nil, nil, :skip_first_run => true)
|
47
|
+
assert_equal false, event.run_now?(Time.now)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns true on subsequent attempts' do
|
51
|
+
event = Clockwork::Event.new(@manager, 1, nil, nil, :skip_first_run => true)
|
52
|
+
# first run
|
53
|
+
event.run_now?(Time.now)
|
54
|
+
|
55
|
+
# second run
|
56
|
+
assert_equal true, event.run_now?(Time.now + 1)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'event skip_first_run option not set' do
|
61
|
+
it 'returns true on first attempt' do
|
62
|
+
event = Clockwork::Event.new(@manager, 1, nil, nil)
|
63
|
+
assert_equal true, event.run_now?(Time.now + 1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'event skip_first_run option set to false' do
|
68
|
+
it 'returns true on first attempt' do
|
69
|
+
event = Clockwork::Event.new(@manager, 1, nil, nil, :skip_first_run => false)
|
70
|
+
assert_equal true, event.run_now?(Time.now)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
37
74
|
end
|
data/test/manager_test.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'clockwork'
|
2
|
+
require 'active_support/time'
|
3
|
+
|
4
|
+
module Clockwork
|
5
|
+
LOGFILE = File.expand_path('../../tmp/signal_test.log', __FILE__)
|
6
|
+
|
7
|
+
handler do |job|
|
8
|
+
File.write(LOGFILE, 'start')
|
9
|
+
sleep 0.1
|
10
|
+
File.write(LOGFILE, 'done')
|
11
|
+
end
|
12
|
+
|
13
|
+
configure do |config|
|
14
|
+
config[:sleep_timeout] = 0
|
15
|
+
config[:logger] = Logger.new(StringIO.new)
|
16
|
+
end
|
17
|
+
|
18
|
+
every(1.seconds, 'run.me.every.1.seconds')
|
19
|
+
end
|
20
|
+
|
data/test/signal_test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'mocha/minitest'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class SignalTest < Test::Unit::TestCase
|
6
|
+
CMD = File.expand_path('../../bin/clockwork', __FILE__)
|
7
|
+
SAMPLE = File.expand_path('../samples/signal_test.rb', __FILE__)
|
8
|
+
LOGFILE = File.expand_path('../tmp/signal_test.log', __FILE__)
|
9
|
+
|
10
|
+
setup do
|
11
|
+
FileUtils.mkdir_p(File.dirname(LOGFILE))
|
12
|
+
@pid = spawn(CMD, SAMPLE)
|
13
|
+
until File.exist?(LOGFILE)
|
14
|
+
sleep 0.1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
teardown do
|
19
|
+
FileUtils.rm_r(File.dirname(LOGFILE))
|
20
|
+
end
|
21
|
+
|
22
|
+
test 'should gracefully shutdown with SIGTERM' do
|
23
|
+
Process.kill(:TERM, @pid)
|
24
|
+
sleep 0.2
|
25
|
+
assert_equal 'done', File.read(LOGFILE)
|
26
|
+
end
|
27
|
+
|
28
|
+
test 'should forcely shutdown with SIGINT' do
|
29
|
+
Process.kill(:INT, @pid)
|
30
|
+
sleep 0.2
|
31
|
+
assert_equal 'start', File.read(LOGFILE)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clockwork
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Wiggins
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-02-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: tzinfo
|
@@ -39,20 +39,6 @@ dependencies:
|
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: bundler
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - "~>"
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '1.3'
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - "~>"
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '1.3'
|
56
42
|
- !ruby/object:Gem::Dependency
|
57
43
|
name: rake
|
58
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,6 +95,20 @@ dependencies:
|
|
109
95
|
- - ">="
|
110
96
|
- !ruby/object:Gem::Version
|
111
97
|
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: test-unit
|
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
112
|
description: A scheduler process to replace cron, using a more flexible Ruby syntax
|
113
113
|
running as a single long-running process. Inspired by rufus-scheduler and resque-scheduler.
|
114
114
|
email:
|
@@ -121,8 +121,9 @@ extensions: []
|
|
121
121
|
extra_rdoc_files:
|
122
122
|
- README.md
|
123
123
|
files:
|
124
|
+
- ".github/workflows/tests.yml"
|
124
125
|
- ".gitignore"
|
125
|
-
-
|
126
|
+
- CHANGELOG.md
|
126
127
|
- Gemfile
|
127
128
|
- LICENSE
|
128
129
|
- README.md
|
@@ -132,8 +133,6 @@ files:
|
|
132
133
|
- clockwork.gemspec
|
133
134
|
- clockworkd.1
|
134
135
|
- example.rb
|
135
|
-
- gemfiles/activesupport3.gemfile
|
136
|
-
- gemfiles/activesupport4.gemfile
|
137
136
|
- lib/clockwork.rb
|
138
137
|
- lib/clockwork/at.rb
|
139
138
|
- lib/clockwork/database_events.rb
|
@@ -152,7 +151,9 @@ files:
|
|
152
151
|
- test/database_events/test_helpers.rb
|
153
152
|
- test/event_test.rb
|
154
153
|
- test/manager_test.rb
|
155
|
-
|
154
|
+
- test/samples/signal_test.rb
|
155
|
+
- test/signal_test.rb
|
156
|
+
homepage: http://github.com/Rykian/clockwork
|
156
157
|
licenses:
|
157
158
|
- MIT
|
158
159
|
metadata: {}
|
@@ -171,8 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
172
|
- !ruby/object:Gem::Version
|
172
173
|
version: '0'
|
173
174
|
requirements: []
|
174
|
-
|
175
|
-
rubygems_version: 2.5.1
|
175
|
+
rubygems_version: 3.0.3.1
|
176
176
|
signing_key:
|
177
177
|
specification_version: 4
|
178
178
|
summary: A scheduler process to replace cron.
|
@@ -185,3 +185,5 @@ test_files:
|
|
185
185
|
- test/database_events/test_helpers.rb
|
186
186
|
- test/event_test.rb
|
187
187
|
- test/manager_test.rb
|
188
|
+
- test/samples/signal_test.rb
|
189
|
+
- test/signal_test.rb
|
data/.travis.yml
DELETED