omf_base 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: