perfectsched 0.8.10 → 0.8.11

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: 759e18c224ac736b6883bde03b5cd95928e7e50f
4
- data.tar.gz: 08c3aa5151589a8b485fbe252e291162f515f447
3
+ metadata.gz: 6a1e556c19cb6846d4efa60879d267a272fd9d8c
4
+ data.tar.gz: a8c46704005fd12df81826d6759ab0a9934e8a8f
5
5
  SHA512:
6
- metadata.gz: d5d3af6f43295e37bc3932ee3367880f77c9489bd96e2aeae841574dd4e3581659a333ca08f2e8d98d8a51f07d17e5f452352b0512713a6483a131236f5a3537
7
- data.tar.gz: c85f86c27798a539c96d476df07893a63ec137d56ae9f1e383ccc426a5f3ba1fdab9fd6d693a744a781132688549b1f3b57989ac7e90efea8c85458a1f35d1fe
6
+ metadata.gz: 2f4ca879b0acb729876fddfaa0712694701c65208039e48e93ee0853d7b141e80d16f59bb2c894e38864fa737bfb86c9cdbfa72737e95b22d7b1e009fa570a3f
7
+ data.tar.gz: d99b81c74ccd32504d4d334259491eabdfb6fdf9db790ee6c6ac0120c9c9678ff678cd256364a7694c84ec74697195f7b74d4e89225fe209f8cd8e114e7250bc
data/.travis.yml CHANGED
@@ -1,8 +1,15 @@
1
1
  rvm:
2
- - 1.9.3
3
- - 2.0.0
4
- - 2.1.6
5
- - 2.2.2
2
+ - 2.2.5
3
+ - 2.3.1
6
4
  - ruby-head
7
5
 
8
6
  script: "bundle exec rake spec"
7
+
8
+ sudo: false
9
+
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: ruby-head
13
+
14
+ notifications:
15
+ webhooks: http://td-beda.herokuapp.com/travisci_callback
data/ChangeLog CHANGED
@@ -1,3 +1,9 @@
1
+ == 2016-09-18 version 0.8.11
2
+
3
+ * allow perfectqueue v0.9.x
4
+ * drop ruby 2.0 support
5
+ * use chrono instead of cron-spec
6
+ * explicitly specify development dependency rspec, simplecov and mysql
1
7
 
2
8
  == 2015-06-29 version 0.8.10
3
9
 
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ gem 'coveralls', require: false
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # PerfectSched
2
2
 
