metrognome 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YzU2MDJhNDNmODk3YzA0ZGFlYWEyMDQyNWMyOGIzZWUzZDM0ZjBmOQ==
5
+ data.tar.gz: !binary |-
6
+ YTk4OWZiNmRiMWY4ODIwZDdlNmIzOGUxMmRlMzY0MmViOGQwYzFiNw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZTJkNjRjM2FhMGU4MDcyZWRlZmM5NThlMzUxZWZiZDhiNzk1N2NjNGUwZjlh
10
+ NzBhY2Q1NjdkMjBiM2ZmZTU5ZWVkMDA3MWYzMDE3ZDU3OWY4ZjA4ZWM2MmI2
11
+ YmYyNTc1YzY5NTg5ZjFjZDAzMjQ3NzUyNmM0YzVjYmNkYTkzMTc=
12
+ data.tar.gz: !binary |-
13
+ MGIyYjM0YzEzNzFjNjBmYThkNGM2ZTY3MzA0ZWY2NGZjNTI2MGUzNmZlNDMw
14
+ M2EwODE3NmEwZGRjYjhlNTdjMzQ3YTRmNmFkMzFiZGZlZWUwNmU2YzM3Nzdl
15
+ YmFlZDEyMzkxNDNiMmVjOTA2Mjc2OWEzOTBmOWFjNzk1MDlmOWU=
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .rspec
5
6
  .yardoc
6
7
  Gemfile.lock
7
8
  InstalledFiles
@@ -15,3 +16,6 @@ spec/reports
15
16
  test/tmp
16
17
  test/version_tmp
17
18
  tmp
19
+
20
+ .ruby-version
21
+ .ruby-gemset
data/README.md CHANGED
@@ -18,7 +18,75 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- TODO: Write usage instructions here
21
+ ### Starting and Stopping
22
+
23
+ To start the metrognome, run:
24
+
25
+ $ bundle exec rake metrognome:start
26
+
27
+ In production, use RAILS\_ENV to let Rails know to start the right environment:
28
+
29
+ $ bundle exec rake RAILS_ENV=production metrognome:start
30
+
31
+ To stop the metrognome, run:
32
+
33
+ $ bundle exec rake metrognome:stop
34
+
35
+ ### Schedulers
36
+
37
+ Schedulers are registered with metrognome and run on a periodic basis.
38
+ Schedulers are `.rb` files read from the folder `config/metrognome`
39
+
40
+ #### Initialization and task
41
+
42
+ All schedulers are instances of Metrognome::Scheduler, initialized like:
43
+
44
+ ```ruby
45
+ beeper = Metrognome::Scheduler.new(10.seconds)
46
+ ```
47
+
48
+ The beeper task will be executed every 10 seconds beginning when the metrognome
49
+ starts. To define the task to execute, pass a block to `beeper.task`:
50
+
51
+ ```ruby
52
+ beeper.task do
53
+ puts "Beep!"
54
+ end
55
+ ```
56
+
57
+ Finally, register the task with `Metrognome::Registrar.register`:
58
+
59
+ ```ruby
60
+ Metrognome::Registrar.register beeper
61
+ ```
62
+
63
+ #### Setup and local variables
64
+
65
+ Most schedulers can benefit from some persistence, or at least some setup. Let's
66
+ have our beeper keep track of how many times it's beeped.
67
+
68
+ Accessors can be declared when the scheduler is initialized:
69
+
70
+ ```ruby
71
+ beeper = Metrognome::Scheduler.new(10.seconds, :counter)
72
+ ```
73
+
74
+ The counter can be given an initial value by passing a block to `beeper.setup`:
75
+
76
+ ```ruby
77
+ beeper.setup do
78
+ self.counter = 0
79
+ end
80
+ ```
81
+
82
+ Finally, the counter can be used in the task block:
83
+
84
+ ```ruby
85
+ beeper.task do
86
+ self.counter += 1
87
+ logger.info "Beep! #{counter} time(s)"
88
+ end
89
+ ```
22
90
 
23
91
  ## Contributing
24
92
 
@@ -3,5 +3,4 @@ require 'metrognome/railtie'
3
3
 
4
4
  require 'metrognome/scheduler'
5
5
  require 'metrognome/registrar'
6
- require 'metrognome/runner'
7
6
 
@@ -1,27 +1,41 @@
1
+ require 'singleton'
2
+
1
3
  module Metrognome
2
4
  class Registrar
3
- class << self
4
- def registered
5
- @registered ||= []
6
- end
5
+ include Singleton
7
6
 
