iyyov 1.0.0-java
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.
- data/History.rdoc +2 -0
- data/Manifest.txt +18 -0
- data/README.rdoc +31 -0
- data/Rakefile +53 -0
- data/bin/iyyov-fg +63 -0
- data/init/iyyov-daemon +34 -0
- data/lib/iyyov/base.rb +20 -0
- data/lib/iyyov/context.rb +205 -0
- data/lib/iyyov/daemon.rb +348 -0
- data/lib/iyyov/errors.rb +19 -0
- data/lib/iyyov/log_rotator.rb +99 -0
- data/lib/iyyov/scheduler.rb +120 -0
- data/lib/iyyov/shutdown_handler.rb +46 -0
- data/lib/iyyov/task.rb +179 -0
- data/lib/iyyov.rb +78 -0
- data/test/setup.rb +50 -0
- data/test/test_daemon.rb +89 -0
- data/test/test_scheduler.rb +75 -0
- metadata +143 -0
data/lib/iyyov/daemon.rb
ADDED
@@ -0,0 +1,348 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2010 David Kellum
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
+
# may not use this file except in compliance with the License. You
|
6
|
+
# may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
+
# implied. See the License for the specific language governing
|
14
|
+
# permissions and limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'rjack-slf4j'
|
18
|
+
require 'fileutils'
|
19
|
+
|
20
|
+
require 'iyyov/log_rotator'
|
21
|
+
|
22
|
+
module Iyyov
|
23
|
+
include RJack
|
24
|
+
|
25
|
+
# A daemon isntance to start and monitor
|
26
|
+
class Daemon
|
27
|
+
|
28
|
+
# Name of this daemon. Must be unique in combination with any
|
29
|
+
# specified instance.
|
30
|
+
#
|
31
|
+
# String (required)
|
32
|
+
attr_accessor :name
|
33
|
+
|
34
|
+
# Optional specific instance identifier, distinguishing this
|
35
|
+
# daemon from others of the same name. For example, a port number
|
36
|
+
# could be used.
|
37
|
+
#
|
38
|
+
# Proc,~to_s (default: nil)
|
39
|
+
attr_writer :instance
|
40
|
+
|
41
|
+
# Full path to executable to start.
|
42
|
+
#
|
43
|
+
# Proc,~to_s (default: compute from gem_name, init_name, and version)
|
44
|
+
attr_writer :exe_path
|
45
|
+
|
46
|
+
# Any additional args to use on start
|
47
|
+
#
|
48
|
+
# Proc,Array[~to_s] (default: [])
|
49
|
+
attr_writer :args
|
50
|
+
|
51
|
+
# Base directory under which run directories are found
|
52
|
+
#
|
53
|
+
# Proc,~to_s (default: Context.base_dir)
|
54
|
+
attr_writer :base_dir
|
55
|
+
|
56
|
+
# Directory to execute under
|
57
|
+
#
|
58
|
+
# Proc,~to_s (default: base_dir / full_name)
|
59
|
+
attr_writer :run_dir
|
60
|
+
|
61
|
+
# Whether to make run_dir, if not already present
|
62
|
+
#
|
63
|
+
# Boolean (default: Context.make_run_dir )
|
64
|
+
attr_accessor :make_run_dir
|
65
|
+
|
66
|
+
# Whether to stop this daemon when Iyyov exits
|
67
|
+
#
|
68
|
+
# Boolean (default: Context.stop_on_exit)
|
69
|
+
attr_accessor :stop_on_exit
|
70
|
+
|
71
|
+
# Duration in seconds between SIGTERM and final SIGKILL when
|
72
|
+
# stopping.
|
73
|
+
#
|
74
|
+
# Numeric (default: Context.stop_delay)
|
75
|
+
attr_accessor :stop_delay
|
76
|
+
|
77
|
+
# PID file written by the daemon process after start, containing
|
78
|
+
# the running daemon Process ID
|
79
|
+
#
|
80
|
+
# Proc,~to_s (default: run_dir, init_name + '.pid')
|
81
|
+
attr_writer :pid_file
|
82
|
+
|
83
|
+
# The gem name used, in conjunction with version for gem-based
|
84
|
+
# default exe_path
|
85
|
+
#
|
86
|
+
# Proc,~to_s (default: name)
|
87
|
+
attr_writer :gem_name
|
88
|
+
|
89
|
+
# The gem version requirements, i.e '~> 1.1.3'
|
90
|
+
#
|
91
|
+
# Proc,~to_s,Array[~to_s] (default: '>= 0')
|
92
|
+
attr_writer :version
|
93
|
+
|
94
|
+
# The init script name used for gem-based default exe_path.
|
95
|
+
#
|
96
|
+
# Proc,~to_s (default: name)
|
97
|
+
attr_writer :init_name
|
98
|
+
|
99
|
+
# Last found state of this daemon.
|
100
|
+
#
|
101
|
+
# Symbol (in STATES)
|
102
|
+
attr_reader :state
|
103
|
+
|
104
|
+
# Once do_first is called and provided a gem is found (gem version
|
105
|
+
# specified).
|
106
|
+
#
|
107
|
+
# Gem::Specification
|
108
|
+
# FIXME: May not want to expose this.
|
109
|
+
attr_reader :gem_spec
|
110
|
+
|
111
|
+
# States tracked
|
112
|
+
STATES = [ :begin, :up, :failed, :stopped ]
|
113
|
+
|
114
|
+
# Instance variables which may be set as Procs
|
115
|
+
LVARS = [ :@instance, :@exe_path, :@args, :@base_dir, :@run_dir, :@pid_file,
|
116
|
+
:@gem_name, :@version, :@init_name ]
|
117
|
+
|
118
|
+
# New daemon given specified or default global
|
119
|
+
# Iyyov.context. Yields self to block for configuration.
|
120
|
+
def initialize( context = Iyyov.context )
|
121
|
+
|
122
|
+
@context = context
|
123
|
+
@name = nil
|
124
|
+
|
125
|
+
@instance = nil
|
126
|
+
@exe_path = method :gem_exe_path
|
127
|
+
@args = []
|
128
|
+
@base_dir = method :default_base_dir
|
129
|
+
@run_dir = method :default_run_dir
|
130
|
+
@make_run_dir = @context.make_run_dir
|
131
|
+
@stop_on_exit = @context.stop_on_exit
|
132
|
+
@stop_delay = @context.stop_delay
|
133
|
+
|
134
|
+
@pid_file = method :default_pid_file
|
135
|
+
@gem_name = method :name
|
136
|
+
@version = '>= 0'
|
137
|
+
@init_name = method :name
|
138
|
+
|
139
|
+
@state = :begin
|
140
|
+
@gem_spec = nil
|
141
|
+
@rotators = {}
|
142
|
+
|
143
|
+
yield self if block_given?
|
144
|
+
|
145
|
+
raise "name not specified" unless name
|
146
|
+
|
147
|
+
@log = SLF4J[ [ SLF4J[ self.class ].name,
|
148
|
+
name, instance ].compact.join( '.' ) ]
|
149
|
+
end
|
150
|
+
|
151
|
+
# Given name + ( '-' + instance ) if provided.
|
152
|
+
def full_name
|
153
|
+
[ name, instance ].compact.join('-')
|
154
|
+
end
|
155
|
+
|
156
|
+
# Create a new LogRotator and yields it to block for
|
157
|
+
# configuration.
|
158
|
+
# The default log path is name + ".log" in run_dir
|
159
|
+
def log_rotate( &block )
|
160
|
+
lr = LogRotator.new( default_log, &block )
|
161
|
+
@rotators[ lr.log ] = lr
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
|
165
|
+
# Post initialization validation, attempt immediate start if
|
166
|
+
# needed, and add appropriate tasks to scheduler.
|
167
|
+
def do_first( scheduler )
|
168
|
+
unless File.directory?( run_dir )
|
169
|
+
if make_run_dir
|
170
|
+
@log.info { "Creating run_dir [#{run_dir}]." }
|
171
|
+
FileUtils.mkdir_p( run_dir, :mode => 0755 )
|
172
|
+
else
|
173
|
+
raise( DaemonFailed, "run_dir [#{run_dir}] not found" )
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
res = start_check
|
178
|
+
unless res == :stop
|
179
|
+
tasks.each { |t| scheduler.add( t ) }
|
180
|
+
end
|
181
|
+
res
|
182
|
+
rescue DaemonFailed, SystemCallError => e
|
183
|
+
#FIXME: Ruby 1.4.0 throws SystemCallError when mkdir fails from
|
184
|
+
#permissions
|
185
|
+
@log.error( e.to_s )
|
186
|
+
@state = :failed
|
187
|
+
:stop
|
188
|
+
end
|
189
|
+
|
190
|
+
def tasks
|
191
|
+
t = [ Task.new( :name => full_name, :period => 5.0 ) { start_check } ]
|
192
|
+
t += @rotators.values.map do |lr|
|
193
|
+
Task.new( :name => "#{full_name}.rotate",
|
194
|
+
:mode => :async,
|
195
|
+
:period => lr.check_period ) do
|
196
|
+
lr.check_rotate( pid ) do |rlog|
|
197
|
+
@log.info { "Rotating log #{rlog}" }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
t
|
202
|
+
end
|
203
|
+
|
204
|
+
def do_exit
|
205
|
+
stop if stop_on_exit
|
206
|
+
end
|
207
|
+
|
208
|
+
def default_base_dir
|
209
|
+
@context.base_dir
|
210
|
+
end
|
211
|
+
|
212
|
+
def default_run_dir
|
213
|
+
File.join( base_dir, full_name )
|
214
|
+
end
|
215
|
+
|
216
|
+
def default_pid_file
|
217
|
+
in_dir( init_name + '.pid' )
|
218
|
+
end
|
219
|
+
|
220
|
+
def default_log
|
221
|
+
in_dir( init_name + '.log' )
|
222
|
+
end
|
223
|
+
|
224
|
+
# Return full path to file_name within run_dir
|
225
|
+
def in_dir( file_name )
|
226
|
+
File.join( run_dir, file_name )
|
227
|
+
end
|
228
|
+
|
229
|
+
def gem_exe_path
|
230
|
+
File.join( find_gem_spec.full_gem_path, 'init', init_name )
|
231
|
+
end
|
232
|
+
|
233
|
+
def find_gem_spec
|
234
|
+
#FIXME: Use Gem.clear_paths to rescan.
|
235
|
+
@gem_spec ||= Gem.source_index.find_name( gem_name, version ).last
|
236
|
+
unless @gem_spec
|
237
|
+
raise( Gem::GemNotFoundException, "Missing gem #{gem_name} (#{version})" )
|
238
|
+
end
|
239
|
+
@gem_spec
|
240
|
+
end
|
241
|
+
|
242
|
+
def start
|
243
|
+
epath = File.expand_path( exe_path )
|
244
|
+
eargs = args.to_a.map { |a| a.to_s.strip }.compact
|
245
|
+
aversion = @gem_spec && @gem_spec.version
|
246
|
+
@log.info { ( [ "starting", aversion || epath ] + eargs ).join(' ') }
|
247
|
+
|
248
|
+
unless File.executable?( epath )
|
249
|
+
raise( DaemonFailed, "Exe path: #{epath} not found/executable." )
|
250
|
+
end
|
251
|
+
|
252
|
+
Dir.chdir( run_dir ) do
|
253
|
+
system( epath, *eargs ) or raise( DaemonFailed, "Start failed with #{$?}" )
|
254
|
+
end
|
255
|
+
|
256
|
+
@state = :up
|
257
|
+
true
|
258
|
+
rescue Gem::GemNotFoundException, DaemonFailed, Errno::ENOENT => e
|
259
|
+
@log.error( e.to_s )
|
260
|
+
@state = :failed
|
261
|
+
false
|
262
|
+
end
|
263
|
+
|
264
|
+
# Return array suitable for comparing this daemon with prior
|
265
|
+
# running instance.
|
266
|
+
def exec_key
|
267
|
+
keys = [ run_dir, exe_path ].map { |p| File.expand_path( p ) }
|
268
|
+
keys += args.to_a.map { |a| a.to_s.strip }.compact
|
269
|
+
keys.compact
|
270
|
+
end
|
271
|
+
|
272
|
+
def start_check
|
273
|
+
p = pid
|
274
|
+
if alive?( p )
|
275
|
+
@log.debug { "checked: alive pid: #{p}" }
|
276
|
+
@state = :up
|
277
|
+
else
|
278
|
+
unless start
|
279
|
+
@log.info "start failed, done trying"
|
280
|
+
:stop
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# True if process is up
|
286
|
+
def alive?( p = pid )
|
287
|
+
( Process.getpgid( p ) != -1 ) if p
|
288
|
+
rescue Errno::ESRCH
|
289
|
+
false
|
290
|
+
end
|
291
|
+
|
292
|
+
# Stop via SIGTERM, waiting for shutdown for up to stop_delay, then
|
293
|
+
# SIGKILL as last resort. Return true if a process was stopped.
|
294
|
+
def stop
|
295
|
+
p = pid
|
296
|
+
if p
|
297
|
+
@log.info "Sending TERM signal"
|
298
|
+
Process.kill( "TERM", p )
|
299
|
+
unless wait_pid( p )
|
300
|
+
@log.info "Sending KILL signal"
|
301
|
+
Process.kill( "KILL", p )
|
302
|
+
end
|
303
|
+
@status = :stopped
|
304
|
+
true
|
305
|
+
end
|
306
|
+
false
|
307
|
+
rescue Errno::ESRCH
|
308
|
+
# No such process: only raised by MRI ruby currently
|
309
|
+
false
|
310
|
+
rescue Errno::EPERM => e
|
311
|
+
# Not permitted: only raised by MRI ruby currently
|
312
|
+
@log.error( e )
|
313
|
+
false
|
314
|
+
end
|
315
|
+
|
316
|
+
# Wait for process to go away
|
317
|
+
def wait_pid( p = pid )
|
318
|
+
delta = 1.0 / 16
|
319
|
+
delay = 0.0
|
320
|
+
check = false
|
321
|
+
while delay < stop_delay do
|
322
|
+
break if ( check = ! alive?( p ) )
|
323
|
+
sleep delta
|
324
|
+
delay += delta
|
325
|
+
delta += ( 1.0 / 16 ) if delta < 0.50
|
326
|
+
end
|
327
|
+
check
|
328
|
+
end
|
329
|
+
|
330
|
+
# Return process ID from pid_file if exists or nil otherwise
|
331
|
+
def pid
|
332
|
+
id = IO.read( pid_file ).strip.to_i
|
333
|
+
( id > 0 ) ? id : nil
|
334
|
+
rescue Errno::ENOENT # Pid file doesn't exist
|
335
|
+
nil
|
336
|
+
end
|
337
|
+
|
338
|
+
LVARS.each do |sym|
|
339
|
+
define_method( sym.to_s[1..-1] ) do
|
340
|
+
exp = instance_variable_get( sym )
|
341
|
+
exp.respond_to?( :call ) ? exp.call : exp
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
class DaemonFailed < StandardError; end
|
348
|
+
end
|
data/lib/iyyov/errors.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2010 David Kellum
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
+
# may not use this file except in compliance with the License. You
|
6
|
+
# may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
+
# implied. See the License for the specific language governing
|
14
|
+
# permissions and limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
module Iyyov
|
18
|
+
class SetupError < StandardError; end
|
19
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2010 David Kellum
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
+
# may not use this file except in compliance with the License. You
|
6
|
+
# may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
+
# implied. See the License for the specific language governing
|
14
|
+
# permissions and limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'logrotate'
|
18
|
+
|
19
|
+
module Iyyov
|
20
|
+
include RJack
|
21
|
+
|
22
|
+
# Support check for size and rotation of a log file.
|
23
|
+
class LogRotator
|
24
|
+
|
25
|
+
# Full path to log file to check and rotate
|
26
|
+
#
|
27
|
+
# String (required)
|
28
|
+
attr_accessor :log
|
29
|
+
|
30
|
+
# Maximum log size triggering rotation
|
31
|
+
#
|
32
|
+
# Fixnum bytes (default: 256M bytes)
|
33
|
+
attr_accessor :max_size
|
34
|
+
|
35
|
+
# Number of rotated logs in addition to active log.
|
36
|
+
#
|
37
|
+
# Fixnum (default: 3)
|
38
|
+
attr_accessor :count
|
39
|
+
|
40
|
+
# GZIP compress rotated logs?
|
41
|
+
#
|
42
|
+
# Boolean (default: true)
|
43
|
+
attr_accessor :gzip
|
44
|
+
|
45
|
+
# The signal to use post rotation (but before gzip) requesting
|
46
|
+
# that the daemon reopen its logs.
|
47
|
+
#
|
48
|
+
# String (default: "HUP")
|
49
|
+
attr_accessor :signal
|
50
|
+
|
51
|
+
# Period between subsequent checks for rotation (default: 300.0)
|
52
|
+
#
|
53
|
+
# Float seconds
|
54
|
+
attr_accessor :check_period
|
55
|
+
|
56
|
+
# Process ID to signal (if known in advance/constant, i.e. 0 for
|
57
|
+
# this process)
|
58
|
+
#
|
59
|
+
# Fixnum
|
60
|
+
attr_writer :pid
|
61
|
+
|
62
|
+
# Set max_size in megabytes
|
63
|
+
#
|
64
|
+
# mb<Fixnum>:: megabytes
|
65
|
+
def max_size_mb=( mb )
|
66
|
+
@max_size = mb * 1024 * 1024
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize( log = nil )
|
70
|
+
@log = log
|
71
|
+
max_size_mb = 256
|
72
|
+
@count = 3
|
73
|
+
@gzip = true
|
74
|
+
@signal = "HUP"
|
75
|
+
@check_period = 5 * 60.0
|
76
|
+
@pid = nil
|
77
|
+
|
78
|
+
yield self if block_given?
|
79
|
+
|
80
|
+
#FIXME: Validate log directory?
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check if log is over size and rotate if needed. Yield log name
|
85
|
+
# to block just before rotating
|
86
|
+
def check_rotate( pid = @pid )
|
87
|
+
if File.exist?( log ) && File.size( log ) > max_size
|
88
|
+
yield log if block_given?
|
89
|
+
opts = { :count => count, :gzip => gzip }
|
90
|
+
if signal && pid
|
91
|
+
opts[ :post_rotate ] = lambda { Process.kill( signal, pid ) }
|
92
|
+
end
|
93
|
+
LogRotate.rotate_file( log, opts )
|
94
|
+
end
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2010 David Kellum
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
+
# may not use this file except in compliance with the License. You
|
6
|
+
# may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
+
# implied. See the License for the specific language governing
|
14
|
+
# permissions and limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'java'
|
18
|
+
|
19
|
+
require 'thread'
|
20
|
+
require 'iyyov/shutdown_handler'
|
21
|
+
|
22
|
+
module Iyyov
|
23
|
+
|
24
|
+
# Maintains a queue of Task to be executed at fixed or periodic
|
25
|
+
# times.
|
26
|
+
class Scheduler
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
# min heap
|
30
|
+
@queue = Java::java.util.PriorityQueue.new( 67, TimeComp.new )
|
31
|
+
@lock = Mutex.new
|
32
|
+
@shutdown_handler = nil
|
33
|
+
@log = SLF4J[ self.class ]
|
34
|
+
end
|
35
|
+
|
36
|
+
def add( t, now = Time.now )
|
37
|
+
@queue.add( t ) if t.schedule( now )
|
38
|
+
end
|
39
|
+
|
40
|
+
def peek
|
41
|
+
@queue.peek
|
42
|
+
end
|
43
|
+
|
44
|
+
def poll
|
45
|
+
@queue.poll
|
46
|
+
end
|
47
|
+
|
48
|
+
# Execute the specified block on exit (in a shutdown thread.) The
|
49
|
+
# Scheduler queue is drained such that the on_exit block is
|
50
|
+
# guaranteed to be the last to run.
|
51
|
+
def on_exit( &block )
|
52
|
+
off_exit
|
53
|
+
@shutdown_handler = ShutdownHandler.new do
|
54
|
+
|
55
|
+
# Need to lock out the event loop since exit handler is called
|
56
|
+
# from a different thread.
|
57
|
+
@lock.synchronize do
|
58
|
+
@queue.clear
|
59
|
+
block.call
|
60
|
+
end
|
61
|
+
end
|
62
|
+
@log.debug { "Registered exit: #{ @shutdown_handler.handler }" }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Deregister any previously added on_exit block
|
66
|
+
def off_exit
|
67
|
+
if @shutdown_handler
|
68
|
+
@log.debug { "Unregistered exit: #{ @shutdown_handler.handler }" }
|
69
|
+
@shutdown_handler.unregister
|
70
|
+
@shutdown_handler = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Loop forever executing tasks or waiting for the next to be
|
75
|
+
# ready. Return only when the queue is empty (which may be arranged by
|
76
|
+
# on_exit) or if a Task returns :shutdown.
|
77
|
+
def event_loop
|
78
|
+
rc = nil
|
79
|
+
|
80
|
+
# While not shutdown
|
81
|
+
while ( rc != :shutdown )
|
82
|
+
now = Time.now
|
83
|
+
delta = 0.0
|
84
|
+
|
85
|
+
@lock.synchronize do
|
86
|
+
# While we don't need to wait, and a task is available
|
87
|
+
while ( delta <= 0.0 && ( task = peek ) && rc != :shutdown )
|
88
|
+
delta = task.next_time - now
|
89
|
+
|
90
|
+
if delta <= 0.0
|
91
|
+
task = poll
|
92
|
+
|
93
|
+
rc = task.run
|
94
|
+
add( task, now ) unless ( rc == :shutdown || rc == :stop )
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end #lock
|
98
|
+
|
99
|
+
break unless delta > 0.0
|
100
|
+
sleep delta
|
101
|
+
end
|
102
|
+
|
103
|
+
if rc == :shutdown
|
104
|
+
@log.debug "Begin scheduler shutdown sequence."
|
105
|
+
@queue.clear
|
106
|
+
off_exit
|
107
|
+
end
|
108
|
+
rc
|
109
|
+
end
|
110
|
+
|
111
|
+
# Implements java.util.Comparator over task.next_time values
|
112
|
+
class TimeComp
|
113
|
+
include Java::java.util.Comparator
|
114
|
+
def compare( p, n )
|
115
|
+
p.next_time <=> n.next_time
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2010 David Kellum
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
+
# may not use this file except in compliance with the License. You
|
6
|
+
# may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
+
# implied. See the License for the specific language governing
|
14
|
+
# permissions and limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'java'
|
18
|
+
|
19
|
+
module Iyyov
|
20
|
+
|
21
|
+
class ShutdownHandler
|
22
|
+
Thread = Java::java.lang.Thread
|
23
|
+
Runtime = Java::java.lang.Runtime
|
24
|
+
|
25
|
+
include Java::java.lang.Runnable
|
26
|
+
|
27
|
+
attr_reader :handler
|
28
|
+
|
29
|
+
def initialize( &block )
|
30
|
+
@block = block
|
31
|
+
@handler = Thread.new( self )
|
32
|
+
Runtime::runtime.add_shutdown_hook( @handler )
|
33
|
+
end
|
34
|
+
|
35
|
+
def unregister
|
36
|
+
Runtime::runtime.remove_shutdown_hook( @handler )
|
37
|
+
@handler = nil
|
38
|
+
@block = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
@block.call
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|