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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36bcb6a7e87480c94e15a521e2d74a7de07ed995
4
- data.tar.gz: 7f049f5eb6155d16b14278e599544268cf3426cc
3
+ metadata.gz: 1e72cd2a275c0cc94bcac05463b285062788c4bd
4
+ data.tar.gz: 37d0d9082bca7164f370fca2f1e8c7ec057db9f8
5
5
  SHA512:
6
- metadata.gz: 21fc8a8219c66db4fc212dd6a992dcea86b68f22bb5b3102c5e2c628636dadb23a17b138d759dcc982a6abe46581fd151953250e19425094b7093c83bce915cc
7
- data.tar.gz: c38a34ed3220b5ed5c2b804ff714f9fabf8d413959d647bef757f8585cd5bfbd884746deddce9aec1e056003ccd03e5d8871a056f29c9ac3bb8496ca32e11809
6
+ metadata.gz: 4f4f468799a33bea2755f2bf99ff971940197296c3ace39a4a55737c8d2f06c5250f1a27c8d1e1d33ea5ba72eba143f0f1901a4d7fdc844ff0ab4c3b582c1064
7
+ data.tar.gz: 46f254797c31d50e690c36d6c388b79053997e5e0e0f59af72f1c59938965c4369a975c4b33f552e14e6c70407a2e595dcaa97ec64d6cec3c23e25d57fe01c0c
data/lib/threasy.rb CHANGED
@@ -4,6 +4,7 @@ require "threasy/version"
4
4
  require "threasy/config"
5
5
  require "threasy/work"
6
6
  require "threasy/schedule"
7
+ require "threasy/schedule/entry"
7
8
 
8
9
  module Threasy
9
10
  def self.config
@@ -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 = 5
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
@@ -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(self, job, options)
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
- @schedules << entry
23
- @schedules.sort_by!(&:at)
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{ @schedules.delete entry }
34
+ sync { schedules.delete entry }
35
35
  end
36
36
 
37
37
  def tickle_watcher
38
- @watcher.wakeup if @watcher.stop?
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
- entries.each { |entry| yield entry }
46
+ schedules.each { |entry| yield entry }
51
47
  end
52
48
 
53
49
  def clear
54
50
  log.debug "Clearing schedules"
55
- sync{ @schedules.clear }
51
+ sync { schedules.clear }
56
52
  end
57
53
 
58
54
  def watch
59
55
  loop do
60
- Thread.stop if @schedules.empty?
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, MAX_SLEEP].min
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 @schedules.first && @schedules.first.due?
79
- entries << @schedules.shift
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
- @schedules.count
82
+ schedules.count
87
83
  end
88
84
 
89
85
  def log
90
86
  Threasy.logger
91
87
  end
92
88
 
93
- class Entry
94
- MAX_OVERDUE = 300 # 5 minutes
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
@@ -1,3 +1,3 @@
1
1
  module Threasy
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -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.2
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.2
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.3
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.2
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.2
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.3
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).at_most(:twice)
48
- entry = subject.add(job, every: 0.2)
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
- sleep 0.5
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.2) }
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.1.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-16 00:00:00.000000000 Z
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: '0'
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: '0'
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: