Module: Sensu::Spawn
- Defined in:
- lib/sensu/spawn.rb
Constant Summary collapse
- POSIX_SPAWN_PLATFORMS =
[:linux, :macosx].freeze
- POSIX_SPAWN_ARCHS =
["x86_64", "i386"].freeze
- @@mutex =
Mutex.new
Class Method Summary collapse
-
.build_child_process(command) ⇒ Array
Build a child process attached to a pipe, in order to capture its output (STDERR, STDOUT).
-
.child_process(command, options = {}) ⇒ Array
Create a child process, return its output (STDERR & STDOUT), and exit status.
-
.on_windows? ⇒ TrueClass, FalseClass
Determine if the current platform is Windows.
-
.posix_spawn? ⇒ TrueClass, FalseClass
Determine if POSIX Spawn is used to create child processes on the current platform.
-
.process(command, options = {}, &callback) ⇒ Object
Spawn a child process.
-
.setup(options = {}) ⇒ Object
Setup a spawn process worker, to limit the number of concurrent child processes allowed at one time.
-
.write_and_read(writer, reader, data) ⇒ String
Write data to a stream/file and read a stream/file until end of file (EOF).
Class Method Details
.build_child_process(command) ⇒ Array
Build a child process attached to a pipe, in order to capture its output (STDERR, STDOUT). The child process will be a platform dependent shell, that is responsible for executing the provided command.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/sensu/spawn.rb', line 87 def build_child_process(command) reader, writer = IO.pipe shell = case when on_windows? ["cmd", "/c"] else ["sh", "-c"] end ChildProcess.posix_spawn = posix_spawn? shell_command = shell + [command] child = ChildProcess.build(*shell_command) child.io.stdout = child.io.stderr = writer child.leader = true [child, reader, writer] end |
.child_process(command, options = {}) ⇒ Array
Create a child process, return its output (STDERR & STDOUT), and exit status. The child process will have its own process group, may accept data via STDIN, and have a timeout. ChildProcess Unix POSIX spawn (‘start()`) is not thread safe, so a mutex is used to allow safe execution on Ruby runtimes with real threads (JRuby).
Using stdlib’s Timeout instead of child.poll_for_exit to avoid a deadlock, when the child output is greater than the OS max buffer size.
147 148 149 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 |
# File 'lib/sensu/spawn.rb', line 147 def child_process(command, ={}) child, reader, writer = build_child_process(command) child.duplex = true if [:data] @@mutex.synchronize do child.start end writer.close output = "" if [:timeout] Timeout::timeout([:timeout], ChildProcess::TimeoutError) do output = write_and_read(child.io.stdin, reader, [:data]) child.wait end else output = write_and_read(child.io.stdin, reader, [:data]) child.wait end [output, child.exit_code] rescue ChildProcess::TimeoutError output = "Execution timed out" begin child.stop rescue => error pid = child.pid rescue "?" output += " - Unable to TERM/KILL the process: ##{pid}, #{error}" end [output, 2] rescue => error child.stop rescue nil ["Unexpected error: #{error}", 3] end |
.on_windows? ⇒ TrueClass, FalseClass
Determine if the current platform is Windows.
75 76 77 78 |
# File 'lib/sensu/spawn.rb', line 75 def on_windows? return @on_windows unless @on_windows.nil? @on_windows = ChildProcess.windows? end |
.posix_spawn? ⇒ TrueClass, FalseClass
Determine if POSIX Spawn is used to create child processes on the current platform. ChildProcess supports POSIX Spawn for several platforms (OSs & architectures), however, Sensu only enables the use of POSIX Spawn on a select few.
65 66 67 68 69 70 |
# File 'lib/sensu/spawn.rb', line 65 def posix_spawn? return @posix_spawn unless @posix_spawn.nil? platform_supported = POSIX_SPAWN_PLATFORMS.include?(ChildProcess.os) arch_supported = POSIX_SPAWN_ARCHS.include?(ChildProcess.arch) @posix_spawn = platform_supported && arch_supported end |
.process(command, options = {}, &callback) ⇒ Object
Spawn a child process. The EventMachine reactor (loop) must be running for this method to work.
51 52 53 54 55 56 57 |
# File 'lib/sensu/spawn.rb', line 51 def process(command, ={}, &callback) create = Proc.new do child_process(command, ) end setup() unless @process_worker @process_worker.enqueue(create, callback) end |
.setup(options = {}) ⇒ Object
Setup a spawn process worker, to limit the number of concurrent child processes allowed at one time. This method creates the spawn process worker instance variable: ‘@process_worker`.
37 38 39 40 |
# File 'lib/sensu/spawn.rb', line 37 def setup(={}) limit = [:limit] || 12 @process_worker ||= EM::Worker.new(:concurrency => limit) end |
.write_and_read(writer, reader, data) ⇒ String
Write data to a stream/file and read a stream/file until end of file (EOF).
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/sensu/spawn.rb', line 110 def write_and_read(writer, reader, data) buffer = (data || "").dup output = "" loop do unless buffer.empty? writer.write(buffer.slice!(0, 8191)) writer.close if buffer.empty? end begin readable, _ = IO.select([reader], nil, nil, 0) if readable || buffer.empty? output << reader.readpartial(8192) end rescue EOFError reader.close break end end output end |