time_scheduler 1.0.0

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.
@@ -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
+