mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	Issue #14632: Updated WatchedFileHandler to deal with race condition. Thanks to John Mulligan for the problem report and patch.
This commit is contained in:
		
							parent
							
								
									231d474a7b
								
							
						
					
					
						commit
						885f6fd472
					
				
					 2 changed files with 69 additions and 20 deletions
				
			
		| 
						 | 
					@ -23,7 +23,7 @@ Copyright (C) 2001-2012 Vinay Sajip. All Rights Reserved.
 | 
				
			||||||
To use, simply 'import logging.handlers' and log away!
 | 
					To use, simply 'import logging.handlers' and log away!
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import logging, socket, os, cPickle, struct, time, re
 | 
					import errno, logging, socket, os, cPickle, struct, time, re
 | 
				
			||||||
from stat import ST_DEV, ST_INO, ST_MTIME
 | 
					from stat import ST_DEV, ST_INO, ST_MTIME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
| 
						 | 
					@ -392,11 +392,13 @@ class WatchedFileHandler(logging.FileHandler):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    def __init__(self, filename, mode='a', encoding=None, delay=0):
 | 
					    def __init__(self, filename, mode='a', encoding=None, delay=0):
 | 
				
			||||||
        logging.FileHandler.__init__(self, filename, mode, encoding, delay)
 | 
					        logging.FileHandler.__init__(self, filename, mode, encoding, delay)
 | 
				
			||||||
        if not os.path.exists(self.baseFilename):
 | 
					        self.dev, self.ino = -1, -1
 | 
				
			||||||
            self.dev, self.ino = -1, -1
 | 
					        self._statstream()
 | 
				
			||||||
        else:
 | 
					
 | 
				
			||||||
            stat = os.stat(self.baseFilename)
 | 
					    def _statstream(self):
 | 
				
			||||||
            self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
 | 
					        if self.stream:
 | 
				
			||||||
 | 
					            sres = os.fstat(self.stream.fileno())
 | 
				
			||||||
 | 
					            self.dev, self.ino = sres[ST_DEV], sres[ST_INO]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def emit(self, record):
 | 
					    def emit(self, record):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -406,19 +408,27 @@ class WatchedFileHandler(logging.FileHandler):
 | 
				
			||||||
        has, close the old stream and reopen the file to get the
 | 
					        has, close the old stream and reopen the file to get the
 | 
				
			||||||
        current stream.
 | 
					        current stream.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if not os.path.exists(self.baseFilename):
 | 
					        # Reduce the chance of race conditions by stat'ing by path only
 | 
				
			||||||
            stat = None
 | 
					        # once and then fstat'ing our new fd if we opened a new log stream.
 | 
				
			||||||
            changed = 1
 | 
					        # See issue #14632: Thanks to John Mulligan for the problem report
 | 
				
			||||||
        else:
 | 
					        # and patch.
 | 
				
			||||||
            stat = os.stat(self.baseFilename)
 | 
					        try:
 | 
				
			||||||
            changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
 | 
					            # stat the file by path, checking for existence
 | 
				
			||||||
        if changed and self.stream is not None:
 | 
					            sres = os.stat(self.baseFilename)
 | 
				
			||||||
            self.stream.flush()
 | 
					        except OSError as err:
 | 
				
			||||||
            self.stream.close()
 | 
					            if err.errno == errno.ENOENT:
 | 
				
			||||||
            self.stream = self._open()
 | 
					                sres = None
 | 
				
			||||||
            if stat is None:
 | 
					            else:
 | 
				
			||||||
                stat = os.stat(self.baseFilename)
 | 
					                raise
 | 
				
			||||||
            self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
 | 
					        # compare file system stat with that of our stream file handle
 | 
				
			||||||
 | 
					        if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino:
 | 
				
			||||||
 | 
					            if self.stream is not None:
 | 
				
			||||||
 | 
					                # we have an open file handle, clean it up
 | 
				
			||||||
 | 
					                self.stream.flush()
 | 
				
			||||||
 | 
					                self.stream.close()
 | 
				
			||||||
 | 
					                # open a new file handle and get new stat info from that fd
 | 
				
			||||||
 | 
					                self.stream = self._open()
 | 
				
			||||||
 | 
					                self._statstream()
 | 
				
			||||||
        logging.FileHandler.emit(self, record)
 | 
					        logging.FileHandler.emit(self, record)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SocketHandler(logging.Handler):
 | 
					class SocketHandler(logging.Handler):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,7 @@ import cStringIO
 | 
				
			||||||
