Selaa lähdekoodia

- Spurious errors related to unclosed files ("bad file descriptor",
typically) were evident at supervisord "reload" time (when using
the "reload" command from supervisorctl).

Chris McDonough 17 vuotta sitten
vanhempi
commit
87c2e490c2

+ 4 - 0
CHANGES.txt

@@ -235,6 +235,10 @@ Next Release
     http://www.repoze.org/LICENSE.txt; it's slightly less restrictive
     http://www.repoze.org/LICENSE.txt; it's slightly less restrictive
     than the ZPL (no servicemark clause).
     than the ZPL (no servicemark clause).
 
 
+  - Spurious errors related to unclosed files ("bad file descriptor",
+    typically) were evident at supervisord "reload" time (when using
+    the "reload" command from supervisorctl).
+
 3.0a2
 3.0a2
 
 
   - Fixed the README.txt example for defining the supervisor RPC
   - Fixed the README.txt example for defining the supervisor RPC

+ 17 - 2
src/supervisor/loggers.py

@@ -70,11 +70,15 @@ class Handler:
         try:
         try:
             self.stream.flush()
             self.stream.flush()
         except IOError, why:
         except IOError, why:
-            # if supervisor output is piped, this can be raised at exit
+            # if supervisor output is piped, EPIPE can be raised at exit
             if why[0] != errno.EPIPE:
             if why[0] != errno.EPIPE:
                 raise
                 raise
 
 
     def close(self):
     def close(self):
+        if hasattr(self.stream, 'fileno'):
+            fd = self.stream.fileno()
+            if fd < 3: # don't ever close stdout or stderr
+                return
         self.stream.close()
         self.stream.close()
 
 
     def emit(self, record):
     def emit(self, record):
@@ -194,7 +198,14 @@ class RotatingFileHandler(FileHandler):
         """
         """
         if self.maxBytes <= 0:
         if self.maxBytes <= 0:
             return
             return
-        
+
+        try:
+            pos = self.stream.tell()
+        except IOError, why:
+            # Attempt to trap IOError: [Errno 29] Illegal seek
+            print self.baseFilename, self.maxBytes, self.stream
+            raise
+            
         if not (self.stream.tell() >= self.maxBytes):
         if not (self.stream.tell() >= self.maxBytes):
             return
             return
 
 
@@ -245,6 +256,10 @@ class Logger:
             handlers = []
             handlers = []
         self.handlers = handlers
         self.handlers = handlers
 
 
+    def close(self):
+        for handler in self.handlers:
+            handler.close()
+
     def blather(self, msg, **kw):
     def blather(self, msg, **kw):
         if LevelsByName.BLAT >= self.level:
         if LevelsByName.BLAT >= self.level:
             self.log(LevelsByName.BLAT, msg, **kw)
             self.log(LevelsByName.BLAT, msg, **kw)

+ 4 - 7
src/supervisor/options.py

@@ -885,6 +885,9 @@ class ServerOptions(Options):
         for config, server in self.httpservers:
         for config, server in self.httpservers:
             server.close()
             server.close()
 
 
+    def close_logger(self):
+        self.logger.close()
+
     def setsignals(self):
     def setsignals(self):
         signal.signal(signal.SIGTERM, self.sigreceiver)
         signal.signal(signal.SIGTERM, self.sigreceiver)
         signal.signal(signal.SIGINT, self.sigreceiver)
         signal.signal(signal.SIGINT, self.sigreceiver)
@@ -940,14 +943,8 @@ class ServerOptions(Options):
         return asyncore.socket_map
         return asyncore.socket_map
 
 
     def cleanup_fds(self):
     def cleanup_fds(self):
-        # try to close any unused file descriptors to prevent leakage.
-        # we start at the "highest" descriptor in the asyncore socket map
-        # because this might be called remotely and we don't want to close
-        # the internet channel during this call.
-        asyncore_fds = asyncore.socket_map.keys()
+        # try to close any leaked file descriptors (for reload)
         start = 5
         start = 5
-        if asyncore_fds:
-            start = max(asyncore_fds) + 1
         for x in range(start, self.minfds):
         for x in range(start, self.minfds):
             try:
             try:
                 os.close(x)
                 os.close(x)

+ 1 - 0
src/supervisor/supervisord.py

@@ -317,6 +317,7 @@ def main(args=None, test=False):
         if test or (options.mood < SupervisorStates.RESTARTING):
         if test or (options.mood < SupervisorStates.RESTARTING):
             break
             break
         options.close_httpservers()
         options.close_httpservers()
+        options.close_logger()
         first = False
         first = False
 
 
 def go(options):
 def go(options):

+ 0 - 1
src/supervisor/tests/base.py

@@ -267,7 +267,6 @@ class DummyLogger:
     def getvalue(self):
     def getvalue(self):
         return ''.join(self.data)
         return ''.join(self.data)
 
 
-
 class DummySupervisor:
 class DummySupervisor:
     def __init__(self, options=None, state=None, process_groups=None):
     def __init__(self, options=None, state=None, process_groups=None):
         if options is None:
         if options is None:

+ 10 - 0
src/supervisor/tests/test_loggers.py

@@ -271,12 +271,22 @@ class LoggerTests(unittest.TestCase):
         logger.critical('hello')
         logger.critical('hello')
         self.assertEqual(len(handler.records), 1)
         self.assertEqual(len(handler.records), 1)
 
 
+    def test_close(self):
+        from supervisor.loggers import LevelsByName
+        handler = DummyHandler(LevelsByName.CRIT)
+        logger = self._makeOne(LevelsByName.CRIT, (handler,))
+        logger.close()
+        self.assertEqual(handler.closed, True)
+
 class DummyHandler:
 class DummyHandler:
+    close = False
     def __init__(self, level):
     def __init__(self, level):
         self.level = level
         self.level = level
         self.records = []
         self.records = []
     def emit(self, record):
     def emit(self, record):
         self.records.append(record)
         self.records.append(record)
+    def close(self):
+        self.closed = True
 
 
 def test_suite():
 def test_suite():
     return unittest.findTestCases(sys.modules[__name__])
     return unittest.findTestCases(sys.modules[__name__])

+ 18 - 0
src/supervisor/tests/test_options.py

@@ -305,6 +305,24 @@ class ServerOptionsTests(unittest.TestCase):
             except OSError:
             except OSError:
                 pass
                 pass
 
 
+    def test_close_httpservers(self):
+        instance = self._makeOne()
+        class Server:
+            closed = False
+            def close(self):
+                self.closed = True
+        server = Server()
+        instance.httpservers = [({}, server)]
+        instance.close_httpservers()
+        self.assertEqual(server.closed, True)
+        
+    def test_close_logger(self):
+        instance = self._makeOne()
+        logger = DummyLogger()
+        instance.logger = logger
+        instance.close_logger()
+        self.assertEqual(logger.closed, True)
+
     def test_write_pidfile_ok(self):
     def test_write_pidfile_ok(self):
         fn = tempfile.mktemp()
         fn = tempfile.mktemp()
         try:
         try: