Module: RunInBackground

Includes:
Win32
Defined in:
lib/run_in_background.rb,
lib/run_in_background/version.rb

Overview

This library provides a basic cross-platform functionality to run arbitrary ruby scripts in background and control them.
Supported platforms: Windows, Linux, Mac

NOTE UAC (User Account Control) should be disabled to use library on Windows 7:
* Click on the Windows Icon
* Click on the Control Panel
* Type in UAC in the search box (up-right corner of your window)
* Click on "Change User Account Control settings"
* Drag the slider down to either "Notify me when programs try to make changes to my computer"
  or to disable it completely
* Reboot your computer when you're ready

General Limitations:

  • Only ruby scripts can be run in background.

  • No multiple instances with the same name.

Notes:

  • Linux/Mac specific methods have _linux suffix

  • Windows specific methods have _windows suffix

  • While enhancing windows code, take care that paths are in windows format,
    e.i. with “\” file separator while ruby by default uses a “/”

  • Additional functionality such as restart, reload, etc. will be added on demand

  • Remains support to provide platform specific options for start.
    For more information regarding such options see documentation for win32-sevice (Windows), daemons (Linux/Mac)

Linux Notes:

  • pid_dir parameter contains absolute path to directory where pid files will be stored.
    If directory doesn’t exists then it will be created.
    User should have a read/write permissions to this location.
    Default location: $HOME/.bbfs/pids

  • User should check that default pid directory is free from pid files of “killed” daemons.
    It may happen, for example, when system finished in abnormal way then pid files were not deleted by daemons library.
    In such case incorrect results can be received, for example for exists? method
    One of the suggested methods can be before starting a daemon to check with exists? method whether daemon already exists and with running? method does it running.

Constant Summary collapse

TIMEOUT =

Maximal time in seconds to wait until OS will finish a requested operation, e.g. daemon start/delete.

20
OS =
:WINDOWS
RUBY_INTERPRETER_PATH =

Get ruby interpreter path. Need it to run ruby binary.
TODO check whether this code works with Windows Ruby Version Management (e.g. Pik)

File.join(Config::CONFIG["bindir"],
                                      Config::CONFIG["RUBY_INSTALL_NAME"] +
Config::CONFIG["EXEEXT"]).tr!('/','\\')
WRAPPER_SCRIPT =

Wrapper script, that can receive commands from Windows Service Control and run user script,
provided as it’s argument

File.expand_path(wrapper).tr!('/','\\')
VERSION =
"1.2.0"

Class Method Summary collapse

Class Method Details

.delete(name) ⇒ Object

Delete service/daemon.
If running then stop and delete.



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/run_in_background.rb', line 296

def RunInBackground.delete name
  if not exists? name
    raise ArgumentError.new("Daemon #{name} doesn't exists")
  elsif running? name
    stop name
  end
  if OS == :WINDOWS
    Service.delete name
  else  # OS == :LINUX
    opts = {:app_name => name,
            :ARGV => ['zap'],
            :dir_mode => :normal,
            :dir => Params['pid_dir']
    }
    # Current process, that creates daemon, will transfer control to the Daemons library.
    # So to continue working with current process, daemon creation initiated from separate process.
    # It looks that it holds only for start command
    #pid = fork do
    Daemons.run "", opts
    #end
    #Process.waitpid pid
  end
  0.upto(TIMEOUT) do
    unless exists? name
      puts "daemon/service #{name} deleted\n"
      Log.debug1("daemon/service #{name} deleted")
      return
    end
    sleep 1
  end
  # if got here then something gone wrong and daemon/service wasn't deleted in timely manner
  Log.error("daemon/service #{name} wasn't deleted in timely manner")
Log.flush
  raise "daemon/service #{name} wasn't deleted in timely manner"
end

.exists?(name) ⇒ Boolean

Returns:

  • (Boolean)


332
333
334
335
336
337
338
339
340
341
# File 'lib/run_in_background.rb', line 332

def RunInBackground.exists? name
  if name == nil
    raise ArgumentError.new("service/daemon name argument must be defined")
  elsif OS == :WINDOWS
    Service.exists? name
  else  # OS == :LINUX
    pid_files = Daemons::PidFile.find_files(Params['pid_dir'], name)
    pid_files != nil && pid_files.size > 0
  end
end

.prepare_argvObject

Prepare ARGV so it can be provided as a command line arguments. Remove bg_command from ARGV to prevent infinite recursion.



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/run_in_background.rb', line 372

def RunInBackground.prepare_argv
  new_argv = Array.new
  ARGV.each do |arg|
    # For each argument try splitting to 'name'='value'
    arg_arr = arg.split '='
    # If no '=' is argument, just copy paste.
    if arg_arr.size == 1
      arg = "\"#{arg}\"" if arg =~ / /
      new_argv << arg
      # If it is a 'name'='value' argument add "" so the value can be passed as argument again.
    elsif arg_arr.size == 2
      # Skip bg_command flag (remove infinite recursion)!
      if arg_arr[0] !~ /bg_command/
        arg_arr[1] = "\"#{arg_arr[1]}\"" if arg_arr[1] =~ / /
        new_argv << arg_arr.join('=')
      end
    else
      Log.warning("ARGV argument #{arg} wasn't processed")
      new_argv << arg
    end
  end
  ARGV.clear
  ARGV.concat new_argv
