Browse Source

Fix deadlock while dealing with a large amount of data in queue

Emmanuel Bouthenot 16 years ago
parent
commit
4c8b72c880
2 changed files with 56 additions and 13 deletions
  1. 1 1
      CHANGELOG
  2. 55 12
      pmailq

+ 1 - 1
CHANGELOG

@@ -9,4 +9,4 @@ Version 0.2 (2008-01-08):
   * Fix exit code on errors (http://bugs.debian.org/457299)
 
 Version 0.1 (2007-11-27):
-  * first release
+  * first release

+ 55 - 12
pmailq

@@ -20,9 +20,55 @@ DELQ = "postsuper -d"
 
 
 from optparse import OptionParser, OptionGroup # needs python >= 2.3
-import sys, popen2, fnmatch
+import sys, os, popen2, fcntl, select, fnmatch
 
-class mailqueue:
+class Proc:
+    
+    def run(self, command):
+        child = popen2.Popen3(command, 1)
+        child.tochild.close()             
+        outfile = child.fromchild
+        outfd = outfile.fileno()
+        errfile = child.childerr
+        errfd = errfile.fileno()
+
+        # avoid deadlocks
+        self.set_no_block(outfd)
+        self.set_no_block(errfd)
+        
+        outdata = errdata = ''
+        outeof = erreof = False
+
+        while True:
+            # wait for activity
+            ready = select.select([outfd,errfd],[],[])
+            if outfd in ready[0]:
+                outchunk = outfile.read()
+                if outchunk == '':
+                    outeof = True
+                outdata = outdata + outchunk
+            if errfd in ready[0]:
+                errchunk = errfile.read()
+                if errchunk == '':
+                    erreof = True
+                errdata = errdata + errchunk
+            if outeof and erreof:
+                break
+            # give a little time for buffers to fill
+            select.select([],[],[],.1)
+        
+        err = child.wait()
+        
+        return err, outdata, errdata
+
+    def set_no_block(self, fd):
+        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+        try:
+            fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
+        except AttributeError:            
+            fcntl.fcntl(fd, fcntl.F_SETFL, fl | fcntl.FNDELAY)
+
+class Mailqueue:
 
     def __init__(self):
         self.mailqueue = []
@@ -42,24 +88,21 @@ class mailqueue:
     
 
     def parse(self):
+        p_ret, p_stdout, p_stderr = Proc().run(MAILQ)
         
-        proc = popen2.Popen3(MAILQ, True)    
-
         # mail system down ?
-        p_ret = proc.wait()
         if p_ret != 0:
-            sys.stderr.write ("ERR : %s\n" % "".join(proc.childerr.readline()).strip())
+            sys.stderr.write ("ERR : %s\n" % ", ".join(p_stderr.strip().split("\n")))
             sys.exit (-1)
-
-        # checking empty mail queue
-        buffer = proc.fromchild.readlines()
+        
+        buffer = p_stdout.strip().split('\n');
+        # checking empty mail queue        
         if len(buffer)>0 and buffer[0].strip() == "Mail queue is empty":
             sys.stderr.write ("INFO : %s\n" % buffer[0].strip())
             return None
         
         # skip first and last line
-        buffer = "".join(buffer[1:-1]).strip()
-        
+        buffer = "\n".join(buffer[1:-1]).strip()
         for block in buffer.split("\n\n"):
             lines = block.split("\n")
 
@@ -193,7 +236,7 @@ def main():
 
     (options, args) = parser.parse_args()
 
-    m = mailqueue()
+    m = Mailqueue()
     m.add_filter("email", options.email)
     m.add_filter("msg", options.msg)
     m.add_filter("lowsize", options.lowsize)