3
3
  [![Build Status](https://travis-ci.org/treasure-data/perfectsched.svg?branch=master)](https://travis-ci.org/treasure-data/perfectsched)
4
+ [![Coverage Status](https://coveralls.io/repos/treasure-data/perfectsched/badge.svg?branch=master&service=github)](https://coveralls.io/github/treasure-data/perfectsched?branch=master)
4
5
 
5
6
  PerfectSched is a highly available distributed cron built on top of RDBMS.
6
7
 
@@ -85,7 +86,7 @@ PerfectSched.open(config) {|sc|
85
86
  :next_time => Time.parse('2013-01-01 00:00:00 +0900').to_i,
86
87
  :data => data,
87
88
  }
88
- sc.submit("sched-id", "type1", options)
89
+ sc.add("sched-id", "type1", options)
89
90
  }
90
91
  ```
91
92
 
data/circle.yml ADDED
@@ -0,0 +1,3 @@
1
+ machine:
2
+ ruby:
3
+ version: 2.3.1
data/lib/perfectsched.rb CHANGED
@@ -52,7 +52,7 @@ module PerfectSched
52
52
  require File.expand_path(v, File.dirname(__FILE__))
53
53
  }
54
54
 
55
- require 'cron-spec'
55
+ require 'chrono'
56
56
  require 'tzinfo'
57
57
 
58
58
  def self.open(config, &block)
@@ -71,28 +71,9 @@ module PerfectSched
71
71
  end
72
72
 
73
73
  def self.cron_time(cron, timestamp, timezone)
74
- begin
75
- tab = CronSpec::CronSpecification.new(cron)
76
- rescue
77
- raise ArgumentError, "invalid cron format: #{$!}: #{cron.inspect}"
78
- end
79
-
80
- begin
81
- tz = TZInfo::Timezone.get(timezone) if timezone
82
- rescue
83
- raise ArgumentError, "unknown timezone: #{$!}: #{timezone.inspect}"
84
- end
85
-
86
- ts = (timestamp + 59) / 60 * 60
87
- while true
88
- t = Time.at(ts).utc
89
- t = tz.utc_to_local(t) if tz
90
- if tab.is_specification_in_effect?(t)
91
- return ts
92
- end
93
- ts += 60
94
- # FIXME break
95
- end
74
+ ts = timestamp - 1 # compatibility
75
+ t = Time.find_zone!(timezone || 'UTC'.freeze).at(ts)
76
+ Chrono::NextTime.new(now: t, source: cron).to_time.to_i
96
77
  end
97
78
 
98
79
  def self.next_time(cron, before_timestamp, timezone)
@@ -40,7 +40,7 @@ module PerfectSched
40
40
  begin
41
41
  m = method(type)
42
42
  rescue NameError
43
- raise UndefinedDecisionError, "Undefined decision #{type} options=#{opt.inspect}"
43
+ raise UndefinedDecisionError, "Undefined decision #{type} options=#{opts.inspect}"
44
44
  end
45
45
  m.call(opts)
46
46
  end
@@ -20,6 +20,7 @@ module PerfectSched
20
20
  module Backend
21
21
  class RDBCompatBackend
22
22
  include BackendHelper
23
+ MAX_RETRY = 10
23
24
 
24
25
  class Token < Struct.new(:row_id, :scheduled_time, :cron, :delay, :timezone)
25
26
  end
@@ -76,13 +77,13 @@ module PerfectSched
76
77
  def init_database(options)
77
78
  sql = %[
78
79
  CREATE TABLE IF NOT EXISTS `#{@table}` (
79
- id VARCHAR(256) NOT NULL,
80
+ id VARCHAR(255) NOT NULL,
80
81
  timeout INT NOT NULL,
81
82
  next_time INT NOT NULL,
82
83
  cron VARCHAR(128) NOT NULL,
83
84
  delay INT NOT NULL,
84
- data BLOB NOT NULL,
85
- timezone VARCHAR(256) NULL,
85
+ data LONGBLOB NOT NULL,
86
+ timezone VARCHAR(255) NULL,
86
87
  PRIMARY KEY (id)
87
88
  );]
88
89
  connect {
@@ -97,7 +98,7 @@ module PerfectSched
97
98
  raise NotFoundError, "schedule key=#{key} does not exist"
98
99
  end
99
100
  attributes = create_attributes(row)
100
- return ScheduleMetadata.new(@client, key, attributes)
101
+ return ScheduleWithMetadata.new(@client, key, attributes)
101
102
  }
102
103
  end
103
104
 
@@ -57,24 +57,4 @@ module PerfectSched
57
57
  @attributes[:message]
58
58
  end
59
59
  end
60
-
61
- class ScheduleMetadata
62
- include Model
63
-
64
- def initialize(client, key, attributes)
65
- super(client)
66
- @key = key
67
- end
68
-
69
- def schedule
70
- Schedule.new(@client, @key)
71
- end
72
-
73
- def inspect
74
- "#<#{self.class} @key=#{@key.inspect} @attributes=#{@attributes.inspect}>"
75
- end
76
-
77
- include ScheduleMetadataAccessors
78
- end
79
-
80
60
  end
@@ -1,3 +1,3 @@
1
1
  module PerfectSched
2
- VERSION = "0.8.10"
2
+ VERSION = "0.8.11"
3
3
  end
@@ -88,10 +88,7 @@ module PerfectSched
88
88
  begin
89
89
  return if @replaced_pid
90
90
  stop
91
- @replaced_pid = Process.fork do
92
- exec(*command)
93
- exit!(127)
94
- end
91
+ @replaced_pid = Process.spawn(*command)
95
92
  rescue
96
93
  @log.error "failed to replace: #{$!}"
97
94
  $!.backtrace.each {|bt| @log.warn "\t#{bt}" }
data/perfectsched.gemspec CHANGED
@@ -16,12 +16,14 @@ Gem::Specification.new do |gem|
16
16
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  gem.require_paths = ['lib']
18
18
 
19
- gem.add_dependency "cron-spec", [">= 0.1.2", "<= 0.1.2"]
19
+ gem.required_ruby_version = '>= 2.1'
20
+ gem.add_dependency "chrono", "~> 0.3.0"
20
21
  gem.add_dependency "sequel", "~> 3.48.0"
21
22
  gem.add_dependency "tzinfo", "~> 1.1"
22
- gem.add_dependency "perfectqueue", "~> 0.8.41"
23
+ gem.add_dependency "perfectqueue", "~>0.9", ">= 0.8.41"
23
24
  gem.add_development_dependency "rake", "~> 0.9.2"
24
- gem.add_development_dependency "rspec", "~> 2.10.0"
25
- gem.add_development_dependency "simplecov", "~> 0.5.4"
25
+ gem.add_development_dependency "rspec", "~> 3.4.0"
26
+ gem.add_development_dependency "simplecov", "~> 0.10.0"
26
27
  gem.add_development_dependency "sqlite3", "~> 1.3.3"
28
+ gem.add_development_dependency "mysql2", "~> 0.3.20"
27
29
  end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectSched::Application::Base do
4
+ describe '.decider=' do
5
+ it 'defines .decider which returns the decider' do
6
+ decider_klass = double('decider_klass')
7
+ klass = PerfectSched::Application::Base
8
+ allow(klass).to receive(:decider).and_call_original
9
+ allow(klass).to receive(:decider=).with(decider_klass).and_call_original
10
+ expect(klass.decider = decider_klass).to eq(decider_klass)
11
+ expect(klass.decider).to eq(decider_klass)
12
+ end
13
+ end
14
+
15
+ describe '.decider' do
16
+ it 'returns DefaultDecider' do
17
+ expect(PerfectSched::Application::Base.decider).to eq(PerfectSched::Application::DefaultDecider)
18
+ end
19
+ end
20
+
21
+ describe '#new' do
22
+ let (:task){ double('task') }
23
+ let (:base) { PerfectSched::Application::Base.new(task) }
24
+ it 'calls super and set decider'do
25
+ expect(base).to be_an_instance_of(PerfectSched::Application::Base)
26
+ expect(base.instance_variable_get(:@task)).to eq(task)
27
+ expect(base.instance_variable_get(:@decider)).to be_an_instance_of(Application::DefaultDecider)
28
+ end
29
+ end
30
+
31
+ describe '#run' do
32
+ let (:base) { PerfectSched::Application::Base.new(double('task')) }
33
+ it 'returns nil if before_perform returns false' do
34
+ allow(base).to receive(:before_perform).and_return(false)
35
+ expect(base.run).to be_nil
36
+ end
37
+ it 'returns nil' do
38
+ expect(base).to receive(:before_perform).exactly(:once).and_call_original
39
+ expect(base).to receive(:perform).exactly(:once).and_return(nil)
40
+ expect(base).to receive(:after_perform).exactly(:once).and_call_original
41
+ expect(base.run).to be_nil
42
+ end
43
+ it 'calls unexpected_error_raised on error' do
44
+ allow(base).to receive(:before_perform).exactly(:once).and_call_original
45
+ allow(base).to receive(:perform).exactly(:once) { raise }
46
+ allow(base).to receive(:decide!).with(:unexpected_error_raised, error: kind_of(Exception)).exactly(:once)
47
+ expect(base.run).to be_nil
48
+ end
49
+ end
50
+
51
+ describe '#before_perform' do
52
+ let (:base) { PerfectSched::Application::Base.new(double('task')) }
53
+ it 'returns true' do
54
+ expect(base.before_perform).to be true
55
+ end
56
+ end
57
+
58
+ describe '#after_perform' do
59
+ let (:base) { PerfectSched::Application::Base.new(double('task')) }
60
+ it 'returns nil' do
61
+ expect(base.after_perform).to be_nil
62
+ end
63
+ end
64
+
65
+ describe '#decide!' do
66
+ let (:base) do
67
+ decider = double('decider')
68
+ expect(decider).to receive(:decide!).with(:type, :option).exactly(:once)
69
+ decider_klass = double('decider_klass')
70
+ allow(decider_klass).to receive(:new).with(kind_of(PerfectSched::Application::Base)).and_return(decider)
71
+ klass = PerfectSched::Application::Base
72
+ allow(klass).to receive(:decider).and_call_original
73
+ allow(klass).to receive(:decider=).with(decider_klass).and_call_original
74
+ klass.decider = decider_klass
75
+ klass.new(double('task'))
76
+ end
77
+ it 'calls decider.decide' do
78
+ expect(base.decide!(:type, :option)).to be_nil
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectSched::Application::UndefinedDecisionError do
4
+ it { is_expected.to be_an_instance_of(PerfectSched::Application::UndefinedDecisionError) }
5
+ it { is_expected.to be_a(Exception) }
6
+ end
7
+
8
+ describe PerfectSched::Application::Decider do
9
+ let (:task){ double('task') }
10
+ let (:schedules){ double('schedules') }
11
+ let (:base){ double('base', schedules: schedules, task: task) }
12
+ let (:decider) { PerfectSched::Application::Decider.new(base) }
13
+ describe '#new' do
14
+ it 'returns a decider' do
15
+ expect(decider).to be_an_instance_of(PerfectSched::Application::Decider)
16
+ expect(decider.instance_variable_get(:@base)).to eq(base)
17
+ end
18
+ end
19
+
20
+ describe '#schedules' do
21
+ it 'returns @base.schedules' do
22
+ expect(decider.schedules).to eq(schedules)
23
+ end
24
+ end
25
+
26
+ describe '#task' do
27
+ let (:decider) do
28
+ base = double('base')
29
+ allow(base).to receive(:task).exactly(:once).and_return(task)
30
+ PerfectSched::Application::Decider.new(base)
31
+ end
32
+ it 'calls @base.task' do
33
+ expect(decider.task).to eq(task)
34
+ end
35
+ end
36
+
37
+ describe '#decide!' do
38
+ it 'calls the specified method' do
39
+ opts = double('opts')
40
+ ret = double('ret')
41
+ allow(decider).to receive(:foo).exactly(:once).with(opts).and_return(ret)
42
+ expect(decider.decide!(:foo, opts)).to eq(ret)
43
+ end
44
+ it 'raises UndefinedDecisionError on unknown method' do
45
+ expect{ decider.decide!(:foo, double) }.to raise_error(PerfectSched::Application::UndefinedDecisionError)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe PerfectSched::Application::DefaultDecider do
51
+ subject { PerfectSched::Application::DefaultDecider.new(nil) }
52
+ it { is_expected.to be_a(PerfectSched::Application::Decider) }
53
+ it { is_expected.to be_an_instance_of(PerfectSched::Application::DefaultDecider) }
54
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Client do
4
+ let (:config){ {} }
5
+ let (:client){ Client.new(config) }
6
+ let (:ret){ double('ret') }
7
+ let (:backend){ double('backend') }
8
+ let (:options){ double('options') }
9
+ let (:task_token){ double('task_token') }
10
+ let (:key){ double('key') }
11
+ before do
12
+ allow(Backend).to receive(:new_backend) \
13
+ .with(kind_of(Client), config).and_return(backend)
14
+ end
15
+ describe '.new' do
16
+ subject { client }
17
+ it {
18
+ is_expected.to be_an_instance_of(Client) }
19
+ end
20
+ describe '#backend' do
21
+ subject { client.backend }
22
+ it { is_expected.to eq backend }
23
+ end
24
+ describe '#config' do
25
+ subject { client.config }
26
+ it { is_expected.to eq config }
27
+ end
28
+ describe '#init_database' do
29
+ subject { client.init_database(options) }
30
+ before { expect(backend).to receive(:init_database).with(options).and_return(ret) }
31
+ it { is_expected.to eq ret }
32
+ end
33
+ describe '#get_schedule_metadata' do
34
+ subject { client.get_schedule_metadata(key, options) }
35
+ before { expect(backend).to receive(:get_schedule_metadata).with(key, options).and_return(ret) }
36
+ it { is_expected.to eq ret }
37
+ end
38
+ describe '#delete' do
39
+ subject { client.delete(key, options) }
40
+ before { expect(backend).to receive(:delete).with(key, options).and_return(ret) }
41
+ it { is_expected.to eq ret }
42
+ end
43
+ describe '#modify' do
44
+ subject { client.modify(key, options) }
45
+ before { expect(backend).to receive(:modify).with(key, options).and_return(ret) }
46
+ it { is_expected.to eq ret }
47
+ end
48
+ describe '#list' do
49
+ let (:pr){ proc{} }
50
+ subject { client.list(key, &pr) }
51
+ before { expect(backend).to receive(:list).with(key){|*_, &b| expect(b).to eq pr; ret} }
52
+ it { is_expected.to eq ret }
53
+ end
54
+ describe '#acquire' do
55
+ let (:alive_time){ double('alive_time') }
56
+ let (:max_acquire){ double('max_acquire') }
57
+ subject { client.acquire(options) }
58
+ before { expect(backend).to receive(:acquire).with(alive_time, max_acquire, options).and_return(ret) }
59
+ context 'options are given' do
60
+ let (:options){ {alive_time: alive_time, max_acquire: max_acquire} }
61
+ it { is_expected.to eq ret }
62
+ end
63
+ context 'alive_time options is not given' do
64
+ let (:max_acquire){ 1 }
65
+ let (:options){ {} }
66
+ let (:config){ {alive_time: alive_time} }
67
+ it { is_expected.to eq ret }
68
+ end
69
+ end
70
+ describe '#release' do
71
+ let (:alive_time){ double('alive_time') }
72
+ subject { client.release(task_token, options) }
73
+ before { expect(backend).to receive(:release).with(task_token, alive_time, options).and_return(ret) }
74
+ context 'alive_time options is given' do
75
+ let (:options){ {alive_time: alive_time} }
76
+ it { is_expected.to eq ret }
77
+ end
78
+ context 'alive_time options is not given' do
79
+ let (:options){ {} }
80
+ let (:config){ {alive_time: alive_time} }
81
+ it { is_expected.to eq ret }
82
+ end
83
+ end
84
+ describe '#heartbeat' do
85
+ let (:alive_time){ double('alive_time') }
86
+ subject { client.heartbeat(task_token, options) }
87
+ before { expect(backend).to receive(:heartbeat).with(task_token, alive_time, options).and_return(ret) }
88
+ context 'alive_time options is given' do
89
+ let (:options){ {alive_time: alive_time} }
90
+ it { is_expected.to eq ret }
91
+ end
92
+ context 'alive_time options is not given' do
93
+ let (:options){ {} }
94
+ let (:config){ {alive_time: alive_time} }
95
+ it { is_expected.to eq ret }
96
+ end
97
+ end
98
+ describe '#retry' do
99
+ let (:retry_wait){ double('retry_wait') }
100
+ subject { client.retry(task_token, options) }
101
+ before { expect(backend).to receive(:heartbeat).with(task_token, retry_wait, options).and_return(ret) }
102
+ context 'retry_wait options is given' do
103
+ let (:options){ {retry_wait: retry_wait} }
104
+ it { is_expected.to eq ret }
105
+ end
106
+ context 'retry_wait options is not given' do
107
+ let (:options){ {} }
108
+ let (:config){ {retry_wait: retry_wait} }
109
+ it { is_expected.to eq ret }
110
+ end
111
+ end
112
+ describe '#finish' do
113
+ subject { client.finish(task_token, options) }
114
+ before { expect(backend).to receive(:finish).with(task_token, options).and_return(ret) }
115
+ it { is_expected.to eq ret }
116
+ end
117
+ describe '#close' do
118
+ subject { client.close }
119
+ before { expect(backend).to receive(:close).with(no_args).and_return(ret) }
120
+ it { is_expected.to eq ret }
121
+ end
122
+ end