steady 0.0.1

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.
@@ -0,0 +1 @@
1
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+
4
+ gemspec
@@ -0,0 +1,10 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ speedytime (0.0.2)
5
+
6
+ PLATFORMS
7
+ ruby
8
+
9
+ DEPENDENCIES
10
+ speedytime
@@ -0,0 +1,30 @@
1
+ # Steady
2
+
3
+ This gem is aimed at helping speed up ruby webapps by putting reoccuring but non time critical tasks into a background thread.
4
+ It aids with the periodic scheduling as well as the threading issues that arise from moving data between threads.
5
+
6
+ # Example
7
+
8
+ Scheduler = Steady::Scheduler.new
9
+
10
+ Scheduler.every 3.seconds do |changes|
11
+ changes[:plans] = JSON.parse(open("http://mysite.com/plans.json"))
12
+ end
13
+
14
+ # Run all above tasks now to get initial data
15
+ Scheduler.run
16
+
17
+ # Schedule a thread to do this periodically
18
+ Scheduler.schedule
19
+
20
+
21
+ # Access your data in a thread safe manner
22
+ Scheduler.data[:plans]
23
+
24
+ # Uses at Shopify
25
+
26
+ * Monitoring read slave lagginess
27
+ * Fetching blog posts to show in the admin
28
+ * Fetching centrally configured beta flags from remote
29
+ * and many many more
30
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ require 'bundler'
7
+ require 'bundler/gem_tasks'
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << '.' << 'lib' << 'test'
11
+ t.test_files = FileList['test/**/*_test.rb']
12
+ t.verbose = false
13
+ end
14
+
@@ -0,0 +1,134 @@
1
+ require 'speedytime'
2
+ require 'thread'
3
+ require 'set'
4
+
5
+ module Steady
6
+
7
+ class SyncronizedHash
8
+ def initialize
9
+ @hash = Hash.new
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ def [](key)
14
+ @mutex.synchronize { @hash[key] }
15
+ end
16
+
17
+ def []=(key)
18
+ @mutex.synchronize { @hash.[]=(key, value) }
19
+ end
20
+
21
+ def apply(changes)
22
+ @mutex.synchronize { @hash.merge!(changes) }
23
+ end
24
+ end
25
+
26
+ class DirtyTrackingHash < Hash
27
+ def dirty?
28
+ @dirty
29
+ end
30
+
31
+ def []=(key, value)
32
+ @dirty = true
33
+ super
34
+ end
35
+ end
36
+
37
+ class Scheduler
38
+ attr_reader :tasks, :data
39
+
40
+ def initialize
41
+ @tasks = []
42
+ @data = SyncronizedHash.new
43
+ end
44
+
45
+ def every(interval, &block)
46
+ push Task.new(interval, &block)
47
+ end
48
+
49
+ def run
50
+ changes = DirtyTrackingHash.new
51
+ runs = false
52
+
53
+
54
+ @tasks.each do |task|
55
+ if task.needs_running?
56
+ task.run(changes)
57
+ runs = true
58
+ else
59
+ # tasks are sorted, if a tasks doesn't need running
60
+ # then no task behind it will need running either at
61
+ # the moment.
62
+ break
63
+ end
64
+ end
65
+
66
+ # if tasks ran we resort the tasks and apply
67
+ # any changes to the data hash
68
+ if runs
69
+ @tasks.sort!
70
+ @data.apply(changes) if changes.dirty?
71
+ true
72
+ else
73
+ false
74
+ end
75
+ end
76
+
77
+ def schedule
78
+ Thread.new do
79
+ loop { sleep; run }
80
+ end
81
+ end
82
+
83
+ def drowsiness
84
+ drowsiness = (t = @tasks.first) ? t.next_run - Speedytime.current : 1
85
+ drowsiness = 1 if drowsiness < 1
86
+ drowsiness
87
+ end
88
+
89
+ def sleep
90
+ sleep(drowsiness)
91
+ end
92
+
93
+ private
94
+
95
+ def push(task)
96
+ @tasks.push(task)
97
+ @tasks.sort!
98
+ end
99
+
100
+ end
101
+
102
+ class Task
103
+ attr_accessor :last_run, :interval
104
+
105
+ def initialize(interval, &block)
106
+ @interval = interval
107
+ @proc = block
108
+ @last_run = 0
109
+ end
110
+
111
+ def next_run
112
+ @last_run + @interval
113
+ end
114
+
115
+ def needs_running?
116
+ next_run <= Speedytime.current
117
+ end
118
+
119
+ def run(changes = nil)
120
+ if needs_running?
121
+ @proc.call(changes)
122
+ @last_run = Speedytime.current
123
+ true
124
+ else
125
+ false
126
+ end
127
+ end
128
+
129
+ def <=>(other)
130
+ next_run <=> other.next_run
131
+ end
132
+ end
133
+
134
+ end
@@ -0,0 +1,3 @@
1
+ module Steady
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'steady/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "steady"
8
+ gem.version = Steady::VERSION
9
+ gem.authors = ["Tobias Lutke"]
10
+ gem.email = ["tobi@shopify.com"]
11
+ gem.description = %q{Periodically run tasks that fetch data}
12
+ gem.summary = %q{Simple worker thread for steady tasks}
13
+ gem.homepage = ""
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_dependency('speedytime')
20
+ end
@@ -0,0 +1,81 @@
1
+ require "test/unit"
2
+ require "steady"
3
+
4
+ class SteadyTask < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @sched = Steady::Scheduler.new
8
+ end
9
+
10
+ def test_tasks_run
11
+ i = 0
12
+ @sched.every 1 do
13
+ i += 1
14
+ end
15
+
16
+ assert_equal 1, @sched.tasks.length
17
+ @sched.run
18
+ assert_equal 1, @sched.tasks.length
19
+
20
+ assert_equal 1, i
21
+ end
22
+
23
+ def test_task_sorting
24
+ i = 0
25
+
26
+ @sched.every 2 do
27
+ i += 1
28
+ end
29
+ @sched.every 5 do
30
+ i += 1
31
+ end
32
+ @sched.every 1 do
33
+ i += 1
34
+ end
35
+
36
+ @sched.run
37
+ assert_equal 3, i
38
+
39
+ sleep 1.1
40
+
41
+ @sched.run
42
+ assert_equal 4, i
43
+
44
+ sleep 1.1
45
+
46
+ @sched.run
47
+ assert_equal 6, i
48
+ end
49
+
50
+ def test_task_drowsiness
51
+
52
+ @sched.every 5 do
53
+ end
54
+
55
+ assert_equal 1, @sched.drowsiness
56
+
57
+ @sched.run
58
+
59
+ assert_equal 5, @sched.drowsiness
60
+
61
+ @sched.every 1 do
62
+ end
63
+
64
+ assert_equal 1, @sched.drowsiness
65
+
66
+ end
67
+
68
+ def test_mege
69
+
70
+ assert_equal nil, @sched.data[:test]
71
+
72
+ @sched.every 1 do |changes|
73
+ changes[:test] = 'ok'
74
+ end
75
+
76
+ @sched.run
77
+
78
+ assert_equal 'ok', @sched.data[:test]
79
+ end
80
+
81
+ end
@@ -0,0 +1,50 @@
1
+ require "test/unit"
2
+ require "periodic"
3
+
4
+ class TestTask < Test::Unit::TestCase
5
+
6
+ def test_tasks_run_by_default
7
+ i = 0
8
+
9
+ task = Periodic::Task.new(5) do
10
+ i += 1
11
+ end
12
+
13
+ task.run
14
+
15
+ assert_equal 1, i
16
+ end
17
+
18
+ def test_tasks_run_one_per_second
19
+ i = 0
20
+
21
+ task = Periodic::Task.new(1) do
22
+ i += 1
23
+ end
24
+
25
+ task.run
26
+ task.run
27
+ sleep 1.1
28
+ task.run
29
+ task.run
30
+
31
+ assert_equal 2, i, 'should have been 2 because only 2 unique seconds elapsed'
32
+ end
33
+
34
+ def test_needs_running?
35
+ task = Periodic::Task.new(1)
36
+ assert task.needs_running?
37
+ task.last_run = Speedytime.current
38
+ assert !task.needs_running?
39
+ task.last_run = Speedytime.current - 5
40
+ assert task.needs_running?
41
+ end
42
+
43
+ def test_interval
44
+ task = Periodic::Task.new(1)
45
+ task.last_run = Speedytime.current - 5
46
+ assert task.needs_running?
47
+ task.interval = 6
48
+ assert !task.needs_running?
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: steady
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tobias Lutke
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: speedytime
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Periodically run tasks that fetch data
31
+ email:
32
+ - tobi@shopify.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - README.md
41
+ - Rakefile
42
+ - lib/steady.rb
43
+ - lib/steady/version.rb
44
+ - steady.gemspec
45
+ - test/steady_test.rb
46
+ - test/task_test.rb
47
+ homepage: ''
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 1.8.23
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Simple worker thread for steady tasks
71
+ test_files:
72
+ - test/steady_test.rb
73
+ - test/task_test.rb