time_scheduler 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/README.adoc +200 -0
- data/README.ja.adoc +199 -0
- data/Rakefile +96 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/time_scheduler.rb +4 -0
- data/lib/time_scheduler/event.rb +84 -0
- data/lib/time_scheduler/scheduler.rb +226 -0
- data/lib/time_scheduler/version.rb +3 -0
- data/sample/base_1.rb +11 -0
- data/sample/base_2.rb +12 -0
- data/sample/base_3.rb +10 -0
- data/sample/base_4.rb +14 -0
- data/sample/base_5.rb +25 -0
- data/sample/base_6.rb +24 -0
- data/sample/base_7.rb +20 -0
- data/sample/base_8.rb +27 -0
- data/sample/base_9.rb +29 -0
- data/sample/base_a.rb +31 -0
- data/sample/base_b.rb +17 -0
- data/sample/base_c.rb +24 -0
- data/sample/base_d.rb +21 -0
- data/sample/base_e.rb +25 -0
- data/sample/base_f.rb +21 -0
- data/sample/base_g.rb +36 -0
- data/time_scheduler.gemspec +28 -0
- metadata +114 -0
data/Rakefile
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "bundler/gem_helper"
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
class Bundler::GemHelper
|
10
|
+
|
11
|
+
def git_archive( dir = "../#{Time.now.strftime("%Y%m%d")}" )
|
12
|
+
FileUtils.mkdir_p dir
|
13
|
+
dest_path = File.join(dir, "#{name}-#{version}.zip")
|
14
|
+
cmnd = "git archive --format zip --prefix=#{name}/ HEAD > #{dest_path}"
|
15
|
+
|
16
|
+
out, code = sh_with_status( cmnd )
|
17
|
+
raise "Couldn't archive gem," unless code == 0
|
18
|
+
|
19
|
+
Bundler.ui.confirm "#{name} #{version} archived to #{dest_path}."
|
20
|
+
end
|
21
|
+
|
22
|
+
def git_push
|
23
|
+
ver = version.to_s
|
24
|
+
|
25
|
+
cmnd = "git push origin #{ver} "
|
26
|
+
out, code = sh_with_status( cmnd )
|
27
|
+
raise "Couldn't git push origin." unless code == 0
|
28
|
+
|
29
|
+
cmnd = "git push "
|
30
|
+
out, code = sh_with_status( cmnd )
|
31
|
+
raise "Couldn't git push." unless code == 0
|
32
|
+
|
33
|
+
Bundler.ui.confirm "Git Push #{ver}."
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_version( new_version )
|
37
|
+
version_filename = %x[ find . -type f -name "version.rb" | grep -v vendor | head -1 ].chomp
|
38
|
+
version_pathname = File.expand_path( version_filename )
|
39
|
+
lines = File.open( version_pathname ).read
|
40
|
+
lines = lines.gsub( /VERSION\s*=\s*\"\d+\.\d+\.\d+\"/, "VERSION = \"#{new_version}\"" )
|
41
|
+
File.open( version_pathname, "w" ) do |file|
|
42
|
+
file.write( lines )
|
43
|
+
end
|
44
|
+
|
45
|
+
cmnd = "git add #{version_pathname} "
|
46
|
+
out, code = sh_with_status( cmnd )
|
47
|
+
raise "Couldn't git add," unless code == 0
|
48
|
+
|
49
|
+
cmnd = "git commit -m '#{new_version}' "
|
50
|
+
out, code = sh_with_status( cmnd )
|
51
|
+
raise "Couldn't git commit." unless code == 0
|
52
|
+
|
53
|
+
cmnd = "git tag #{new_version} "
|
54
|
+
out, code = sh_with_status( cmnd )
|
55
|
+
raise "Couldn't git tag." unless code == 0
|
56
|
+
|
57
|
+
Bundler.ui.confirm "Update Tags to #{new_version}."
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
Bundler::GemHelper.new(Dir.pwd).instance_eval do
|
63
|
+
|
64
|
+
desc "Archive #{name}-#{version}.zip from repository"
|
65
|
+
task 'zip' do
|
66
|
+
git_archive
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "Git Push"
|
70
|
+
task 'push' do
|
71
|
+
git_push
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Update Version Tiny"
|
75
|
+
task 'tiny' do
|
76
|
+
major, minor, tiny = version.to_s.split('.')
|
77
|
+
new_version = [major, minor, tiny.to_i + 1].join('.')
|
78
|
+
update_version( new_version )
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "Update Version Minor"
|
82
|
+
task 'minor' do
|
83
|
+
major, minor, tiny = version.to_s.split('.')
|
84
|
+
new_version = [major, minor.to_i + 1, 0].join('.')
|
85
|
+
update_version( new_version )
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Update Version Major"
|
89
|
+
task 'major' do
|
90
|
+
major, minor, tiny = version.to_s.split('.')
|
91
|
+
new_version = [major.to_i + 1, 0, 0].join('.')
|
92
|
+
update_version( new_version )
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "time_scheduler"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
class TimeScheduler
|
4
|
+
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class EventItem
|
8
|
+
include ::Comparable
|
9
|
+
|
10
|
+
attr_reader :time, :queue, :ident
|
11
|
+
|
12
|
+
def initialize( time, queue, ident )
|
13
|
+
@time = time
|
14
|
+
@queue = queue
|
15
|
+
@ident = ident
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=>( other )
|
19
|
+
return nil unless other.is_a?( EventItem )
|
20
|
+
self.time <=> other.time
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class EventQueue
|
25
|
+
|
26
|
+
REFRESH_TIME = 3600.0
|
27
|
+
|
28
|
+
def initialize( **option )
|
29
|
+
@event_mutex = ::Mutex.new
|
30
|
+
@event_queue = ::Queue.new
|
31
|
+
@event_set = ::SortedSet.new
|
32
|
+
run
|
33
|
+
end
|
34
|
+
|
35
|
+
def reserve( time, queue, ident )
|
36
|
+
@event_mutex.synchronize do
|
37
|
+
@event_set.add( EventItem.new( time, queue, ident ) )
|
38
|
+
wait_time = [time - Time.now, REFRESH_TIME + rand * 2 - 1].min
|
39
|
+
if wait_time <= 0
|
40
|
+
@event_queue.push( 1 )
|
41
|
+
else
|
42
|
+
Thread.start do
|
43
|
+
::Kernel.sleep( wait_time )
|
44
|
+
@event_queue.push( 1 )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def refresh( time )
|
51
|
+
wait_time = [time - Time.now, REFRESH_TIME + rand * 2 - 1].min
|
52
|
+
Thread.start do
|
53
|
+
::Kernel.sleep( wait_time )
|
54
|
+
@event_queue.push( 1 )
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def run
|
59
|
+
Thread.start do
|
60
|
+
begin
|
61
|
+
while true
|
62
|
+
@event_queue.pop
|
63
|
+
@event_mutex.synchronize do
|
64
|
+
event_item = @event_set.first
|
65
|
+
|
66
|
+
now = Time.now
|
67
|
+
if event_item.time <= now
|
68
|
+
@event_set.delete( event_item )
|
69
|
+
event_item.queue.push( [now, event_item.ident] )
|
70
|
+
else
|
71
|
+
refresh( event_item.time )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue => e
|
76
|
+
raise ::TimeScheduler::Error, "#{__FILE__}: #{e.message}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require "time_cursor"
|
2
|
+
|
3
|
+
class TimeScheduler
|
4
|
+
|
5
|
+
class Schedule
|
6
|
+
NICE_TIME = 0.001
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def active?
|
10
|
+
! @@pause
|
11
|
+
end
|
12
|
+
|
13
|
+
def suspend
|
14
|
+
@@pause = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def resume
|
18
|
+
@@pause = false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@schedule_thread = nil
|
24
|
+
@schedule_mutex = ::Mutex.new
|
25
|
+
@signal_queue = ::Queue.new
|
26
|
+
|
27
|
+
@@pause = false
|
28
|
+
@@EventQueue ||= TimeScheduler::EventQueue.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def cancel
|
32
|
+
@signal_queue.push( nil )
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup( **option, &block )
|
36
|
+
@option = option
|
37
|
+
@action = option[:action] || block || @action
|
38
|
+
|
39
|
+
opt = {}
|
40
|
+
@option.each do |k, v|
|
41
|
+
if [:at, :cron, :year, :month, :day, :wday, :hour, :min, :sec, :msec].include?(k)
|
42
|
+
opt[k] = v
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@time_cursor = TimeCursor.new( **opt )
|
46
|
+
@nice_time = option[:nice].to_i * NICE_TIME
|
47
|
+
time = Time.now
|
48
|
+
@ident = time.to_i * 1000000 + time.usec
|
49
|
+
end
|
50
|
+
|
51
|
+
def wait_each( **option, &block )
|
52
|
+
setup( **option, &block )
|
53
|
+
|
54
|
+
@schedule_thread = ::Thread.start do
|
55
|
+
while true
|
56
|
+
target_time = @time_cursor.next
|
57
|
+
if target_time.nil?
|
58
|
+
@signal_queue.clear
|
59
|
+
break
|
60
|
+
end
|
61
|
+
|
62
|
+
@@EventQueue.reserve( target_time + @nice_time, @signal_queue, @ident )
|
63
|
+
|
64
|
+
while true
|
65
|
+
time, ident = * @signal_queue.pop
|
66
|
+
break if time.nil? || ident == @ident
|
67
|
+
end
|
68
|
+
|
69
|
+
if time.nil?
|
70
|
+
@signal_queue.clear
|
71
|
+
break
|
72
|
+
end
|
73
|
+
|
74
|
+
next if @@pause
|
75
|
+
|
76
|
+
::Thread.start( time ) do
|
77
|
+
@schedule_mutex.synchronize do
|
78
|
+
@action.call( time )
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def wait_once( **option )
|
88
|
+
setup( **option )
|
89
|
+
|
90
|
+
@schedule_thread = ::Thread.start do
|
91
|
+
target_time = @time_cursor.next
|
92
|
+
if target_time.nil?
|
93
|
+
@signal_queue.clear
|
94
|
+
break
|
95
|
+
end
|
96
|
+
|
97
|
+
@@EventQueue.reserve( target_time + @nice_time, @signal_queue, @ident )
|
98
|
+
|
99
|
+
while true
|
100
|
+
time, ident = * @signal_queue.pop
|
101
|
+
break if time.nil? || ident == @ident
|
102
|
+
end
|
103
|
+
|
104
|
+
if time.nil?
|
105
|
+
@signal_queue.clear
|
106
|
+
end
|
107
|
+
time
|
108
|
+
end
|
109
|
+
|
110
|
+
@schedule_thread.join
|
111
|
+
time = @schedule_thread.value
|
112
|
+
time
|
113
|
+
end
|
114
|
+
|
115
|
+
def wait_reset( **option, &block )
|
116
|
+
if @action
|
117
|
+
setup( **option, &block )
|
118
|
+
else
|
119
|
+
option[:action] = nil
|
120
|
+
setup( **option )
|
121
|
+
end
|
122
|
+
|
123
|
+
time_next = @time_cursor.next
|
124
|
+
@@EventQueue.reserve( time_next + @nice_time, @signal_queue, @ident ) if time_next
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Scheduler
|
129
|
+
def schedules
|
130
|
+
@schedules ||= {}
|
131
|
+
end
|
132
|
+
|
133
|
+
def reserved?( topic )
|
134
|
+
schedules.has_key?( topic )
|
135
|
+
end
|
136
|
+
|
137
|
+
def wait_reset( topic, **option, &block )
|
138
|
+
if schedule = schedules[topic]
|
139
|
+
schedule.wait_reset( **option, &block )
|
140
|
+
topic
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def wait_each( topic, **option, &block )
|
145
|
+
schedule = TimeScheduler::Schedule.new
|
146
|
+
schedules[topic] = schedule
|
147
|
+
schedule.wait_each( **option, &block )
|
148
|
+
topic
|
149
|
+
end
|
150
|
+
|
151
|
+
def wait_once( topic, **option )
|
152
|
+
schedule = TimeScheduler::Schedule.new
|
153
|
+
schedules[topic] = schedule
|
154
|
+
schedule.wait_once( **option )
|
155
|
+
ensure
|
156
|
+
schedules.delete( topic )
|
157
|
+
end
|
158
|
+
|
159
|
+
def topics
|
160
|
+
schedules.keys.dup
|
161
|
+
end
|
162
|
+
|
163
|
+
def cancel( topic )
|
164
|
+
if schedule = schedules[topic]
|
165
|
+
schedule.cancel
|
166
|
+
end
|
167
|
+
rescue => e
|
168
|
+
puts e.backtrace
|
169
|
+
ensure
|
170
|
+
schedules.delete( topic )
|
171
|
+
end
|
172
|
+
|
173
|
+
def join
|
174
|
+
while schedules.size > 0
|
175
|
+
topic = schedules.keys.first
|
176
|
+
schedule = schedules[topic]
|
177
|
+
schedule.join
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def initialize
|
183
|
+
end
|
184
|
+
|
185
|
+
def scheduler
|
186
|
+
@@Scheduler ||= TimeScheduler::Scheduler.new
|
187
|
+
end
|
188
|
+
|
189
|
+
def wait( topic = Time.now.iso8601(6), **option, &block )
|
190
|
+
raise TimeScheduler::Error, "option missing." if option.empty?
|
191
|
+
|
192
|
+
if scheduler.reserved?( topic )
|
193
|
+
scheduler.wait_reset( topic, **option, &block )
|
194
|
+
else
|
195
|
+
if option[:action].nil? && block.nil?
|
196
|
+
scheduler.wait_once( topic, **option )
|
197
|
+
else
|
198
|
+
scheduler.wait_each( topic, **option, &block )
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def topics
|
204
|
+
scheduler.topics
|
205
|
+
end
|
206
|
+
|
207
|
+
def cancel( *topics )
|
208
|
+
topics.each do |topic|
|
209
|
+
scheduler.cancel( topic )
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def active?
|
214
|
+
TimeScheduler::Schedule.active?
|
215
|
+
end
|
216
|
+
|
217
|
+
def suspend
|
218
|
+
TimeScheduler::Schedule.suspend
|
219
|
+
end
|
220
|
+
|
221
|
+
def resume
|
222
|
+
TimeScheduler::Schedule.resume
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|