end

.run(&b) ⇒ Object



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/run_in_background.rb', line 397

def RunInBackground.run &b
  case Params['bg_command']
    when nil
      yield b
    when 'start'
      # To prevent service enter loop cause of background parameter
      # all options that points to run in background must be disabled
      # (for more information see documentation for RunInBackground::start!)
      Params['bg_command'] = nil
      RunInBackground.prepare_argv

      begin
        RunInBackground.start! Params['service_name']
      rescue Exception => e
        Log.error("Start service command failed: #{e.message}")
        raise
      end
    when 'delete'
      if RunInBackground.exists? Params['service_name']
        RunInBackground.delete Params['service_name']
      else
        msg = "Can't delete. Service #{Params['service_name']} already deleted"
        puts msg
        Log.warning(msg)
      end
    else
	  msg = "Unsupported command #{Params['bg_command']}. Supported commands are: start, delete"
      puts msg
      Log.error(msg)
  end
nil	
end

.running?(name) ⇒ Boolean

Returns:

  • (Boolean)


343
344
345
346
347
348
349
350
351
# File 'lib/run_in_background.rb', line 343

def RunInBackground.running? name
  if not exists? name
    raise ArgumentError.new("Daemon #{name} doesn't exists")
  elsif OS == :WINDOWS
    Service.status(name).current_state == 'running'
  else  # OS == :LINUX
    Daemons::Pid.running? name
  end
end

.start(binary_path, binary_args, name, opts_specific = {}) ⇒ Object

Start a service/daemon.
It important to delete it after usage.

Arguments

  • binary_path - absolute path to the script that should be run in background

    NOTE for Linux script should be executable and with UNIX end-of-lines (LF).
    
  • binary_args - Array (not nil) of script’s command line arguments

  • name - service/daemon name.

    NOTE should be unique
    
  • opts_specific - Hash of platform specific options (only for more specific usage)

    For more information regarding such options see documentation for
    win32-sevice (Windows), daemons (Linux/Mac)
    

Example (LINUX)

RunInBackground.start "/home/user/test_app", [], "daemon_test", {:monitor => true}



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/run_in_background.rb', line 119

def RunInBackground.start binary_path, binary_args, name, opts_specific = {}
  Log.debug1("executable that should be run as daemon/service: #{binary_path}")
  Log.debug1("arguments: #{binary_args}")
  Log.debug1("specific options: #{opts_specific}")

  if binary_path == nil or binary_args == nil or name == nil
    Log.error("binary path, binary args, name arguments must be defined")
    raise ArgumentError.new("binary path, binary args, name arguments must be defined")
  end

  if OS == :WINDOWS
    new_binary_path = String.new(binary_path)
    new_binary_args = Array.new(binary_args)
    wrap_windows new_binary_path, new_binary_args
    start_windows new_binary_path, new_binary_args, name, opts_specific
  else  # OS == LINUX
    start_linux binary_path, binary_args, name, opts_specific
  end

  0.upto(TIMEOUT) do
    if exists?(name) && running?(name)
      puts "daemon/service #{name} started\n"
      Log.debug1("daemon/service #{name} started")
      return
    end
    sleep 1
  end
  # if got here then something gone wrong and daemon/service wasn't started in timely manner
  delete name if exists? name
  Log.error("daemon/service #{name} wasn't started in timely manner")
Log.flush
  raise "daemon/service #{name} wasn't started in timely manner"
end

.start!(name, opts = {}) ⇒ Object

Rerun current script in background.
Current process will be closed.
It suggested to remove from ARGV any command line arguments that point to run script in background,
otherwise an unexpexted result can be received



218
219
220
221
222
223
# File 'lib/run_in_background.rb', line 218

def RunInBackground.start! name, opts = {}
  # $0 is the executable name.
  start(File.expand_path($0), ARGV, name, opts)
 Log.flush
  exit!
end

.start_win32service(binary_path, binary_args, name, opts_specific = {}) ⇒ Object

Run in background script that was written as Windows Service, i.e. can receive signals from Service Control.
The code that is run in this script should be an extension of Win32::Daemon class.
For more information see Win32::Daemon help and examples.
No need to wrap such a script.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/run_in_background.rb', line 230

def RunInBackground.start_win32service binary_path, binary_args, name, opts_specific = {}
  Log.debug1("executable that should be run as service: #{binary_path}")
  Log.debug1("arguments: #{binary_args}")
  Log.debug1("specific options: #{opts_specific}")

  if OS == :WINDOWS
    start_windows binary_path, binary_args, name, opts_specific
  else  # OS == :LINUX
    raise NotImplementedError.new("Unsupported method on #{OS}")
  end
  0.upto(TIMEOUT) do
    if exists?(name) && running?(name)
      puts "windows service #{name} started\n"
      Log.debug1("windows service #{name} started")
      return
    end
    sleep 1
  end
  # if got here then something gone wrong and daemon/service wasn't started in timely manner
  delete name if exists? name
  Log.error("daemon/service #{name} wasn't started in timely manner")
Log.flush
  raise "daemon/service #{name} wasn't started in timely manner"
end