8
- def register scheduler
9
- raise ArgumentError.new "scheduler must be a Metrognome::Scheduler!" unless scheduler.is_a? Metrognome::Scheduler
7
+ def self.register scheduler
8
+ self.instance.register scheduler
9
+ end
10
10
 
11
- @registered ||= []
12
- @registered << scheduler
13
- scheduler.lock = next_filename
14
- scheduler.last = Time.now - 1.year
15
- scheduler.call_setup
11
+ def register scheduler
12
+ unless scheduler.is_a? Metrognome::Scheduler
13
+ raise ArgumentError.new "scheduler #{scheduler.inspect} must be a Metrognome::Scheduler."
16
14
  end
17
15
 
18
- private
19
- def next_filename
20
- @counter ||= 0
21
- @counter += 1
22
- "metrognome-#{@counter}"
23
- end
16
+ @registered ||= []
17
+ @registered << scheduler
18
+ end
19
+
20
+ def start
21
+ @registered.each { |scheduler| scheduler.run }
22
+
23
+ wait_for_sigterm
24
+ Signal.trap("TERM") { exit 1 }
25
+
26
+ @registered.each do |scheduler|
27
+ scheduler.stop
28
+ scheduler.thread.run
29
+ end
30
+ @registered.each { |scheduler| scheduler.thread.join }
24
31
  end
32
+
33
+ private
34
+ # Sleep until we get a SIGTERM.
35
+ def wait_for_sigterm
36
+ Signal.trap("TERM") { return }
37
+ sleep
38
+ end
25
39
  end
26
40
  end
27
41
 
@@ -1,18 +1,11 @@
1
1
  module Metrognome
2
2
  class Scheduler
3
- attr_accessor :interval, :lock, :last
3
+ attr_reader :thread
4
4
 
5
- def initialize interval, *variables
6
- self.interval = interval
7
-
8
- variables.each do |v|
9
- raise ArgumentError.new "All variables must be Strings or Symbols!" unless v.is_a? String or v.is_a? Symbol
10
- eval "class << self; attr_accessor :#{v}; end"
11
- end
12
- end
13
-
14
- def lock
15
- File.join(Rails.root, 'tmp', @lock)
5
+ def initialize interval, description = 'Anonymous scheduler'
6
+ @interval = interval
7
+ @description = description
8
+ @stopped = false
16
9
  end
17
10
 
18
11
  def setup &block
@@ -23,15 +16,26 @@ module Metrognome
23
16
  @task = block
24
17
  end
25
18
 
26
- def call_setup
27
- instance_eval &@setup if @setup
19
+ def stop
20
+ @stopped = true
28
21
  end
29
22
 
30
- def call_task
31
- File.open(lock, File::RDWR | File::CREAT, 0600) do |file|
32
- break unless file.flock(File::LOCK_EX | File::LOCK_NB)
33
- self.last = Time.now
34
- instance_eval &@task if @task
23
+ def run
24
+ @thread = Thread.new do
25
+ instance_eval(&@setup) if @setup
26
+
27
+ until @stopped do
28
+ start = Time.now
29
+ Rails.logger.info "#{@description} beginning task execution."
30
+ instance_eval(&@task) if @task
31
+ Rails.logger.info "#{@description} completed task execution."
32
+ # TODO log stopping task
33
+
34
+ until @stopped do
35
+ to_sleep = start + @interval - Time.now
36
+ break if to_sleep <= 0 or Kernel.sleep(to_sleep) >= to_sleep
37
+ end
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -1,3 +1,3 @@
1
1
  module Metrognome
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -14,11 +14,13 @@ namespace :metrognome do
14
14
  desc 'Begin the metrognome scheduler'
15
15
  task :start => :register do
16
16
  unless File.exists? Metrognome::PIDFILE
17
+ # Ignore SIGHUP since we'll probably be started from an SSH session.
17
18
  Signal.trap 'HUP', 'IGNORE'
18
19
 
19
20
  pid = fork do
20
21
  Rails.logger = Logger.new 'log/metrognome.log'
21
- Metrognome::Runner.start
22
+ Rails.logger.formatter = Logger::Formatter.new
23
+ Metrognome::Registrar.instance.start
22
24
  end
23
25
 
24
26
  Process.detach pid
