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<Object>): 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