KVM test: kvm_subprocess: allow garbage collection of kvm_tail instances

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@redhat.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@4709 592f7852-d20e-0410-864c-8624ca9c26a4
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 @@
         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 @@
 
         # 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 @@
         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 @@
             except TypeError:
                 pass
 
-        fd = self._get_fd("tail")
-        buffer = ""
-        while True:
-            if self.__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:
-            params = self.termination_params + (status,)
-            self.termination_func(*params)
-        except TypeError:
-            pass
+            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:
+                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):