omf_base 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,181 @@
1
+ #-------------------------------------------------------------------------------
2
+ # Copyright (c) 2006-2014 National ICT Australia (NICTA), Australia
3
+ # Copyright (c) 2004-2009 WINLAB, Rutgers University, USA
4
+ # This software may be used and distributed solely under the terms of the MIT license (License).
5
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
6
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
7
+ #-------------------------------------------------------------------------------
8
+ #
9
+ #
10
+ # Library of client side helpers
11
+ #
12
+ require 'fcntl'
13
+ require 'omf_base'
14
+
15
+ module OMF::Base
16
+ #
17
+ # Run an application on the client.
18
+ #
19
+ # Borrows from Open3
20
+ #
21
+ class ExecApp < LObject
22
+
23
+ # Holds the pids for all active apps
24
+ @@all_apps = Hash.new
25
+
26
+ # Return an application instance based on its ID
27
+ #
28
+ # @param [String] id of the application to return
29
+ def self.[](id)
30
+ app = @@all_apps[id]
31
+ info "Unknown application '#{id}/#{id.class}'" if app.nil?
32
+ return app
33
+ end
34
+
35
+ def self.signal_all(signal = 'KILL')
36
+ @@all_apps.each_value { |app| app.signal(signal) }
37
+ end
38
+
39
+ attr_reader :pid, :clean_exit
40
+
41
+ # True if this active app is being killed by a proper
42
+ # call to ExecApp.signal_all() or signal()
43
+ # (i.e. when the caller of ExecApp decided to stop the application,
44
+ # as far as we are concerned, this is a 'clean' exit)
45
+ @clean_exit = false
46
+
47
+
48
+ def stdin(line)
49
+ debug "Writing '#{line}' to app '#{@id}'"
50
+ @stdin.write("#{line}\n")
51
+ @stdin.flush
52
+ end
53
+
54
+ def signal(signal = 'KILL')
55
+ debug "Sending signal '#{signal}' to app '#{@id}' with pid #{@pid}"
56
+ @clean_exit = true
57
+ Process.kill(signal, -1 * @pid) # we are sending to the entire process group
58
+ end
59
+
60
+ #
61
+ # Run an application 'cmd' in a separate thread and monitor
62
+ # its stdout. Also send status reports to the 'observer' by
63
+ # calling its "call(eventType, appId, message")"
64
+ #
65
+ # @param id ID of application (used for reporting)
66
+ # @param observer Observer of application's progress
67
+ # @param cmd Command path and args
68
+ # @param map_std_err_to_out If true report stderr as stdin [false]
69
+ #
70
+ def initialize(id, cmd, map_std_err_to_out = false, working_directory = nil, &observer)
71
+
72
+ @id = id || self.object_id
73
+ @observer = observer
74
+ @@all_apps[@id] = self
75
+ @exit_status = nil
76
+ @threads = []
77
+
78
+ pw = IO::pipe # pipe[0] for read, pipe[1] for write
79
+ pr = IO::pipe
80
+ pe = IO::pipe
81
+
82
+ debug "Starting application '#{@id}' - cmd: '#{cmd}'"
83
+ #@observer.call(:STARTED, id, cmd)
84
+ call_observer(:STARTED, cmd)
85
+ @pid = fork {
86
+ # child will remap pipes to std and exec cmd
87
+ pw[1].close
88
+ STDIN.reopen(pw[0])
89
+ pw[0].close
90
+
91
+ pr[0].close
92
+ STDOUT.reopen(pr[1])
93
+ pr[1].close
94
+
95
+ pe[0].close
96
+ STDERR.reopen(pe[1])
97
+ pe[1].close
98
+
99
+ begin
100
+ pgid = Process.setsid # Create a new process group
101
+ # which includes all potential child processes
102
+ STDOUT.puts "INTERNAL WARNING: Assuming process_group_id == pid" unless pgid == $$
103
+ Dir.chdir working_directory if working_directory
104
+ exec(cmd)
105
+ rescue => ex
106
+ cmd = cmd.join(' ') if cmd.kind_of?(Array)
107
+ STDERR.puts "exec failed for '#{cmd}' (#{$!}): #{ex}"
108
+ end
109
+ # Should never get here
110
+ exit!
111
+ }
112
+
113
+ pw[0].close
114
+ pr[1].close
115
+ pe[1].close
116
+ monitor_pipe(:stdout, pr[0])
117
+ monitor_pipe(map_std_err_to_out ? :stdout : :stderr, pe[0])
118
+ # Create thread which waits for application to exit
119
+ @threads << Thread.new(id, @pid) do |id, pid|
120
+ Process.waitpid(pid)
121
+ # Exit status is sometimes nil (OSX 10.8, ping)
122
+ @exit_status = $?.exitstatus || 0
123
+ if @exit_status > 127
124
+ @exit_status = 128 - @exit_status
125
+ end
126
+ @@all_apps.delete(@id)
127
+ # app finished
128
+ if (@exit_status == 0) || @clean_exit
129
+ debug "Application '#{@id}' finished"
130
+ else
131
+ debug "Application '#{@id}' failed (code=#{@exit_status})"
132
+ end
133
+ end
134
+ @stdin = pw[1]
135
+
136
+ # wait for done in yet another thread
137
+ Thread.new do
138
+ @threads.each {|t| t.join }
139
+ call_observer("EXIT", @exit_status)
140
+ end
141
+ debug "Application is running with PID #{@pid}"
142
+ end
143
+
144
+ private
145
+
146
+ #
147
+ # Create a thread to monitor the process and its output
148
+ # and report that back to the server
149
+ #
150
+ # @param name Name of app stream to monitor (should be :stdout, :stderr)
151
+ # @param pipe Pipe to read from
152
+ #
153
+ def monitor_pipe(name, pipe)
154
+ @threads << Thread.new() do
155
+ begin
156
+ while true do
157
+ s = pipe.readline.chomp
158
+ call_observer(name.to_s.upcase, s)
159
+ end
160
+ rescue EOFError
161
+ # do nothing
162
+ rescue => err
163
+ error "monitorApp(#{@id}): #{err}"
164
+ debug "#{err}\n\t#{err.backtrace.join("\n\t")}"
165
+ ensure
166
+ pipe.close
167
+ end
168
+ end
169
+ end
170
+
171
+ def call_observer(event_type, msg)
172
+ return unless @observer
173
+ begin
174
+ @observer.call(event_type, @id, msg)
175
+ rescue Exception => ex
176
+ warn "Exception while calling observer '#{@observer}': #{ex}"
177
+ debug "#{ex}\n\t#{ex.backtrace.join("\n\t")}"
178
+ end
179
+ end
180
+ end
181
+ end
@@ -6,6 +6,6 @@
6
6
  #-------------------------------------------------------------------------------
7
7
  module OMF
8
8
  module Base
9
- VERSION = '1.0.2'
9
+ VERSION = '1.0.3'
10
10
  end
11
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omf_base
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-30 00:00:00.000000000 Z
12
+ date: 2014-02-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: log4r
@@ -40,6 +40,7 @@ files:
40
40
  - README.md
41
41
  - Rakefile
42
42
  - lib/omf_base.rb
43
+ - lib/omf_base/exec_app.rb
43
44
  - lib/omf_base/load_yaml.rb
44
45
  - lib/omf_base/lobject.rb
45
46
  - lib/omf_base/log4r/file_outputter.rb
@@ -72,3 +73,4 @@ signing_key:
72
73
  specification_version: 3
73
74
  summary: Some basic, but common functionality used in OMF libraries.
74
75
  test_files: []
76
+ has_rdoc: