threasy 0.1.0 → 0.2.2
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.
- checksums.yaml +4 -4
- data/lib/threasy.rb +1 -0
- data/lib/threasy/config.rb +5 -3
- data/lib/threasy/schedule.rb +16 -61
- data/lib/threasy/schedule/entry.rb +49 -0
- data/lib/threasy/version.rb +1 -1
- data/spec/threasy/schedule/entry_spec.rb +22 -0
- data/spec/threasy/schedule_spec.rb +15 -39
- data/threasy.gemspec +4 -4
- metadata +21 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e72cd2a275c0cc94bcac05463b285062788c4bd
|
4
|
+
data.tar.gz: 37d0d9082bca7164f370fca2f1e8c7ec057db9f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f4f468799a33bea2755f2bf99ff971940197296c3ace39a4a55737c8d2f06c5250f1a27c8d1e1d33ea5ba72eba143f0f1901a4d7fdc844ff0ab4c3b582c1064
|
7
|
+
data.tar.gz: 46f254797c31d50e690c36d6c388b79053997e5e0e0f59af72f1c59938965c4369a975c4b33f552e14e6c70407a2e595dcaa97ec64d6cec3c23e25d57fe01c0c
|
data/lib/threasy.rb
CHANGED
data/lib/threasy/config.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
module Threasy
|
2
2
|
class Config
|
3
|
-
attr_accessor :work, :schedule, :max_workers
|
3
|
+
attr_accessor :work, :schedule, :max_workers, :max_sleep, :max_overdue
|
4
4
|
attr_writer :logger
|
5
5
|
|
6
6
|
def initialize
|
7
|
-
self.max_workers
|
7
|
+
self.max_workers = 4
|
8
|
+
self.max_sleep = 60.0
|
9
|
+
self.max_overdue = 300.0
|
8
10
|
end
|
9
11
|
|
10
12
|
def logger
|
11
|
-
@logger ||= Logger.new(STDOUT).tap{|l| l.level = Logger::INFO }
|
13
|
+
@logger ||= Logger.new(STDOUT).tap { |l| l.level = Logger::INFO }
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/threasy/schedule.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Threasy
|
2
2
|
class Schedule
|
3
|
-
MAX_SLEEP = 5.0 # 5 seconds
|
4
|
-
|
5
3
|
include Enumerable
|
6
4
|
|
5
|
+
attr_reader :schedules, :watcher
|
6
|
+
|
7
7
|
def initialize(work = nil)
|
8
8
|
@work = work
|
9
9
|
@semaphore = Mutex.new
|
@@ -14,13 +14,13 @@ module Threasy
|
|
14
14
|
def add(*args, &block)
|
15
15
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
16
16
|
job = block_given? ? block : args.first
|
17
|
-
add_entry Entry.new(
|
17
|
+
add_entry Entry.new(job, {schedule: self}.merge(options))
|
18
18
|
end
|
19
19
|
|
20
20
|
def add_entry(entry)
|
21
21
|
sync do
|
22
|
-
|
23
|
-
|
22
|
+
schedules << entry
|
23
|
+
schedules.sort_by!(&:at)
|
24
24
|
end
|
25
25
|
tickle_watcher
|
26
26
|
entry
|
@@ -31,33 +31,29 @@ module Threasy
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def remove_entry(entry)
|
34
|
-
sync{
|
34
|
+
sync { schedules.delete entry }
|
35
35
|
end
|
36
36
|
|
37
37
|
def tickle_watcher
|
38
|
-
|
38
|
+
watcher.wakeup if watcher.stop?
|
39
39
|
end
|
40
40
|
|
41
41
|
def sync
|
42
42
|
@semaphore.synchronize { yield }
|
43
43
|
end
|
44
44
|
|
45
|
-
def entries
|
46
|
-
@schedules
|
47
|
-
end
|
48
|
-
|
49
45
|
def each
|
50
|
-
|
46
|
+
schedules.each { |entry| yield entry }
|
51
47
|
end
|
52
48
|
|
53
49
|
def clear
|
54
50
|
log.debug "Clearing schedules"
|
55
|
-
sync{
|
51
|
+
sync { schedules.clear }
|
56
52
|
end
|
57
53
|
|
58
54
|
def watch
|
59
55
|
loop do
|
60
|
-
Thread.stop if
|
56
|
+
Thread.stop if schedules.empty?
|
61
57
|
entries_due.each do |entry|
|
62
58
|
log.debug "Adding scheduled job to work queue"
|
63
59
|
entry.work!
|
@@ -65,7 +61,7 @@ module Threasy
|
|
65
61
|
end
|
66
62
|
next_job = @schedules.first
|
67
63
|
if next_job && next_job.future?
|
68
|
-
seconds = [next_job.at - Time.now,
|
64
|
+
seconds = [next_job.at - Time.now, max_sleep].min
|
69
65
|
log.debug "Schedule watcher sleeping for #{seconds} seconds"
|
70
66
|
sleep seconds
|
71
67
|
end
|
@@ -75,64 +71,23 @@ module Threasy
|
|
75
71
|
def entries_due
|
76
72
|
[].tap do |entries|
|
77
73
|
sync do
|
78
|
-
while
|
79
|
-
entries <<
|
74
|
+
while schedules.first && schedules.first.due?
|
75
|
+
entries << schedules.shift
|
80
76
|
end
|
81
77
|
end
|
82
78
|
end
|
83
79
|
end
|
84
80
|
|
85
81
|
def count
|
86
|
-
|
82
|
+
schedules.count
|
87
83
|
end
|
88
84
|
|
89
85
|
def log
|
90
86
|
Threasy.logger
|
91
87
|
end
|
92
88
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
attr_accessor :schedule, :job, :at, :repeat
|
97
|
-
|
98
|
-
def initialize(schedule, job, options = {})
|
99
|
-
self.schedule = schedule
|
100
|
-
self.job = job
|
101
|
-
self.repeat = options[:every]
|
102
|
-
seconds = options.fetch(:in){ repeat || 60 }
|
103
|
-
self.at = options.fetch(:at){ Time.now + seconds }
|
104
|
-
end
|
105
|
-
|
106
|
-
def repeat?
|
107
|
-
!! repeat
|
108
|
-
end
|
109
|
-
|
110
|
-
def once?
|
111
|
-
! repeat?
|
112
|
-
end
|
113
|
-
|
114
|
-
def due?
|
115
|
-
Time.now > at
|
116
|
-
end
|
117
|
-
|
118
|
-
def future?
|
119
|
-
! due?
|
120
|
-
end
|
121
|
-
|
122
|
-
def overdue
|
123
|
-
Time.now - at
|
124
|
-
end
|
125
|
-
|
126
|
-
def work!
|
127
|
-
if once? || overdue < MAX_OVERDUE
|
128
|
-
schedule.work.enqueue job
|
129
|
-
end
|
130
|
-
self.at = at + repeat if repeat?
|
131
|
-
end
|
132
|
-
|
133
|
-
def remove
|
134
|
-
schedule.remove_entry self
|
135
|
-
end
|
89
|
+
def max_sleep
|
90
|
+
Threasy.config.max_sleep
|
136
91
|
end
|
137
92
|
end
|
138
93
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Threasy
|
2
|
+
class Schedule::Entry
|
3
|
+
attr_accessor :schedule, :work, :job, :at, :repeat
|
4
|
+
|
5
|
+
def initialize(job, options = {})
|
6
|
+
self.schedule = options.fetch(:schedule) { Threasy.schedules }
|
7
|
+
self.work = options.fetch(:work) { schedule.work }
|
8
|
+
self.job = job
|
9
|
+
self.repeat = options[:every]
|
10
|
+
seconds = options.fetch(:in) { repeat || 60 }
|
11
|
+
self.at = options.fetch(:at) { Time.now + seconds }
|
12
|
+
end
|
13
|
+
|
14
|
+
def repeat?
|
15
|
+
!! repeat
|
16
|
+
end
|
17
|
+
|
18
|
+
def once?
|
19
|
+
! repeat?
|
20
|
+
end
|
21
|
+
|
22
|
+
def due?
|
23
|
+
Time.now > at
|
24
|
+
end
|
25
|
+
|
26
|
+
def future?
|
27
|
+
! due?
|
28
|
+
end
|
29
|
+
|
30
|
+
def overdue
|
31
|
+
Time.now - at
|
32
|
+
end
|
33
|
+
|
34
|
+
def max_overdue
|
35
|
+
Threasy.config.max_overdue
|
36
|
+
end
|
37
|
+
|
38
|
+
def work!
|
39
|
+
if once? || overdue < max_overdue
|
40
|
+
work.enqueue job
|
41
|
+
end
|
42
|
+
self.at = at + repeat if repeat?
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove
|
46
|
+
schedule.remove_entry self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/threasy/version.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
describe Threasy::Schedule::Entry do
|
2
|
+
let(:work) { double("work") }
|
3
|
+
let(:job) { double("work") }
|
4
|
+
let(:hour) { 60 * 60 }
|
5
|
+
|
6
|
+
subject { Threasy::Schedule::Entry }
|
7
|
+
|
8
|
+
describe "#work!" do
|
9
|
+
it "should execute a one-time job long after its due" do
|
10
|
+
expect(work).to receive(:enqueue).with(job)
|
11
|
+
entry = subject.new(job, work: work, in: hour/2)
|
12
|
+
Timecop.travel(Time.now + hour) { entry.work! }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not execute a repeating job long after its due" do
|
16
|
+
expect(work).not_to receive(:enqueue).with(job)
|
17
|
+
entry = subject.new(job, work: work, in: hour/2, every: hour)
|
18
|
+
Timecop.travel(Time.now + hour) { entry.work! }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -7,48 +7,52 @@ describe "Threasy::Schedule" do
|
|
7
7
|
it "should allow a job to be processed after specified delay" do
|
8
8
|
expect(work).to receive(:enqueue).with(job)
|
9
9
|
subject.add(job, in: 0.1)
|
10
|
-
sleep 0.
|
10
|
+
sleep 0.3
|
11
11
|
end
|
12
12
|
|
13
13
|
it "should allow a job to be processed at the specified time" do
|
14
14
|
expect(work).to receive(:enqueue).with(job)
|
15
15
|
subject.add(job, at: Time.now + 0.1)
|
16
|
-
sleep 0.
|
16
|
+
sleep 0.3
|
17
17
|
end
|
18
18
|
|
19
19
|
it "should allow a job to be repeated at the specified interval" do
|
20
20
|
expect(work).to receive(:enqueue).with(job).at_least(:twice)
|
21
21
|
subject.add(job, every: 0.1, in: 0.1)
|
22
|
-
sleep 0.
|
22
|
+
sleep 0.5
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should allow blocks to be processed on schedule" do
|
26
26
|
block_job = ->{ 1 + 1 }
|
27
27
|
expect(work).to receive(:enqueue).with(block_job)
|
28
28
|
subject.add(in: 0.1, &block_job)
|
29
|
-
sleep 0.
|
29
|
+
sleep 0.3
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should allow string expressions to be processed on schedule" do
|
33
33
|
expect(work).to receive(:enqueue).with("TestScheduledJob")
|
34
34
|
subject.add("TestScheduledJob", in: 0.1)
|
35
|
-
sleep 0.
|
35
|
+
sleep 0.3
|
36
36
|
end
|
37
37
|
|
38
38
|
it "should default first run to now + every_interval" do
|
39
39
|
expect(work).to receive(:enqueue).with(job).at_least(:twice)
|
40
40
|
subject.add(job, every: 0.1)
|
41
|
-
sleep 0.
|
41
|
+
sleep 0.5
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
describe "#remove" do
|
46
46
|
it "should be possible to remove a job from the schedule" do
|
47
|
-
expect(work).to receive(:enqueue).with(job).at_least(:once)
|
48
|
-
|
47
|
+
expect(work).to receive(:enqueue).with(job).at_least(:once)
|
48
|
+
|
49
|
+
entry = subject.add(job, every: 0.1)
|
49
50
|
sleep 0.3
|
51
|
+
|
50
52
|
entry.remove
|
51
|
-
|
53
|
+
|
54
|
+
expect(work).not_to receive(:enqueue)
|
55
|
+
sleep 0.3
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
@@ -57,46 +61,18 @@ describe "Threasy::Schedule" do
|
|
57
61
|
let(:hour) { 60*60 }
|
58
62
|
|
59
63
|
it "should recover in a few seconds when time suddenly jumps forward" do
|
64
|
+
Threasy.config.max_sleep = 0.1
|
60
65
|
job_ran = false
|
61
66
|
job = -> { job_ran = true }
|
62
67
|
subject.add(in: hour + 1, &job)
|
63
68
|
|
64
69
|
Timecop.travel(Time.now + hour) do
|
65
70
|
Timeout.timeout(6) do
|
66
|
-
loop { job_ran ? break : sleep(0.
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
it "should execute a one-time schedule long after it's due" do
|
72
|
-
job_ran = false
|
73
|
-
job = -> { job_ran = true }
|
74
|
-
subject.add(in: hour/2, &job)
|
75
|
-
|
76
|
-
Timecop.travel(Time.now + hour) do
|
77
|
-
Timeout.timeout(6) do
|
78
|
-
loop { job_ran ? break : sleep(0.2) }
|
71
|
+
loop { job_ran ? break : sleep(0.1) }
|
79
72
|
end
|
80
73
|
end
|
81
74
|
end
|
82
75
|
|
83
|
-
# This really tests Schedule::Entry and should be moved to its own spec
|
84
|
-
it "should skip a repeating schedule that is long past due" do
|
85
|
-
job1_ran = false
|
86
|
-
job1 = -> { job1_ran = true }
|
87
|
-
|
88
|
-
job2_ran = false
|
89
|
-
job2 = -> { job2_ran = true }
|
90
|
-
|
91
|
-
subject.add(every: hour, in: hour/2, &job1)
|
92
|
-
subject.add(in: hour/2, &job2)
|
93
|
-
|
94
|
-
Timecop.travel(Time.now + hour) { sleep 6 }
|
95
|
-
|
96
|
-
expect(job1_ran).to eq(false)
|
97
|
-
expect(job2_ran).to eq(true)
|
98
|
-
end
|
99
|
-
|
100
76
|
end
|
101
77
|
|
102
78
|
end
|
data/threasy.gemspec
CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
23
|
s.add_development_dependency "bundler", "~> 1.5"
|
24
|
-
s.add_development_dependency "rake"
|
25
|
-
s.add_development_dependency "pry"
|
26
|
-
s.add_development_dependency "rspec"
|
27
|
-
s.add_development_dependency "timecop"
|
24
|
+
s.add_development_dependency "rake", "~> 10.0"
|
25
|
+
s.add_development_dependency "pry", "~> 0.10"
|
26
|
+
s.add_development_dependency "rspec", "~> 3.3"
|
27
|
+
s.add_development_dependency "timecop", "~> 0.8"
|
28
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: threasy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Zulauf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,58 +28,58 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '10.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '10.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
47
|
+
version: '0.10'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
54
|
+
version: '0.10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '3.3'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '3.3'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: timecop
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
75
|
+
version: '0.8'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
82
|
+
version: '0.8'
|
83
83
|
description: Dead simple in-process background job solution using threads, with support
|
84
84
|
for scheduled jobs.
|
85
85
|
email:
|
@@ -95,9 +95,11 @@ files:
|
|
95
95
|
- lib/threasy.rb
|
96
96
|
- lib/threasy/config.rb
|
97
97
|
- lib/threasy/schedule.rb
|
98
|
+
- lib/threasy/schedule/entry.rb
|
98
99
|
- lib/threasy/version.rb
|
99
100
|
- lib/threasy/work.rb
|
100
101
|
- spec/spec_helper.rb
|
102
|
+
- spec/threasy/schedule/entry_spec.rb
|
101
103
|
- spec/threasy/schedule_spec.rb
|
102
104
|
- spec/threasy/work_spec.rb
|
103
105
|
- threasy.gemspec
|
@@ -127,6 +129,7 @@ specification_version: 4
|
|
127
129
|
summary: Simple threaded background jobs and scheduling.
|
128
130
|
test_files:
|
129
131
|
- spec/spec_helper.rb
|
132
|
+
- spec/threasy/schedule/entry_spec.rb
|
130
133
|
- spec/threasy/schedule_spec.rb
|
131
134
|
- spec/threasy/work_spec.rb
|
132
135
|
has_rdoc:
|