about summary refs log tree commit homepage
path: root/lib/dtas/process.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-08-24 09:54:45 +0000
committerEric Wong <normalperson@yhbt.net>2013-08-24 09:54:45 +0000
commit3e09ac0c10c95bb24a08af62393b4f761e2743d0 (patch)
tree778dffa2ba8798503fc047db0feef6d65426ea22 /lib/dtas/process.rb
downloaddtas-3e09ac0c10c95bb24a08af62393b4f761e2743d0.tar.gz
Diffstat (limited to 'lib/dtas/process.rb')
-rw-r--r--lib/dtas/process.rb88
1 files changed, 88 insertions, 0 deletions
diff --git a/lib/dtas/process.rb b/lib/dtas/process.rb
new file mode 100644
index 0000000..35ca6a6
--- /dev/null
+++ b/lib/dtas/process.rb
@@ -0,0 +1,88 @@
+# -*- encoding: binary -*-
+# :stopdoc:
+# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require 'shellwords'
+require 'io/wait'
+module DTAS::Process
+  PIDS = {}
+
+  def self.reaper
+    begin
+      pid, status = Process.waitpid2(-1, Process::WNOHANG)
+      pid or return
+      obj = PIDS.delete(pid)
+      yield status, obj
+    rescue Errno::ECHILD
+      return
+    end while true
+  end
+
+  # a convienient way to display commands so it's easy to
+  # read, copy and paste to a shell
+  def xs(cmd)
+    cmd.map { |w| Shellwords.escape(w) }.join(' ')
+  end
+
+  # for long-running processes (sox/play/ecasound filters)
+  def dtas_spawn(env, cmd, opts)
+    opts = { close_others: true, pgroup: true }.merge!(opts)
+
+    # stringify env, integer values are easier to type unquoted as strings
+    env.each { |k,v| env[k] = v.to_s }
+
+    pid = begin
+      Process.spawn(env, cmd, opts)
+    rescue Errno::EINTR # Ruby bug?
+      retry
+    end
+    warn [ :spawn, pid, cmd ].inspect if $DEBUG
+    @spawn_at = Time.now.to_f
+    PIDS[pid] = self
+    pid
+  end
+
+  # this is like backtick, but takes an array instead of a string
+  # This will also raise on errors
+  def qx(cmd, opts = {})
+    r, w = IO.pipe
+    opts = opts.merge(out: w)
+    r.binmode
+    if err = opts[:err]
+      re, we = IO.pipe
+      re.binmode
+      opts[:err] = we
+    end
+    pid = begin
+      Process.spawn(*cmd, opts)
+    rescue Errno::EINTR # Ruby bug?
+      retry
+    end
+    w.close
+    if err
+      we.close
+      res = ""
+      want = { r => res, re => err }
+      begin
+        readable = IO.select(want.keys) or next
+        readable[0].each do |io|
+          bytes = io.nread
+          begin
+            want[io] << io.read_nonblock(bytes > 0 ? bytes : 11)
+          rescue Errno::EAGAIN
+            # spurious wakeup, bytes may be zero
+          rescue EOFError
+            want.delete(io)
+          end
+        end
+      end until want.empty?
+      re.close
+    else
+      res = r.read
+    end
+    r.close
+    _, status = Process.waitpid2(pid)
+    return res if status.success?
+    raise RuntimeError, "`#{xs(cmd)}' failed: #{status.inspect}"
+  end
+end