Module: PosixOpen4

Defined in:
lib/procreate/posix/open4.rb

Class Method Summary collapse

Class Method Details

.background(argv, opts = {}) ⇒ Object

This is a rewritten version of Open4.background. The reason for this is two-fold:

  • The existing function returns a thread that is extended to have a #pid method. This in turn waits on a queue, which will never have a pid pushed to it if there are any problems spawning. This version instead doesn’t create the separate background thread until after the popen4 has succeeded, meaning any exceptions related to spawn are thrown immediately and there is no need for a pid queue.

  • The existing spawn function calls flatten! on its argv parameter. This is because it was trying to support either splatted or not-splatted array, but that falls over for the 1-arg array form of Kernel#exec (eg, to launch a command that has spaces in its name, without using the shell.)



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
# File 'lib/procreate/posix/open4.rb', line 23

def background argv, opts={}
	cmd = [argv].flatten.join(' ')

	getopt = getopts opts

	ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
	ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
	exitstatus = getopt[ %w( exitstatus exit_status status ) ]
	stdin = getopt[ %w( stdin in i 0 ) << 0 ]
	stdout = getopt[ %w( stdout out o 1 ) << 1 ]
	stderr = getopt[ %w( stderr err e 2 ) << 2 ]
	timeout = getopt[ %w( timeout spawn_timeout ) ]
	stdin_timeout = getopt[ %w( stdin_timeout ) ]
	stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
	stderr_timeout = getopt[ %w( stderr_timeout ) ]
	status = getopt[ %w( status ) ]
	cwd = getopt[ %w( cwd dir ) ]

	exitstatus =
		case exitstatus
			when TrueClass, FalseClass
				ignore_exit_failure = true if exitstatus
				[0]
			else
				[*(exitstatus || 0)].map{|i| Integer i}
		end

	stdin  ||= '' if stdin_timeout
	stdout ||= '' if stdout_timeout
	stderr ||= '' if stderr_timeout

	started = false
	begin
		chdir(cwd) do
			pid, i, o, e = popen4(*argv)
			started = true

			thread = Thread.new do
				begin
					# the semantics of this timeout have been changed slightly, in
					# that it doesn't include the popen4 call itself anymore. i think
					# the intention however was for it to apply primarily to the
					# Process.waitpid
					Timeout::timeout timeout do
						te = ThreadEnsemble.new pid
						te.add_thread(i, stdin) do |i, stdin|
							relay stdin, i, stdin_timeout
							i.close rescue nil
						end
						te.add_thread(o, stdout) do |o, stdout|
							relay o, stdout, stdout_timeout
						end
						te.add_thread(e, stderr) do |o, stderr|
							relay e, stderr, stderr_timeout
						end
						te.run

						status = Process.waitpid2(pid).last
						unless ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus)
							raise SpawnError.new(cmd, status)
						end
						status
					end
				ensure
					[i, o, e].each { |fd| fd.close unless fd.closed? }
				end
			end

			sc = class << thread; self; end
			sc.module_eval do
				define_method(:pid) { pid }
				define_method(:spawn_status) { value }
				define_method(:exitstatus) { spawn_status.exitstatus }
			end

			thread
		end
	rescue
		raise unless not started and ignore_exec_failure
	end
end