threasy 0.1.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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: