Tail threads refer to kvm_tail objects, preventing them from being garbage- collected. 1) Before a tail thread exits, remove the reference to the thread from the kvm_tail object. 2) Add a function kill_tail_threads() which asks all tail threads to terminate and waits for them to do so. 3) Use kill_tail_threads() instead of VM.kill_tail_thread() (which was there for a different reason) in kvm_preprocessing.py. Signed-off-by: Michael Goldish <mgoldish@xxxxxxxxxx> --- client/tests/kvm/kvm_preprocessing.py | 5 +- client/tests/kvm/kvm_subprocess.py | 121 ++++++++++++++++++--------------- client/tests/kvm/kvm_vm.py | 8 -- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/client/tests/kvm/kvm_preprocessing.py b/client/tests/kvm/kvm_preprocessing.py index 2f6994a..123928e 100644 --- a/client/tests/kvm/kvm_preprocessing.py +++ b/client/tests/kvm/kvm_preprocessing.py @@ -344,9 +344,8 @@ def postprocess(test, params, env): else: vm.destroy(gracefully=False) - # Kill the tailing threads of all VMs - for vm in kvm_utils.env_get_all_vms(env): - vm.kill_tail_thread() + # Kill all kvm_subprocess tail threads + kvm_subprocess.kill_tail_threads() # Terminate tcpdump if no VMs are alive living_vms = [vm for vm in kvm_utils.env_get_all_vms(env) if vm.is_alive()] diff --git a/client/tests/kvm/kvm_subprocess.py b/client/tests/kvm/kvm_subprocess.py index 93a8429..f815069 100755 --- a/client/tests/kvm/kvm_subprocess.py +++ b/client/tests/kvm/kvm_subprocess.py @@ -548,6 +548,21 @@ class kvm_spawn: self.send(str + self.linesep) +_thread_kill_requested = False + +def kill_tail_threads(): + """ + Kill all kvm_tail threads. + + After calling this function no new threads should be started. + """ + global _thread_kill_requested + _thread_kill_requested = True + for t in threading.enumerate(): + if hasattr(t, "name") and t.name.startswith("tail_thread"): + t.join(10) + + class kvm_tail(kvm_spawn): """ This class runs a child process in the background and sends its output in @@ -608,7 +623,6 @@ class kvm_tail(kvm_spawn): # Start the thread in the background self.tail_thread = None - self.__thread_kill_requested = False if termination_func or output_func: self._start_thread() @@ -675,15 +689,6 @@ class kvm_tail(kvm_spawn): self.output_prefix = output_prefix - def kill_tail_thread(self): - """ - Stop the tailing thread which calls output_func() and - termination_func(). - """ - self.__thread_kill_requested = True - self._join_thread() - - def _tail(self): def print_line(text): # Pre-pend prefix and remove trailing whitespace @@ -695,60 +700,68 @@ class kvm_tail(kvm_spawn): except TypeError: pass - fd = self._get_fd("tail") - buffer = "" - while True: - if self.__thread_kill_requested: + try: + fd = self._get_fd("tail") + buffer = "" + while True: + global _thread_kill_requested + if _thread_kill_requested: + return + try: + # See if there's any data to read from the pipe + r, w, x = select.select([fd], [], [], 0.05) + except: + break + if fd in r: + # Some data is available; read it + new_data = os.read(fd, 1024) + if not new_data: + break + buffer += new_data + # Send the output to output_func line by line + # (except for the last line) + if self.output_func: + lines = buffer.split("\n") + for line in lines[:-1]: + print_line(line) + # Leave only the last line + last_newline_index = buffer.rfind("\n") + buffer = buffer[last_newline_index+1:] + else: + # No output is available right now; flush the buffer + if buffer: + print_line(buffer) + buffer = "" + # The process terminated; print any remaining output + if buffer: + print_line(buffer) + # Get the exit status, print it and send it to termination_func + status = self.get_status() + if status is None: return + print_line("(Process terminated with status %s)" % status) try: - # See if there's any data to read from the pipe - r, w, x = select.select([fd], [], [], 0.05) - except: - break - if fd in r: - # Some data is available; read it - new_data = os.read(fd, 1024) - if not new_data: - break - buffer += new_data - # Send the output to output_func line by line - # (except for the last line) - if self.output_func: - lines = buffer.split("\n") - for line in lines[:-1]: - print_line(line) - # Leave only the last line - last_newline_index = buffer.rfind("\n") - buffer = buffer[last_newline_index+1:] - else: - # No output is available right now; flush the buffer - if buffer: - print_line(buffer) - buffer = "" - # The process terminated; print any remaining output - if buffer: - print_line(buffer) - # Get the exit status, print it and send it to termination_func - status = self.get_status() - if status is None: - return - print_line("(Process terminated with status %s)" % status) - try: - params = self.termination_params + (status,) - self.termination_func(*params) - except TypeError: - pass + params = self.termination_params + (status,) + self.termination_func(*params) + except TypeError: + pass + finally: + self.tail_thread = None def _start_thread(self): - self.tail_thread = threading.Thread(None, self._tail) + self.tail_thread = threading.Thread(target=self._tail, + name="tail_thread_%s" % self.id) self.tail_thread.start() def _join_thread(self): # Wait for the tail thread to exit - if self.tail_thread: - self.tail_thread.join() + # (it's done this way because self.tail_thread may become None at any + # time) + t = self.tail_thread + if t: + t.join() class kvm_expect(kvm_tail): diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py index 78441c2..879e3dc 100755 --- a/client/tests/kvm/kvm_vm.py +++ b/client/tests/kvm/kvm_vm.py @@ -776,14 +776,6 @@ class VM: return not self.process or not self.process.is_alive() - def kill_tail_thread(self): - """ - Stop the tailing thread which reports the output of qemu. - """ - if self.process: - self.process.kill_tail_thread() - - def get_params(self): """ Return the VM's params dict. Most modified params take effect only -- 1.5.4.1 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html