time_scheduler 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,4 @@
1
+ require "time_scheduler/version"
2
+ require "time_scheduler/event"
3
+ require "time_scheduler/scheduler"
4
+
@@ -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
+
@@ -0,0 +1,3 @@
1
+ class TimeScheduler
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,11 @@
1
+
2
+ require "time_scheduler"
3
+
4
+ Scheduler = TimeScheduler.new
5
+
6
+ goal = Time.now + 3
7
+ while time = Scheduler.wait( sec: "*" )
8
+ p Time.now.iso8601(3)
9
+ break if time > goal
10
+ end
11
+