Module: RUtilAnts::ForeignProcess

Defined in:
lib/rUtilAnts/ForeignProcess.rb

Overview

This module defines a method to run a given Ruby's object and parameters in a separate process. This can be useful when $LD_LIBRARY_PATH has to be changed before continuing.

Defined Under Namespace

Classes: MethodCallInfo

Class Method Summary collapse

Class Method Details

.exec_cmd_other_session(iShellCmd, iObject, iMethod, iParameters) ⇒ Object

Execute a command in another Ruby session, executing some Shell commands before invocation.

Parameters
  • iShellCmd (String): Shell command to invoke before Ruby
  • iObject (Object): Object that will have a function to call in the new session
  • iMethod (Symbol): Method to call on the object
  • Parameters (list): Remaining parameters
    Return
    • Exception: An error, or nil if success
    • Object: The result of the function call (valid only if no error returned)
    
    
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    # File 'lib/rUtilAnts/ForeignProcess.rb', line 63
    
    def self.exec_cmd_other_session(iShellCmd, iObject, iMethod, iParameters)
      rError = nil
      rResult = nil
    
      # Protect it from exceptions, to ensure that a valid error message will be returned
      begin
        log_debug "Execute method #{iMethod}(#{iParameters.join(', ')}) in a new process with shell command: #{iShellCmd} ..."
    
        # Create an object that we will serialize, containing all needed information for the session
        lInfo = MethodCallInfo.new
        lInfo.LogFile = get_log_file
        lInfo.LibRootDir = get_lib_root_dir
        lInfo.BugTrackerURL = get_bug_tracker_url
        lInfo.RequireFiles = []
        # Do not store ForeignProcess require
        $".each do |iRequireName|
          if (iRequireName.match(/ForeignProcess/) == nil)
            lInfo.RequireFiles << iRequireName
          end
        end
        lInfo.LoadPath = $LOAD_PATH.clone
        lMethodDetails = MethodCallInfo::MethodDetails.new
        lMethodDetails.Parameters = iParameters
        lMethodDetails.Method = iMethod
        lMethodDetails.Object = iObject
        log_debug "Method to be marshalled: #{lMethodDetails.inspect}"
        lInfo.SerializedMethodDetails = Marshal.dump(lMethodDetails)
        lCurrentThread = Thread.current
        # Dump this object in a temporary file
        require 'tmpdir'
        lInfoFileName = "#{Dir.tmpdir}/RubyExec_#{lCurrentThread.object_id}_Info"
        File.open(lInfoFileName, 'w') do |oFile|
          oFile.write(Marshal.dump(lInfo))
        end
        # For security reasons, ensure that only us can read this file. It can contain passwords.
        require 'fileutils'
        FileUtils.chmod(0700, lInfoFileName)
        # Generate the Ruby file that will run everything
        lExecFileName = "#{Dir.tmpdir}/RubyExec_#{lCurrentThread.object_id}_Exec.rb"
        File.open(lExecFileName, 'w') do |oFile|
          oFile << "
    \# This is a generated file that should not stay persistent. You can delete it.
    \# It has been generated by rUtilAnts::ForeignProcess module. Check http://rutilants.sourceforge.net for further details.
    require '#{File.expand_path(__FILE__)}'
    RUtilAnts::ForeignProcess::execute_embedded_function(ARGV[0], ARGV[1])
    "
        end
        # For security reasons, ensure that only us can read and execute this file.
        FileUtils.chmod(0700, lExecFileName)
        # Name the file that will receive the result of the function call
        lResultFileName = "#{Dir.tmpdir}/RubyExec_#{lCurrentThread.object_id}_Result"
    
        # Call this Ruby file by first executing the Shell command
        lCmd = "#{iShellCmd}; ruby -w #{lExecFileName} #{lInfoFileName} #{lResultFileName} 2>&1"
        lOutput = `#{lCmd}`
        lErrorCode = $?
        if (lErrorCode == 0)
          # Read the result file
          File.open(lResultFileName, 'r') do |iFile|
            rResult = Marshal.load(iFile.read)
          end
        else
          rError = RuntimeError.new("Error while running command \"#{lCmd}\". Here is the output:\n#{lOutput}.")
        end
    
        # Remove files
        File.unlink(lInfoFileName)
        File.unlink(lExecFileName)
        if (File.exists?(lResultFileName))
          File.unlink(lResultFileName)
        end
      rescue Exception
        rError = $!
      end
    
      log_debug "Method executed with error #{rError} and result #{rResult}."
    
      return rError, rResult
    end

    .execute_embedded_function(iInfoFileName, iResultFileName) ⇒ Object

    Execute a function along with its parameters stored in a file. This method is used by the file generated by exec_cmd_other_session. It should not be called directly.

    Parameters
    • iInfoFileName (String): The file containing info
    • iResultFileName (String): The file used to store the result serialized
    
    
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    # File 'lib/rUtilAnts/ForeignProcess.rb', line 150
    
    def self.execute_embedded_function(iInfoFileName, iResultFileName)
      begin
        # Read the file
        lInfo = nil
        File.open(iInfoFileName, 'r') do |iFile|
          lInfo = Marshal.load(iFile.read)
        end
        # Set the load path
        lInfo.LoadPath.each do |iDir|
          if (!$LOAD_PATH.include?(iDir))
            $LOAD_PATH << iDir
          end
        end
        # Require all given files
        lInfo.RequireFiles.each do |iRequireName|
          require iRequireName
        end
        # Initialize logging
        RUtilAnts::Logging::install_logger_on_object(:lib_root_dir => lInfo.LibRootDir, :bug_tracker_url => lInfo.BugTrackerURL, :log_file => lInfo.LogFile)
        log_debug "New process spawned with requires: #{lInfo.RequireFiles.join(', ')}."
        # Unserialize the method details
        lMethodDetails = Marshal.load(lInfo.SerializedMethodDetails)
        # Call the method on the object with all its parameters
        log_debug "Calling method #{lMethodDetails.Method}(#{lMethodDetails.Parameters.join(', ')}) ..."
        lResult = lMethodDetails.Object.send(lMethodDetails.Method, *lMethodDetails.Parameters)
        log_debug "Method returned #{lResult}."
      rescue Exception
        lResult = RuntimeError.new("Error occurred while executing foreign call: #{$!}. Backtrace: #{$!.backtrace.join("\n")}")
      end
      begin
        # Store the result in the file for return
        File.open(iResultFileName, 'w') do |oFile|
          oFile.write(Marshal.dump(lResult))
        end
        # For security reasons, ensure that only us can read this file. It can contain passwords.
        require 'fileutils'
        FileUtils.chmod(0700, iResultFileName)
      rescue Exception
        log_err "Error while writing result in to #{iResultFileName}: #{$!}."
      end
    end

    .install_foreign_process_on_objectObject

    Initialize the ForeignProcess methods in the Object namespace

    
    
    193
    194
    195
    # File 'lib/rUtilAnts/ForeignProcess.rb', line 193
    
    def self.install_foreign_process_on_object
      Object.module_eval('include RUtilAnts::ForeignProcess')
    end