import gc
 | 
					import gc
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import select
 | 
					import select
 | 
				
			||||||
import socket
 | 
					import socket
 | 
				
			||||||
| 
						 | 
					@ -40,6 +41,7 @@ import sys
 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
from test.test_support import captured_stdout, run_with_locale, run_unittest
 | 
					from test.test_support import captured_stdout, run_with_locale, run_unittest
 | 
				
			||||||
import textwrap
 | 
					import textwrap
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
import weakref
 | 
					import weakref
 | 
				
			||||||
| 
						 | 
					@ -1873,6 +1875,43 @@ class ChildLoggerTest(BaseTest):
 | 
				
			||||||
        self.assertTrue(c2 is c3)
 | 
					        self.assertTrue(c2 is c3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HandlerTest(BaseTest):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @unittest.skipUnless(threading, 'Threading required for this test.')
 | 
				
			||||||
 | 
					    def test_race(self):
 | 
				
			||||||
 | 
					        # Issue #14632 refers.
 | 
				
			||||||
 | 
					        def remove_loop(fname, tries):
 | 
				
			||||||
 | 
					            for _ in range(tries):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    os.unlink(fname)
 | 
				
			||||||
 | 
					                except OSError:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					                time.sleep(0.004 * random.randint(0, 4))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def cleanup(remover, fn, handler):
 | 
				
			||||||
 | 
					            handler.close()
 | 
				
			||||||
 | 
					            remover.join()
 | 
				
			||||||
 | 
					            if os.path.exists(fn):
 | 
				
			||||||
 | 
					                os.unlink(fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fd, fn = tempfile.mkstemp('.log', 'test_logging-3-')
 | 
				
			||||||
 | 
					        os.close(fd)
 | 
				
			||||||
 | 
					        del_count = 1000
 | 
				
			||||||
 | 
					        log_count = 1000
 | 
				
			||||||
 | 
					        remover = threading.Thread(target=remove_loop, args=(fn, del_count))
 | 
				
			||||||
 | 
					        remover.daemon = True
 | 
				
			||||||
 | 
					        remover.start()
 | 
				
			||||||
 | 
					        for delay in (False, True):
 | 
				
			||||||
 | 
					            h = logging.handlers.WatchedFileHandler(fn, delay=delay)
 | 
				
			||||||
 | 
					            self.addCleanup(cleanup, remover, fn, h)
 | 
				
			||||||
 | 
					            f = logging.Formatter('%(asctime)s: %(levelname)s: %(message)s')
 | 
				
			||||||
 | 
					            h.setFormatter(f)
 | 
				
			||||||
 | 
					            for _ in range(log_count):
 | 
				
			||||||
 | 
					                time.sleep(0.005)
 | 
				
			||||||
 | 
					                r = logging.makeLogRecord({'msg': 'testing' })
 | 
				
			||||||
 | 
					                h.handle(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Set the locale to the platform-dependent default.  I have no idea
 | 
					# Set the locale to the platform-dependent default.  I have no idea
 | 
				
			||||||
# why the test does this, but in any case we save the current locale
 | 
					# why the test does this, but in any case we save the current locale
 | 
				
			||||||
# first and restore it at the end.
 | 
					# first and restore it at the end.
 | 
				
			||||||
| 
						 | 
					@ -1882,7 +1921,7 @@ def test_main():
 | 
				
			||||||
                 CustomLevelsAndFiltersTest, MemoryHandlerTest,
 | 
					                 CustomLevelsAndFiltersTest, MemoryHandlerTest,
 | 
				
			||||||
                 ConfigFileTest, SocketHandlerTest, MemoryTest,
 | 
					                 ConfigFileTest, SocketHandlerTest, MemoryTest,
 | 
				
			||||||
                 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
 | 
					                 EncodingTest, WarningsTest, ConfigDictTest, ManagerTest,
 | 
				
			||||||
                 ChildLoggerTest)
 | 
					                 ChildLoggerTest, HandlerTest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    test_main()
 | 
					    test_main()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue