Module: DTAS::Process

Includes:
SpawnFix, XS
Included in:
Format, Format, Mlib, PartStats, Sink, Source::Cmd, Source::File, Source::Mp3gain, SplitFX
Defined in:
lib/dtas/process.rb

Overview

process management helpers

Constant Summary collapse

PIDS =

:nodoc:

{}

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SpawnFix

#spawn

Methods included from XS

#xs

Class Method Details

.reaperObject


16
17
18
19
20
21
22
23
24
25
# File 'lib/dtas/process.rb', line 16

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

Instance Method Details

#dtas_spawn(env, cmd, opts) ⇒ Object

for long-running processes (sox/play/ecasound filters)


73
74
75
76
77
78
79
80
81
82
# File 'lib/dtas/process.rb', line 73

def dtas_spawn(env, cmd, opts)
  opts = { close_others: true, pgroup: true }.merge!(opts)
  env = env_expand(env, opts)

  pid = spawn(env, cmd, opts)
  warn [ :spawn, pid, cmd ].inspect if $DEBUG
  @spawn_at = DTAS.now
  PIDS[pid] = self
  pid
end

#env_expand(env, opts) ⇒ Object

expand common shell constructs based on environment variables this is order-dependent, but Ruby 1.9+ hashes are already order-dependent This recurses


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/dtas/process.rb', line 30

def env_expand(env, opts)
  env = env.dup
  if false == opts.delete(:expand)
    env.each do |key, val|
      Numeric === val and env[key] = val.to_s
    end
  else
    env.each do |key, val|
      case val = env_expand_i(env, key, val)
      when Array
        val.flatten!
        env[key] = Shellwords.join(val)
      end
    end
  end
end

#env_expand_ary(env, key, val) ⇒ Object

warning, recursion:


68
69
70
# File 'lib/dtas/process.rb', line 68

def env_expand_ary(env, key, val)
  val.map { |v| env_expand_i(env.dup, key, v) }
end

#env_expand_i(env, key, val) ⇒ Object


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/dtas/process.rb', line 47

def env_expand_i(env, key, val)
  case val
  when Numeric # stringify numeric values to simplify users' lives
    env[key] = val.to_s
  when /[\`\$]/ # perform variable/command expansion
    tmp = env.dup
    tmp.delete(key)
    tmp.each do |k,v|
      # best effort, this can get wonky
      tmp[k] = Shellwords.join(v.flatten) if Array === v
    end
    val = qx(tmp, "echo #{val}", expand: false)
    env[key] = val.chomp
  when Array
    env[key] = env_expand_ary(env, key, val)
  else
    val
  end
end

#qx(env, cmd = {}, opts = {}) ⇒ Object

this is like backtick, but takes an array instead of a string This will also raise on errors

Raises:

  • (RuntimeError)

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/dtas/process.rb', line 86

def qx(env, cmd = {}, opts = {})
  unless Hash === env
    cmd, opts = env, cmd
    env = {}
  end
  buf = ''.b
  r, w = DTAS::Nonblock.pipe
  opts = opts.merge(out: w)
  r.binmode
  no_raise = opts.delete(:no_raise)
  if err_str = opts.delete(:err_str)
    re, we = DTAS::Nonblock.pipe
    re.binmode
    opts[:err] = we
  end
  env = env_expand(env, opts)
  pid = spawn(env, *cmd, opts)
  w.close
  if err_str
    we.close
    res = ''.b
    want = { r => res, re => err_str }
    begin
      readable = IO.select(want.keys) or next
      readable[0].each do |io|
        case rv = io.read_nonblock(2000, buf, exception: false)
        when :wait_readable # spurious wakeup, bytes may be zero
        when nil then want.delete(io)
        else
          want[io] << rv
        end
      end
    end until want.empty?
    re.close
  else
    res = r.read # read until EOF
  end
  r.close
  _, status = Process.waitpid2(pid)
  return res if status.success?
  return status if no_raise
  raise RuntimeError, "`#{xs(cmd)}' failed: #{status.inspect}"
end