perfectsched 0.8.10 → 0.8.11

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: 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