@@ -18,4 +18,6 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_runtime_dependency 'rails', '~> 3.2'
21
+
22
+ gem.add_development_dependency 'rspec'
21
23
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'active_support/time'
3
+
4
+ describe Metrognome::Scheduler do
5
+ before do
6
+ @dummy_scheduler = Metrognome::Scheduler.new 1.minute, 'Test scheduler'
7
+ @dummy_scheduler.setup { @task_counter = 0 }
8
+ @dummy_scheduler.task do
9
+ if @task_counter == 5
10
+ self.stop
11
+ else
12
+ @task_counter += 1
13
+ end
14
+ end
15
+
16
+ @slept = 0
17
+ Kernel.stub(:sleep) do |arg|
18
+ # Make sure we're only sleeping up to the schedule time.
19
+ arg.should be < 1.minute
20
+ @slept += 1
21
+ arg + 1 # Always break out of the sleep loop.
22
+ end
23
+
24
+ # Metrognome should run in a Rails app, so set the logger we use.
25
+ Rails.logger = Logger.new nil
26
+ end
27
+
28
+ it 'should count to 5' do
29
+ @dummy_scheduler.run
30
+ Thread.pass while @dummy_scheduler.thread.alive?
31
+ @slept.should == 5
32
+ @dummy_scheduler.instance_eval { @task_counter }.should == 5
33
+ end
34
+
35
+ it 'should exit without exception' do
36
+ @dummy_scheduler.run
37
+ Thread.pass while @dummy_scheduler.thread.alive?
38
+
39
+ # The scheduler thread status is nil on exit due to exception.
40
+ @dummy_scheduler.thread.status.should === false
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'metrognome'
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metrognome
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.0.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Chris Carlon
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-01-22 00:00:00.000000000 Z
11
+ date: 2014-03-31 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rails
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
@@ -22,11 +20,24 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
29
26
  version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
30
41
  description: A simple repeating task scheduler for Rails applications
31
42
  email:
32
43
  - chris.carlon25@gmail.com
@@ -42,33 +53,35 @@ files:
42
53
  - lib/metrognome.rb
43
54
  - lib/metrognome/railtie.rb
44
55
  - lib/metrognome/registrar.rb
45
- - lib/metrognome/runner.rb
46
56
  - lib/metrognome/scheduler.rb
47
57
  - lib/metrognome/version.rb
48
58
  - lib/tasks/metrognome.rake
49
59
  - metrognome.gemspec
60
+ - spec/metrognome/scheduler_spec.rb
61
+ - spec/spec_helper.rb
50
62
  homepage: https://github.com/cjc25/metrognome
51
63
  licenses: []
64
+ metadata: {}
52
65
  post_install_message:
53
66
  rdoc_options: []
54
67
  require_paths:
55
68
  - lib
56
69
  required_ruby_version: !ruby/object:Gem::Requirement
57
- none: false
58
70
  requirements:
59
71
  - - ! '>='
60
72
  - !ruby/object:Gem::Version
61
73
  version: '0'
62
74
  required_rubygems_version: !ruby/object:Gem::Requirement
63
- none: false
64
75
  requirements:
65
76
  - - ! '>='
66
77
  - !ruby/object:Gem::Version
67
78
  version: '0'
68
79
  requirements: []
69
80
  rubyforge_project:
70
- rubygems_version: 1.8.24
81
+ rubygems_version: 2.1.11
71
82
  signing_key:
72
- specification_version: 3
83
+ specification_version: 4
73
84
  summary: A simple repeating task scheduler for Rails applications
74
- test_files: []
85
+ test_files:
86
+ - spec/metrognome/scheduler_spec.rb
87
+ - spec/spec_helper.rb
@@ -1,55 +0,0 @@
1
- module Metrognome
2
- class Runner
3
- class << self
4
- # TODO pull out reaper_interval into config
5
- attr_accessor :running, :reaper_last
6
- REAPER_INTERVAL = 1
7
-
8
- def start
9
- setup
10
-
11
- loop do
12
- if @_sigterm
13
- running.each { |t| t.kill }
14
- reap
15
- break
16
- end
17
- reap if Time.now - reaper_last > REAPER_INTERVAL
18
-
19
- Metrognome::Registrar.registered.each do |sched|
20
- if Time.now - sched.last > sched.interval
21
- running << Thread.new do
22
- sched.call_task
23
-
24
- # DB connections will no longer be closed automatically, so
25
- # close it manually here
26
- ActiveRecord::Base.connection.close
27
- end
28
- end
29
- end
30
- # TODO GCD sleep instead of 1
31
- sleep 1
32
- end
33
-
34
- Metrognome::Registrar.registered.each { |s| File.delete s.lock }
35
- end
36
-
37
- private
38
- def setup
39
- require 'set'
40
- self.running = Set.new
41
- self.reaper_last = Time.now
42
-
43
- Signal.trap "TERM" do
44
- exit 1 if @_sigterm
45
- @_sigterm = 1
46
- end
47
- end
48
-
49
- def reap
50
- running.delete_if { |t| t.join(0) }
51
- self.reaper_last = Time.now
52
- end
53
- end
54
- end
55
- end