Class: OsVm::Machine::QemuReaper

Inherits:
Object
  • Object
show all
Defined in:
lib/osvm/machine.rb

Overview

Owns all wait and signal operations for one QEMU child. A nonblocking wait is always performed before pending signals are sent. If the child exits between that wait and Process.kill, it remains this process's unreaped zombie, so its PID cannot be reused for an unrelated process.

Constant Summary collapse

POLL_INTERVAL =
0.01

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pid, &on_exit) ⇒ QemuReaper

Returns a new instance of QemuReaper.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/osvm/machine.rb', line 30

def initialize(pid, &on_exit)
  @pid = pid
  @on_exit = on_exit
  @mutex = Mutex.new
  @condition = ConditionVariable.new
  @signals = []
  @reaped = false
  @started = false
  @start = Queue.new
  @thread = Thread.new do
    @start.pop
    run
  end
end

Instance Attribute Details

#pid ⇒ Object (readonly, protected)

Returns the value of attribute pid.



84
85
86
# File 'lib/osvm/machine.rb', line 84

def pid
  @pid
end

Instance Method Details

#deliver_signals ⇒ Object (protected)



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/osvm/machine.rb', line 116

def deliver_signals
  pending = @mutex.synchronize do
    next [] if @reaped

    @signals.shift(@signals.length)
  end

  pending.each do |signal, response|
    result = begin
      Process.kill(signal, pid)
      true
    rescue Errno::ESRCH
      false
    rescue StandardError => e
      e
    end
    response << result
  end
end

#join(timeout = nil) ⇒ Object



77
78
79
80
# File 'lib/osvm/machine.rb', line 77

def join(timeout = nil)
  start
  @thread.join(timeout) && self
end

#mark_reaped ⇒ Object (protected)



136
137
138
139
140
141
142
# File 'lib/osvm/machine.rb', line 136

def mark_reaped
  pending = @mutex.synchronize do
    @reaped = true
    @signals.shift(@signals.length)
  end
  pending.each { |entry| entry.last << false }
end

#reap ⇒ Object (protected)



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/osvm/machine.rb', line 92

def reap
  status = nil

  loop do
    waited = Process.waitpid2(pid, Process::WNOHANG)
    if waited
      status = waited.last
      mark_reaped
      break
    end

    deliver_signals
    @mutex.synchronize do
      @condition.wait(@mutex, POLL_INTERVAL) if @signals.empty? && !@reaped
    end
  end

  status
rescue Errno::ECHILD
  # No signal may be sent once ownership can no longer be proven.
  mark_reaped
  nil
end

#run ⇒ Object (protected)



86
87
88
89
90
# File 'lib/osvm/machine.rb', line 86

def run
  @on_exit.call(reap)
ensure
  mark_reaped
end

#signal(signal) ⇒ Boolean

Request a signal from the sole child owner.

Returns:

  • (Boolean) —

    true when Process.kill was attempted, false when the child had already been reaped



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/osvm/machine.rb', line 59

def signal(signal)
  start
  response = Queue.new
  queued = @mutex.synchronize do
    next false if @reaped

    @signals << [signal, response]
    @condition.signal
    true
  end
  return false unless queued

  result = response.pop
  raise result if result.is_a?(Exception)

  result
end

#start ⇒ Object



45
46
47
48
49
50
51
52
53
54
# File 'lib/osvm/machine.rb', line 45

def start
  activate = @mutex.synchronize do
    next false if @started

    @started = true
    true
  end
  @start << true if activate
  self
end