run_in_background 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/run_in_background/version.rb +5 -0
- data/lib/run_in_background.rb +433 -0
- data/test/run_in_background/run_in_background_test.rb +124 -0
- data/test/run_in_background/test_app +58 -0
- metadata +132 -0
@@ -0,0 +1,433 @@
|
|
1
|
+
# Author: Genady Petelko (nukegluk@gmail.com)
|
2
|
+
# Description: The file contains implementation of 'RunInBackground' module.
|
3
|
+
|
4
|
+
require 'params'
|
5
|
+
require 'log'
|
6
|
+
|
7
|
+
module BBFS
|
8
|
+
# This library provides a basic cross-platform functionality to run arbitrary ruby scripts
|
9
|
+
# in background and control them. <br>Supported platforms: Windows, Linux, Mac
|
10
|
+
# NOTE UAC (User Account Control) should be disabled to use library on Windows 7:
|
11
|
+
# * Click on the Windows Icon
|
12
|
+
# * Click on the Control Panel
|
13
|
+
# * Type in UAC in the search box (up-right corner of your window)
|
14
|
+
# * Click on "Change User Account Control settings"
|
15
|
+
# * Drag the slider down to either "Notify me when programs try to make changes to my computer"
|
16
|
+
# or to disable it completely
|
17
|
+
# * Reboot your computer when you're ready
|
18
|
+
# == General Limitations:
|
19
|
+
# * Only ruby scripts can be run in background.
|
20
|
+
# * No multiple instances with the same name.
|
21
|
+
# == Notes:
|
22
|
+
# * Linux/Mac specific methods have _linux suffix
|
23
|
+
# * Windows specific methods have _windows suffix
|
24
|
+
# * While enhancing windows code, take care that paths are in windows format,
|
25
|
+
# <br>e.i. with "\\" file separator while ruby by default uses a "/"
|
26
|
+
# * Additional functionality such as restart, reload, etc. will be added on demand
|
27
|
+
# * Remains support to provide platform specific options for start.
|
28
|
+
# <br>For more information regarding such options
|
29
|
+
# see documentation for win32-sevice (Windows), daemons (Linux/Mac)
|
30
|
+
# == Linux Notes:
|
31
|
+
# * <tt>pid_dir</tt> parameter contains absolute path to directory where pid files will be stored.
|
32
|
+
# <br>If directory doesn't exists then it will be created.
|
33
|
+
# <br>User should have a read/write permissions to this location.
|
34
|
+
# <br>Default location: <tt>$HOME/.bbfs/pids</tt>
|
35
|
+
# * User should check that default pid directory is free from pid files of "killed" daemons.
|
36
|
+
# <br>It may happen, for example, when system finished in abnormal way then pid files were
|
37
|
+
# not deleted by daemons library.
|
38
|
+
# <br>In such case incorrect results can be received, for example for <tt>exists?</tt> method
|
39
|
+
# <br>One of the suggested methods can be before starting a daemon to check with <tt>exists?</tt>
|
40
|
+
# method whether daemon already exists and with <tt>running?</tt> method does it running.
|
41
|
+
module RunInBackground
|
42
|
+
Params.string('bg_command', nil, 'Server\'s command. Commands are: start, delete and nil for' \
|
43
|
+
' not running in background.')
|
44
|
+
Params.string('service_name', File.basename($0), 'Background service name.')
|
45
|
+
|
46
|
+
# Maximal time in seconds to wait until OS will finish a requested operation,
|
47
|
+
# e.g. daemon start/delete.
|
48
|
+
TIMEOUT = 20
|
49
|
+
|
50
|
+
if RUBY_PLATFORM =~ /linux/ or RUBY_PLATFORM =~ /darwin/
|
51
|
+
begin
|
52
|
+
require 'daemons'
|
53
|
+
require 'fileutils'
|
54
|
+
rescue LoadError
|
55
|
+
require 'rubygems'
|
56
|
+
require 'daemons'
|
57
|
+
require 'fileutils'
|
58
|
+
end
|
59
|
+
|
60
|
+
OS = :LINUX
|
61
|
+
Params.string('pid_dir', File.expand_path(File.join(Dir.home, '.bbfs', 'pids')),
|
62
|
+
'Absolute path to directory, where pid files will be stored. ' + \
|
63
|
+
'User should have a read/write permissions to this location. ' + \
|
64
|
+
'If absent then it will be created. ' + \
|
65
|
+
'It\'s actual only for Linux/Mac. ' + \
|
66
|
+
'For more information see documentation on daemons module. ' +\
|
67
|
+
'Default location is: $HOME/.bbfs/pids')
|
68
|
+
|
69
|
+
if Dir.exists? Params['pid_dir']
|
70
|
+
unless File.directory? Params['pid_dir']
|
71
|
+
raise IOError.new("pid directory #{Params['pid_dir']} should be a directory")
|
72
|
+
end
|
73
|
+
unless File.readable?(Params['pid_dir']) && File.writable?(Params['pid_dir'])
|
74
|
+
raise IOError.new("you should have read/write permissions to pid dir: #{Params['pid_dir']}")
|
75
|
+
end
|
76
|
+
else
|
77
|
+
::FileUtils.mkdir_p Params['pid_dir']
|
78
|
+
end
|
79
|
+
elsif RUBY_PLATFORM =~ /mingw/ or RUBY_PLATFORM =~ /ms/ or RUBY_PLATFORM =~ /win/
|
80
|
+
require 'rbconfig'
|
81
|
+
begin
|
82
|
+
require 'win32/service'
|
83
|
+
require 'win32/daemon'
|
84
|
+
rescue LoadError
|
85
|
+
require 'rubygems'
|
86
|
+
require 'win32/service'
|
87
|
+
require 'win32/daemon'
|
88
|
+
end
|
89
|
+
include Win32
|
90
|
+
|
91
|
+
OS = :WINDOWS
|
92
|
+
# Get ruby interpreter path. Need it to run ruby binary.
|
93
|
+
# <br>TODO check whether this code works with Windows Ruby Version Management (e.g. Pik)
|
94
|
+
RUBY_INTERPRETER_PATH = File.join(Config::CONFIG["bindir"],
|
95
|
+
Config::CONFIG["RUBY_INSTALL_NAME"] +
|
96
|
+
Config::CONFIG["EXEEXT"]).tr!('/','\\')
|
97
|
+
|
98
|
+
# Path has to be absolute cause Win32-Service demands it.
|
99
|
+
wrapper = File.join(File.dirname(__FILE__), "..", "bin", File.basename(__FILE__, ".rb"), "daemon_wrapper")
|
100
|
+
# Wrapper script, that can receive commands from Windows Service Control and run user script,
|
101
|
+
# <br> provided as it's argument
|
102
|
+
WRAPPER_SCRIPT = File.expand_path(wrapper).tr!('/','\\')
|
103
|
+
else
|
104
|
+
raise "Unsupported platform #{RUBY_PLATFORM}"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Start a service/daemon.
|
108
|
+
# <br>It important to delete it after usage.
|
109
|
+
# ==== Arguments
|
110
|
+
# * <tt>binary_path</tt> - absolute path to the script that should be run in background
|
111
|
+
# NOTE for Linux script should be executable and with UNIX end-of-lines (LF).
|
112
|
+
# * <tt>binary_args</tt> - Array (not nil) of script's command line arguments
|
113
|
+
# * <tt>name</tt> - service/daemon name.
|
114
|
+
# NOTE should be unique
|
115
|
+
# * <tt>opts_specific</tt> - Hash of platform specific options (only for more specific usage)
|
116
|
+
# For more information regarding such options see documentation for
|
117
|
+
# win32-sevice (Windows), daemons (Linux/Mac)
|
118
|
+
# ==== Example (LINUX)
|
119
|
+
# <tt> RunInBackground.start "/home/user/test_app", [], "daemon_test", {:monitor => true}</tt>
|
120
|
+
def RunInBackground.start binary_path, binary_args, name, opts_specific = {}
|
121
|
+
Log.debug1("executable that should be run as daemon/service: #{binary_path}")
|
122
|
+
Log.debug1("arguments: #{binary_args}")
|
123
|
+
Log.debug1("specific options: #{opts_specific}")
|
124
|
+
|
125
|
+
if binary_path == nil or binary_args == nil or name == nil
|
126
|
+
Log.error("binary path, binary args, name arguments must be defined")
|
127
|
+
raise ArgumentError.new("binary path, binary args, name arguments must be defined")
|
128
|
+
end
|
129
|
+
|
130
|
+
if OS == :WINDOWS
|
131
|
+
new_binary_path = String.new(binary_path)
|
132
|
+
new_binary_args = Array.new(binary_args)
|
133
|
+
wrap_windows new_binary_path, new_binary_args
|
134
|
+
start_windows new_binary_path, new_binary_args, name, opts_specific
|
135
|
+
else # OS == LINUX
|
136
|
+
start_linux binary_path, binary_args, name, opts_specific
|
137
|
+
end
|
138
|
+
|
139
|
+
0.upto(TIMEOUT) do
|
140
|
+
if exists?(name) && running?(name)
|
141
|
+
puts "daemon/service #{name} started\n"
|
142
|
+
Log.info("daemon/service #{name} started")
|
143
|
+
return
|
144
|
+
end
|
145
|
+
sleep 1
|
146
|
+
end
|
147
|
+
# if got here then something gone wrong and daemon/service wasn't started in timely manner
|
148
|
+
delete name if exists? name
|
149
|
+
Log.error("daemon/service #{name} wasn't started in timely manner")
|
150
|
+
sleep(Params['log_param_max_elapsed_time_in_seconds_from_last_flush'] + 0.5)
|
151
|
+
raise "daemon/service #{name} wasn't started in timely manner"
|
152
|
+
end
|
153
|
+
|
154
|
+
def RunInBackground.start_linux binary_path, binary_args, name, opts = {}
|
155
|
+
unless File.executable? binary_path
|
156
|
+
raise ArgumentError.new("#{binary_path} is not executable.")
|
157
|
+
end
|
158
|
+
|
159
|
+
if opts.has_key? :dir
|
160
|
+
raise ArgumentError.new("No support for user-defined pid directories. See help")
|
161
|
+
end
|
162
|
+
|
163
|
+
opts[:app_name] = name
|
164
|
+
opts[:ARGV] = ['start']
|
165
|
+
(opts[:ARGV] << '--').concat(binary_args) if !binary_args.nil? && binary_args.size > 0
|
166
|
+
opts[:dir] = Params['pid_dir']
|
167
|
+
opts[:dir_mode] = :normal
|
168
|
+
|
169
|
+
Log.debug1("binary path: #{binary_path}")
|
170
|
+
Log.debug1("opts: #{opts}")
|
171
|
+
|
172
|
+
# Current process, that creates daemon, will transfer control to the Daemons library.
|
173
|
+
# So to continue working with current process, daemon creation initiated from separate process.
|
174
|
+
pid = fork do
|
175
|
+
Daemons.run binary_path, opts
|
176
|
+
end
|
177
|
+
Process.waitpid pid
|
178
|
+
end
|
179
|
+
|
180
|
+
def RunInBackground.start_windows binary_path, binary_args, name, opts = {}
|
181
|
+
raise ArgumentError.new("#{binary_path} doesn't exist'") unless File.exists? binary_path
|
182
|
+
|
183
|
+
# path that contains spaces must be escaped to be interpreted correctly
|
184
|
+
binary_path = %Q{"#{binary_path}"} if binary_path =~ / /
|
185
|
+
binary_path.tr!('/','\\')
|
186
|
+
# service should be run with the same load path as a current application
|
187
|
+
load_path = get_load_path
|
188
|
+
binary_args_str = binary_args.join ' '
|
189
|
+
|
190
|
+
opts[:binary_path_name] = \
|
191
|
+
"#{RUBY_INTERPRETER_PATH} #{load_path} #{binary_path} #{binary_args_str}"
|
192
|
+
# quotation marks must be escaped cause of ARGV parsing
|
193
|
+
opts[:binary_path_name] = opts[:binary_path_name].gsub '"', '"\\"'
|
194
|
+
opts[:service_name] = name
|
195
|
+
opts[:description] = name unless opts.has_key? :description
|
196
|
+
opts[:display_name] = name unless opts.has_key? :display_name
|
197
|
+
opts[:service_type] = Service::WIN32_OWN_PROCESS unless opts.has_key? :service_type
|
198
|
+
opts[:start_type] = Service::DEMAND_START unless opts.has_key? :start_type
|
199
|
+
|
200
|
+
# NOTE most of examples uses these dependencies. Meanwhile no explanations were found
|
201
|
+
opts[:dependencies] = ['W32Time','Schedule'] unless opts.has_key? :dependencies
|
202
|
+
# NOTE most of examples uses this option as defined beneath. The default is nil.
|
203
|
+
#opts[:load_order_group] = 'Network' unless opts.has_key? :load_order_group
|
204
|
+
|
205
|
+
Log.debug1("create service options: #{opts}")
|
206
|
+
Service.create(opts)
|
207
|
+
begin
|
208
|
+
Service.start(opts[:service_name])
|
209
|
+
rescue
|
210
|
+
Service.delete(opts[:service_name]) if Service.exists?(opts[:service_name])
|
211
|
+
raise
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Rerun current script in background.
|
216
|
+
# <br>Current process will be closed.
|
217
|
+
# <br>It suggested to remove from ARGV any command line arguments that point
|
218
|
+
# to run script in background, <br>otherwise an unexpexted result can be received
|
219
|
+
def RunInBackground.start! name, opts = {}
|
220
|
+
# $0 is the executable name.
|
221
|
+
start(File.expand_path($0), ARGV, name, opts)
|
222
|
+
sleep Params['log_param_max_elapsed_time_in_seconds_from_last_flush'] + 0.5
|
223
|
+
exit!
|
224
|
+
end
|
225
|
+
|
226
|
+
# Run in background script that was written as Windows Service, i.e. can receive signals
|
227
|
+
# from Service Control.
|
228
|
+
# <br>The code that is run in this script should be an extension of Win32::Daemon class.
|
229
|
+
# <br>For more information see Win32::Daemon help and examples.
|
230
|
+
# <br>No need to wrap such a script.
|
231
|
+
def RunInBackground.start_win32service binary_path, binary_args, name, opts_specific = {}
|
232
|
+
Log.debug1("executable that should be run as service: #{binary_path}")
|
233
|
+
Log.debug1("arguments: #{binary_args}")
|
234
|
+
Log.debug1("specific options: #{opts_specific}")
|
235
|
+
|
236
|
+
if OS == :WINDOWS
|
237
|
+
start_windows binary_path, binary_args, name, opts_specific
|
238
|
+
else # OS == :LINUX
|
239
|
+
raise NotImplementedError.new("Unsupported method on #{OS}")
|
240
|
+
end
|
241
|
+
0.upto(TIMEOUT) do
|
242
|
+
if exists?(name) && running?(name)
|
243
|
+
puts "windows service #{name} started\n"
|
244
|
+
Log.info("windows service #{name} started")
|
245
|
+
return
|
246
|
+
end
|
247
|
+
sleep 1
|
248
|
+
end
|
249
|
+
# if got here then something gone wrong and daemon/service wasn't started in timely manner
|
250
|
+
delete name if exists? name
|
251
|
+
Log.error("daemon/service #{name} wasn't started in timely manner")
|
252
|
+
sleep(Params['log_param_max_elapsed_time_in_seconds_from_last_flush'] + 0.5)
|
253
|
+
raise "daemon/service #{name} wasn't started in timely manner"
|
254
|
+
end
|
255
|
+
|
256
|
+
# Wrap an arbitrary ruby script withing script that can be run as Windows Service.
|
257
|
+
# ==== Arguments
|
258
|
+
# * <tt>binary_path</tt> - absolute path of the script that should be run in background
|
259
|
+
# * <tt>binary_args</tt> - array (not nil) of scripts command line arguments
|
260
|
+
#
|
261
|
+
# NOTE binary_path and binary_args contents will be change
|
262
|
+
def RunInBackground.wrap_windows binary_path, binary_args
|
263
|
+
raise ArgumentError.new("#{binary_path} doesn't exists") unless File.exists? binary_path
|
264
|
+
|
265
|
+
# service should be run with the same load path as a current application
|
266
|
+
load_path = get_load_path
|
267
|
+
# path that contains spaces must be escaped to be interpreted correctly
|
268
|
+
binary_path = %Q{"#{binary_path}"} if binary_path =~ / /
|
269
|
+
binary_args.insert(0, RUBY_INTERPRETER_PATH, load_path, binary_path.tr('/','\\'))
|
270
|
+
binary_path.replace(WRAPPER_SCRIPT)
|
271
|
+
end
|
272
|
+
|
273
|
+
# NOTE if this method will become public then may be needed to change appropriately wrapper script
|
274
|
+
def RunInBackground.stop name
|
275
|
+
if not exists? name
|
276
|
+
raise ArgumentError.new("Daemon #{name} doesn't exists")
|
277
|
+
elsif OS == :WINDOWS
|
278
|
+
Service.stop(name)
|
279
|
+
else # OS == :LINUX
|
280
|
+
opts = {:app_name => name,
|
281
|
+
:ARGV => ['stop'],
|
282
|
+
:dir_mode => :normal,
|
283
|
+
:dir => Params['pid_dir']
|
284
|
+
}
|
285
|
+
# Current process, that creates daemon, will transfer control to the Daemons library.
|
286
|
+
# So to continue working with current process, daemon creation initiated from separate process.
|
287
|
+
# It looks that it holds only for start command
|
288
|
+
#pid = fork do
|
289
|
+
Daemons.run "", opts
|
290
|
+
#end
|
291
|
+
#Process.waitpid pid
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Delete service/daemon.
|
296
|
+
# <br>If running then stop and delete.
|
297
|
+
def RunInBackground.delete name
|
298
|
+
if not exists? name
|
299
|
+
raise ArgumentError.new("Daemon #{name} doesn't exists")
|
300
|
+
elsif running? name
|
301
|
+
stop name
|
302
|
+
end
|
303
|
+
if OS == :WINDOWS
|
304
|
+
Service.delete name
|
305
|
+
else # OS == :LINUX
|
306
|
+
opts = {:app_name => name,
|
307
|
+
:ARGV => ['zap'],
|
308
|
+
:dir_mode => :normal,
|
309
|
+
:dir => Params['pid_dir']
|
310
|
+
}
|
311
|
+
# Current process, that creates daemon, will transfer control to the Daemons library.
|
312
|
+
# So to continue working with current process, daemon creation initiated from separate process.
|
313
|
+
# It looks that it holds only for start command
|
314
|
+
#pid = fork do
|
315
|
+
Daemons.run "", opts
|
316
|
+
#end
|
317
|
+
#Process.waitpid pid
|
318
|
+
end
|
319
|
+
0.upto(TIMEOUT) do
|
320
|
+
unless exists? name
|
321
|
+
puts "daemon/service #{name} deleted\n"
|
322
|
+
Log.info("daemon/service #{name} deleted")
|
323
|
+
return
|
324
|
+
end
|
325
|
+
sleep 1
|
326
|
+
end
|
327
|
+
# if got here then something gone wrong and daemon/service wasn't deleted in timely manner
|
328
|
+
Log.error("daemon/service #{name} wasn't deleted in timely manner")
|
329
|
+
sleep(Params['log_param_max_elapsed_time_in_seconds_from_last_flush'] + 0.5)
|
330
|
+
raise "daemon/service #{name} wasn't deleted in timely manner"
|
331
|
+
end
|
332
|
+
|
333
|
+
def RunInBackground.exists? name
|
334
|
+
if name == nil
|
335
|
+
raise ArgumentError.new("service/daemon name argument must be defined")
|
336
|
+
elsif OS == :WINDOWS
|
337
|
+
Service.exists? name
|
338
|
+
else # OS == :LINUX
|
339
|
+
pid_files = Daemons::PidFile.find_files(Params['pid_dir'], name)
|
340
|
+
pid_files != nil && pid_files.size > 0
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def RunInBackground.running? name
|
345
|
+
if not exists? name
|
346
|
+
raise ArgumentError.new("Daemon #{name} doesn't exists")
|
347
|
+
elsif OS == :WINDOWS
|
348
|
+
Service.status(name).current_state == 'running'
|
349
|
+
else # OS == :LINUX
|
350
|
+
Daemons::Pid.running? name
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Returns absolute standard form of the path.
|
355
|
+
# It includes path separators accepted on this OS
|
356
|
+
def RunInBackground.get_abs_std_path path
|
357
|
+
path = File.expand_path path
|
358
|
+
path = path.tr('/','\\') if OS == :WINDOWS
|
359
|
+
path
|
360
|
+
end
|
361
|
+
|
362
|
+
# Returns load path as it provided in command line.
|
363
|
+
def RunInBackground.get_load_path
|
364
|
+
load_path = Array.new
|
365
|
+
$:.each do |location|
|
366
|
+
load_path << %Q{-I"#{get_abs_std_path(location)}"}
|
367
|
+
end
|
368
|
+
load_path.join ' '
|
369
|
+
end
|
370
|
+
|
371
|
+
# Prepare ARGV so it can be provided as a command line arguments.
|
372
|
+
# Remove bg_command from ARGV to prevent infinite recursion.
|
373
|
+
def RunInBackground.prepare_argv
|
374
|
+
new_argv = Array.new
|
375
|
+
ARGV.each do |arg|
|
376
|
+
# For each argument try splitting to 'name'='value'
|
377
|
+
arg_arr = arg.split '='
|
378
|
+
# If no '=' is argument, just copy paste.
|
379
|
+
if arg_arr.size == 1
|
380
|
+
arg = "\"#{arg}\"" if arg =~ / /
|
381
|
+
new_argv << arg
|
382
|
+
# If it is a 'name'='value' argument add "" so the value can be passed as argument again.
|
383
|
+
elsif arg_arr.size == 2
|
384
|
+
# Skip bg_command flag (remove infinite recursion)!
|
385
|
+
if arg_arr[0] !~ /bg_command/
|
386
|
+
arg_arr[1] = "\"#{arg_arr[1]}\"" if arg_arr[1] =~ / /
|
387
|
+
new_argv << arg_arr.join('=')
|
388
|
+
end
|
389
|
+
else
|
390
|
+
Log.warning("ARGV argument #{arg} wasn't processed")
|
391
|
+
new_argv << arg
|
392
|
+
end
|
393
|
+
end
|
394
|
+
ARGV.clear
|
395
|
+
ARGV.concat new_argv
|
396
|
+
end
|
397
|
+
|
398
|
+
def RunInBackground.run &b
|
399
|
+
case Params['bg_command']
|
400
|
+
when nil
|
401
|
+
yield b
|
402
|
+
when 'start'
|
403
|
+
# To prevent service enter loop cause of background parameter
|
404
|
+
# all options that points to run in background must be disabled
|
405
|
+
# (for more information see documentation for RunInBackground::start!)
|
406
|
+
Params['bg_command'] = nil
|
407
|
+
RunInBackground.prepare_argv
|
408
|
+
|
409
|
+
begin
|
410
|
+
RunInBackground.start! Params['service_name']
|
411
|
+
rescue Exception => e
|
412
|
+
Log.error("Start service command failed: #{e.message}")
|
413
|
+
raise
|
414
|
+
end
|
415
|
+
when 'delete'
|
416
|
+
if RunInBackground.exists? Params['service_name']
|
417
|
+
RunInBackground.delete Params['service_name']
|
418
|
+
else
|
419
|
+
msg = "Can't delete. Service #{Params['service_name']} already deleted"
|
420
|
+
puts msg
|
421
|
+
Log.warning(msg)
|
422
|
+
end
|
423
|
+
else
|
424
|
+
msg = "Unsupported command #{Params['bg_command']}. Supported commands are: start, delete"
|
425
|
+
puts msg
|
426
|
+
Log.error(msg)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
private_class_method :start_linux, :start_windows, :wrap_windows, :stop, :get_abs_std_path,\
|
431
|
+
:get_load_path
|
432
|
+
end
|
433
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'run_in_background'
|
3
|
+
|
4
|
+
module BBFS
|
5
|
+
# TODO break to number of small tests according to functionality
|
6
|
+
# TODO rewrite with Shoulda/RSpec
|
7
|
+
class TestRunInBackground < Test::Unit::TestCase
|
8
|
+
#include BBFS::RunInBackground
|
9
|
+
|
10
|
+
if RUBY_PLATFORM =~ /linux/ or RUBY_PLATFORM =~ /darwin/
|
11
|
+
OS = :LINUX
|
12
|
+
elsif RUBY_PLATFORM =~ /mingw/ or RUBY_PLATFORM =~ /ms/ or RUBY_PLATFORM =~ /win/
|
13
|
+
require 'sys/uname'
|
14
|
+
OS = :WINDOWS
|
15
|
+
else
|
16
|
+
raise "Unsupported platform #{RUBY_PLATFORM}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup
|
20
|
+
@good_daemon = "good_daemon_test"
|
21
|
+
@good_daemonize = "good_daemonize_test"
|
22
|
+
@good_win32daemon = "good_win32daemon_test"
|
23
|
+
@bad_daemon = "bad_daemon_test"
|
24
|
+
@binary = File.join(File.dirname(File.expand_path(__FILE__)), 'test_app')
|
25
|
+
@binary.tr!('/','\\') if OS == :WINDOWS
|
26
|
+
File.chmod(0755, @binary) if OS == :LINUX && !File.executable?(@binary)
|
27
|
+
@absent_binary = File.join(File.dirname(File.expand_path(__FILE__)), "test_app_absent")
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_functionality
|
31
|
+
if OS == :WINDOWS && Sys::Uname.sysname =~ /(Windows 7)/
|
32
|
+
skip "This test shouldn't be run on #{$1}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# test start
|
36
|
+
# test application should actually start here
|
37
|
+
assert_nothing_raised{ RunInBackground.start(@binary, ["1000"], @good_daemon) }
|
38
|
+
assert_raise(ArgumentError) { RunInBackground.start(@absent_binary, ["1000"], @bad_daemon) }
|
39
|
+
|
40
|
+
# start arguments have to be defined
|
41
|
+
assert_raise(ArgumentError) { RunInBackground.start(["1000"], @bad_daemon) }
|
42
|
+
assert_raise(ArgumentError) { RunInBackground.start(nil, ["1000"], @bad_daemon) }
|
43
|
+
assert_raise(ArgumentError) { RunInBackground.start(@absent_binary, nil, @bad_daemon) }
|
44
|
+
assert_raise(ArgumentError) { RunInBackground.start(@absent_binary, ["1000"], nil) }
|
45
|
+
|
46
|
+
# test exists?
|
47
|
+
assert_raise(ArgumentError) { RunInBackground.exists? }
|
48
|
+
assert_equal(false, RunInBackground.exists?(@bad_daemon))
|
49
|
+
assert_equal(true, RunInBackground.exists?(@good_daemon))
|
50
|
+
|
51
|
+
# test running?
|
52
|
+
# if stop method will be public need to add test checks actually stopped daemon
|
53
|
+
assert_raise(ArgumentError) { RunInBackground.running? }
|
54
|
+
assert_raise(ArgumentError) { RunInBackground.running?(@bad_daemon) }
|
55
|
+
assert_equal(true, RunInBackground.running?(@good_daemon))
|
56
|
+
|
57
|
+
# test daemonazing (start!)
|
58
|
+
# from the nature of daemonization need to run it from another process that will be daemonized
|
59
|
+
ruby = (OS == :WINDOWS ? RunInBackground::RUBY_INTERPRETER_PATH : "ruby")
|
60
|
+
cmd = "#{ruby} -Ilib #{@binary} 50 #{@good_daemonize}"
|
61
|
+
pid = spawn(cmd)
|
62
|
+
Process.waitpid pid
|
63
|
+
# checking that it indeed was daemonized
|
64
|
+
# e.i. process was killed and code rerun in background
|
65
|
+
# it takes time to the OS to remove process, so using a timeout here
|
66
|
+
0.upto(RunInBackground::TIMEOUT) do
|
67
|
+
begin
|
68
|
+
Process.kill(0, pid)
|
69
|
+
rescue Errno::ESRCH
|
70
|
+
break
|
71
|
+
end
|
72
|
+
sleep 1
|
73
|
+
end
|
74
|
+
assert_raise(Errno::ESRCH) { Process.kill(0, pid) }
|
75
|
+
assert_equal(true, RunInBackground.exists?(@good_daemonize))
|
76
|
+
assert_equal(true, RunInBackground.running?(@good_daemonize))
|
77
|
+
|
78
|
+
# test running win32 specific daemon (start_win32service)
|
79
|
+
# wrapper script will be run
|
80
|
+
if OS == :WINDOWS
|
81
|
+
win32service_arg = [RunInBackground::RUBY_INTERPRETER_PATH, @binary, 1000]
|
82
|
+
assert_nothing_raised{
|
83
|
+
RunInBackground.start_win32service(RunInBackground::WRAPPER_SCRIPT,
|
84
|
+
win32service_arg, @good_win32daemon)
|
85
|
+
}
|
86
|
+
assert_equal(true, RunInBackground.exists?(@good_win32daemon))
|
87
|
+
assert_equal(true, RunInBackground.running?(@good_win32daemon))
|
88
|
+
else
|
89
|
+
assert_raise(NotImplementedError) {
|
90
|
+
RunInBackground.start_win32service(@absent_binary, [], @bad_daemon)
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
# uncomment following lines if there is a suspicion that something gone wrong
|
95
|
+
# inspired by bug caused by coworking of daemon_wrapper an logger
|
96
|
+
#sleep 10
|
97
|
+
#assert_equal(true, RunInBackground.running?(@good_daemon))
|
98
|
+
#assert_equal(true, RunInBackground.running?(@good_daemonize))
|
99
|
+
#assert_equal(true, RunInBackground.running?(@good_win32daemon))
|
100
|
+
|
101
|
+
# test delete
|
102
|
+
# test application should actually stop here
|
103
|
+
assert_raise(ArgumentError) { RunInBackground.delete }
|
104
|
+
assert_raise(ArgumentError) { RunInBackground.delete(@bad_daemon) }
|
105
|
+
assert_nothing_raised { RunInBackground.delete(@good_daemon) }
|
106
|
+
assert_equal(false, RunInBackground.exists?(@good_daemon))
|
107
|
+
|
108
|
+
assert_nothing_raised { RunInBackground.delete(@good_daemonize) }
|
109
|
+
assert_equal(false, RunInBackground.exists?(@good_daemonize))
|
110
|
+
|
111
|
+
# actuall only for Windows platform
|
112
|
+
if RunInBackground.exists?(@good_win32daemon)
|
113
|
+
assert_nothing_raised { RunInBackground.delete(@good_win32daemon) }
|
114
|
+
assert_equal(false, RunInBackground.exists?(@good_win32daemon))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def teardown
|
119
|
+
RunInBackground.delete @good_daemon if RunInBackground.exists? @good_daemon
|
120
|
+
RunInBackground.delete @good_daemonize if RunInBackground.exists? @good_daemonize
|
121
|
+
RunInBackground.delete @good_win32daemon if RunInBackground.exists? @good_win32daemon
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Toy script used for RunInBackground tests.
|
4
|
+
# usage: $0 number_of_iterations [daemon_name_if_daemonized]
|
5
|
+
|
6
|
+
DBG = false # NOTE be sure to disable it in production
|
7
|
+
|
8
|
+
begin
|
9
|
+
begin
|
10
|
+
require 'log'
|
11
|
+
require 'params'
|
12
|
+
rescue LoadError
|
13
|
+
$:.unshift(File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'lib'))
|
14
|
+
$:.unshift(File.join(File.dirname(File.expand_path(__FILE__)), '..', '..'))
|
15
|
+
require 'log'
|
16
|
+
require 'params'
|
17
|
+
end
|
18
|
+
|
19
|
+
# On WindowsXP log can be found under:
|
20
|
+
# C:/Documents and Settings/NetworkService/.bbfs/test_app_<pid>.log
|
21
|
+
BBFS::Params['log_file_name'] = File.join(Dir.home, '.bbfs', "#{File.basename(__FILE__)}_#{Process.pid}.log")
|
22
|
+
BBFS::Params['log_write_to_console'] = false
|
23
|
+
BBFS::Params['log_param_max_elapsed_time_in_seconds_from_last_flush'] = 1
|
24
|
+
if DBG
|
25
|
+
BBFS::Params['log_debug_level'] = 1
|
26
|
+
BBFS::Log.init
|
27
|
+
end
|
28
|
+
|
29
|
+
# app should be run in background
|
30
|
+
if ARGV.size == 2
|
31
|
+
begin
|
32
|
+
require 'run_in_background'
|
33
|
+
rescue LoadError
|
34
|
+
$:.unshift(File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'lib'))
|
35
|
+
$:.unshift(File.join(File.dirname(File.expand_path(__FILE__)), '..', '..'))
|
36
|
+
require 'run_in_background'
|
37
|
+
end
|
38
|
+
|
39
|
+
BBFS::Log.debug1 "Before run in background: PID #{Process.pid}"
|
40
|
+
# ARGV.pop returns frozen string and thus causes a failure of Service.create
|
41
|
+
# to fix it new string with the same content created.
|
42
|
+
BBFS::RunInBackground.start!(String.new(ARGV.pop))
|
43
|
+
# if got here then error
|
44
|
+
BBFS::Log.error "After run in background: ERROR"
|
45
|
+
end
|
46
|
+
|
47
|
+
max = (ARGV.size > 0 && ARGV[0] != nil && ARGV[0].to_i > 0)? ARGV[0].to_i : 200
|
48
|
+
|
49
|
+
while max > 0
|
50
|
+
BBFS::Log.debug1 "#{max}"
|
51
|
+
sleep 1
|
52
|
+
max -= 1
|
53
|
+
end
|
54
|
+
|
55
|
+
rescue Exception => err
|
56
|
+
BBFS::Log.error "Wrapper error: #{err}"
|
57
|
+
raise
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: run_in_background
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.8
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Genady Petelko
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: log
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: params
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: daemons
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: win32-service
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: sys-uname
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: ! 'This library provides a basic cross-platform functionality to runarbitrary
|
95
|
+
ruby scripts in background and control them.Supported platforms: Windows, Linux,
|
96
|
+
Mac.'
|
97
|
+
email: nukegluk@gmail.com
|
98
|
+
executables: []
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- lib/run_in_background.rb
|
103
|
+
- lib/run_in_background/version.rb
|
104
|
+
- test/run_in_background/run_in_background_test.rb
|
105
|
+
- test/run_in_background/test_app
|
106
|
+
homepage: http://github.com/kolmanv/bbfs
|
107
|
+
licenses: []
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.8.23
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: Cross-platform library for daemons management.
|
130
|
+
test_files:
|
131
|
+
- test/run_in_background/run_in_background_test.rb
|
132
|
+
- test/run_in_background/test_app
|