2 # shell/process-controller.rb -
3 # $Release Version: 0.7 $
5 # by Keiju ISHITSUKA(keiju@ruby-lang.org)
17 class ProcessController
19 @ProcessControllers = {}
20 @ProcessControllersMonitor = Mutex.new
21 @ProcessControllersCV = ConditionVariable.new
23 @BlockOutputMonitor = Mutex.new
24 @BlockOutputCV = ConditionVariable.new
29 def_delegator("@ProcessControllersMonitor",
30 "synchronize", "process_controllers_exclusive")
32 def active_process_controllers
33 process_controllers_exclusive do
34 @ProcessControllers.dup
39 process_controllers_exclusive do
40 @ProcessControllers[pc] ||= 0
41 @ProcessControllers[pc] += 1
46 process_controllers_exclusive do
47 if @ProcessControllers[pc]
48 if (@ProcessControllers[pc] -= 1) == 0
49 @ProcessControllers.delete(pc)
50 @ProcessControllersCV.signal
56 def each_active_object
57 process_controllers_exclusive do
58 for ref in @ProcessControllers.keys
64 def block_output_synchronize(&b)
65 @BlockOutputMonitor.synchronize &b
68 def wait_to_finish_all_process_controllers
69 process_controllers_exclusive do
70 while !@ProcessControllers.empty?
71 Shell::notify("Process finishing, but active shell exists",
72 "You can use Shell#transact or Shell#check_point for more safe execution.")
74 for pc in @ProcessControllers.keys
75 Shell::notify(" Not finished jobs in "+pc.shell.to_s)
77 com.notify(" Jobs: %id")
81 @ProcessControllersCV.wait(@ProcessControllersMonitor)
87 # for shell-command complete finish at this process exit.
88 USING_AT_EXIT_WHEN_PROCESS_EXIT = true
90 wait_to_finish_all_process_controllers unless $@
99 @job_monitor = Mutex.new
100 @job_condition = ConditionVariable.new
107 @jobs_sync.synchronize(:SH) do
108 jobs.concat @waiting_jobs
109 jobs.concat @active_jobs
123 @jobs_sync.synchronize(:SH) do
124 @active_jobs.empty? or @waiting_jobs.empty?
128 def active_jobs_exist?
129 @jobs_sync.synchronize(:SH) do
134 def waiting_jobs_exist?
135 @jobs_sync.synchronize(:SH) do
141 def add_schedule(command)
142 @jobs_sync.synchronize(:EX) do
143 ProcessController.activate(self)
144 if @active_jobs.empty?
147 @waiting_jobs.push(command)
153 def start_job(command = nil)
154 @jobs_sync.synchronize(:EX) do
156 return if command.active?
157 @waiting_jobs.delete command
159 command = @waiting_jobs.shift
160 # command.notify "job(%id) pre-start.", @shell.debug?
162 return unless command
164 @active_jobs.push command
166 # command.notify "job(%id) post-start.", @shell.debug?
168 # start all jobs that input from the job
169 for job in @waiting_jobs.dup
170 start_job(job) if job.input == command
172 # command.notify "job(%id) post2-start.", @shell.debug?
176 def waiting_job?(job)
177 @jobs_sync.synchronize(:SH) do
178 @waiting_jobs.include?(job)
183 @jobs_sync.synchronize(:SH) do
184 @active_jobs.include?(job)
189 def terminate_job(command)
190 @jobs_sync.synchronize(:EX) do
191 @active_jobs.delete command
192 ProcessController.inactivate(self)
193 if @active_jobs.empty?
194 command.notify("start_jon in ierminate_jon(%id)", Shell::debug?)
201 def kill_job(sig, command)
202 @jobs_sync.synchronize(:EX) do
203 if @waiting_jobs.delete command
204 ProcessController.inactivate(self)
206 elsif @active_jobs.include?(command)
208 r = command.kill(sig)
209 ProcessController.inactivate(self)
211 print "Shell: Warn: $!\n" if @shell.verbose?
214 @active_jobs.delete command
220 # wait for all jobs to terminate
221 def wait_all_jobs_execution
222 @job_monitor.synchronize do
225 @job_condition.wait(@job_monitor)
227 job.notify("waiting job(%id)", Shell::debug?)
231 redo unless jobs.empty?
237 def sfork(command, &block)
238 pipe_me_in, pipe_peer_out = IO.pipe
239 pipe_peer_in, pipe_me_out = IO.pipe
243 pid_mutex = Mutex.new
244 pid_cv = ConditionVariable.new
247 ProcessController.block_output_synchronize do
249 ProcessController.each_active_object do |pc|
250 for jobs in pc.active_jobs
256 Thread.list.each do |th|
257 # th.kill unless [Thread.main, Thread.current].include?(th)
258 th.kill unless Thread.current == th
261 STDIN.reopen(pipe_peer_in)
262 STDOUT.reopen(pipe_peer_out)
264 ObjectSpace.each_object(IO) do |io|
265 if ![STDIN, STDOUT, STDERR].include?(io)
266 io.close unless io.closed?
277 command.notify "job(%name:##{pid}) start", @shell.debug?
281 command.notify("job(%id) start to waiting finish.", @shell.debug?)
282 _pid = Process.waitpid(pid, nil)
284 command.notify "warn: job(%id) was done already waitipd."
289 command.notify("Job(%id): Wait to finish when Process finished.", @shell.debug?)
290 # when the process ends, wait until the command termintes
291 if USING_AT_EXIT_WHEN_PROCESS_EXIT or _pid
293 command.notify("notice: Process finishing...",
294 "wait for Job[%id] to finish.",
295 "You can use Shell#transact or Shell#check_point for more safe execution.")
299 # command.notify "job(%id) pre-pre-finish.", @shell.debug?
300 @job_monitor.synchronize do
301 # command.notify "job(%id) pre-finish.", @shell.debug?
302 terminate_job(command)
303 # command.notify "job(%id) pre-finish2.", @shell.debug?
304 @job_condition.signal
305 command.notify "job(%id) finish.", @shell.debug?
310 pid_mutex.synchronize do
312 pid_cv.wait(pid_mutex)
316 return pid, pipe_me_in, pipe_me_out