Skip to content

bpo-33978: close resources before deletion of logging handlers #8008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

tirkarthi
Copy link
Member

@tirkarthi tirkarthi commented Jun 29, 2018

Close the file handlers before deleting the logging handlers so that there is no resource warning. Since the handlers are a weakreference I tried to close them by calling self.close on __del__ but not all handlers have the lock attribute which resulted in AttributeError. shutdown handles the AttributeError and other scenarios but it will be a much cleaner if we close the handlers during weakref deletion instead of calling shudown which might miss some places.

Additionally shutdown is also registered at exit which should clean up the current handlers I hope but doesn't handle the ones that were deleted.

Attached tests and a NEWS entry. Kindly correct me if I am wrong on the approach since this is my first non-easy PR and I would like some help on this fix hoping the fix will not be much complex.

Thanks

https://bugs.python.org/issue33978

@tirkarthi
Copy link
Member Author

Windows builds for fileConfig fails with unicodeescape error and the one for dictConfig fails with permission error to open the file though tests pass on Linux and MacOS. I don't have a windows machine to test this and any pointers will be helpful in fixing the tests.

@@ -74,6 +74,10 @@ def fileConfig(fname, defaults=None, disable_existing_loggers=True):
logging._acquireLock()
try:
logging._handlers.clear()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 3 lines

logging._handlers.clear()
logging.shutdown(logging._handlerList[:])
del logging._handlerList[:]

are now in two places - at line 76 and line 531. I suggest adding a module-level function _clearExistingHandlers() which does these operations, along with two calls to them from those locations.

Also, logging.shutdown() takes _handlerList as a default argument, so it is not necessary to pass the argument to it.

@@ -0,0 +1,2 @@
Close resources before deleting logging handlers. Patch by Karthikeyan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to "Closed existing logging handlers before reconfiguration via fileConfig and dictConfig".

keys=root

[handlers]
keys=file_handler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could just name as file rather than file_handler. The section below then becomes [handler_file] rather than [handler_file_handler].

def test_config8_ok(self):
with self.check_no_resource_warning():
with tempfile.NamedTemporaryFile() as f:
self.apply_config(self.config8.format(tempfile=f.name))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do the formatting just once to a local string, and then use that.

@@ -1442,6 +1464,12 @@ def test_config7_ok(self):
# Original logger output is empty.
self.assert_log_lines([])

def test_config8_ok(self):
with self.check_no_resource_warning():
with tempfile.NamedTemporaryFile() as f:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the approach used elsewhere in this file with fd, fn = tempfile.mkstemp(".log", "test_logging-X-") followed by closing the fd. This allows tracing of where the temp file came from, in the odd case where it isn't deleted for some reason. It should also ensure that the file is writable, or fail at this point rather than later.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

And if you don't make the requested changes, you will be poked with soft cushions!

@tirkarthi
Copy link
Member Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@vsajip: please review the changes made to this pull request.

@tirkarthi
Copy link
Member Author

The windows build fails due to the below :

  1. With respect fileConfig test formatting the string results in path having \Users and hence a unicodeescape error is produced. I have tried raw string suggested in SO answer but as noted in the comments it seems not to work for ''' quoted strings and I have to manually escape \ myself as per the comment.

  2. With respect to dictConfig I can see two errors. One in VSTS there is permissionDenied error which I hope is due to permission error but there is a comment in f315df3 that This exception is thrown when a directory with the chosen name already exists on windows. . In AppVeyor I cannot see the error and hence the file is created successfully but it seems that dicConfig uses the file handler and hence os.unlink(fn) fails since windows doesn't allow deleting files already used by the process. But trying to close the handler before calling os.unlink kind of removes the point of testing the resource leak.

os.remove

On Windows, attempting to remove a file that is in use causes an exception to be raised; on Unix, the directory entry is removed but the storage allocated to the file is not made available until the original file is no longer in use.

I don't have a windows machine to test this and if mkstemp failure is due to the name being already present then I don't know why I could see it in other tests like test_tempfile.py. Perhaps there is a better way to test this I am not aware of?

Thanks

@tirkarthi
Copy link
Member Author

@vsajip I think I have fixed the issues with respect to windows.

  1. I have added code to replace '\' with '\\' as I came across another gotcha where using raw string makes it look like it's escaping the backslash but actually only escapes while printing and the backslashes are not escaped in the string.

  2. The case where deleting a file while it's used by another process in windows I have added a cleanup method like other tests in the file where the handler is closed and then the actual file is deleted safely using os.remove. I have verified that manually doing it doesn't affect the test cases for warnings by removing the _cleanExistingHandlers call that causes the warnings to happen in the tests.

On an additional note I think this bug is also present in Python 2.7 and I hope a backport is feasible. Ref :

del logging._handlerList[:]

Thanks

@vsajip
Copy link
Member

vsajip commented Jul 2, 2018

The case where deleting a file while it's used by another process in windows I have added a cleanup method like other tests in the file where the handler is closed and then the actual file is deleted safely using os.remove.

Removing the file shouldn't be needed, just closing it should be enough. If you can delete the file, it means it can't be held open in another process. I assume you're deleting it solely because it's a temp file that's no longer needed.

@tirkarthi
Copy link
Member Author

Sorry, to be clear I am not deleting the file in logging handler I am talking about the test case related scenario in mkstemp where the file is not deleted automatically and the user has to delete the file on his own. Since the file is used only by the single test process then I think the file has to be deleted in the test case at the end in the cleanup method. Correct me if I am wrong or misunderstood your reply.

Thanks

@vsajip vsajip merged commit 087570a into python:master Jul 2, 2018
@vsajip
Copy link
Member

vsajip commented Jul 2, 2018

Thanks for your work on this.

@tirkarthi
Copy link
Member Author

Sure, thanks much for your review comments and guidance. Does this need to be backported to 3.7, 3.6 and 2.7?

@miss-islington
Copy link
Contributor

Thanks @tirkarthi for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7.
🐍🍒⛏🤖

@bedevere-bot
Copy link

GH-8044 is a backport of this pull request to the 3.7 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 2, 2018
…pythonGH-8008)

(cherry picked from commit 087570a)

Co-authored-by: Xtreak <tirkarthi@users.noreply.github.com>
@miss-islington
Copy link
Contributor

Thanks @tirkarthi for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.6.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Thanks @tirkarthi for the PR, and @vsajip for merging it 🌮🎉.. I'm working now to backport this PR to: 3.7.
🐍🍒⛏🤖

@miss-islington
Copy link
Contributor

Sorry, @tirkarthi and @vsajip, I could not cleanly backport this to 3.6 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker 087570af6d5d39b51bdd5e660a53903960e58678 3.6

tirkarthi added a commit to tirkarthi/cpython that referenced this pull request Jul 2, 2018
…ation. (pythonGH-8008).

(cherry picked from commit 087570a)

Co-authored-by: Xtreak <tirkarthi@users.noreply.github.com>
@bedevere-bot
Copy link

GH-8045 is a backport of this pull request to the 3.6 branch.

vsajip pushed a commit that referenced this pull request Jul 2, 2018
…GH-8008) (GH-8044)

(cherry picked from commit 087570a)

Co-authored-by: Xtreak <tirkarthi@users.noreply.github.com>
vsajip pushed a commit that referenced this pull request Jul 2, 2018
…ation. (GH-8008). (GH-8045)

(cherry picked from commit 087570a)

Co-authored-by: Xtreak <tirkarthi@users.noreply.github.com>
@flyte
Copy link

flyte commented Feb 11, 2021

Close the file handlers before deleting the logging handlers so that there is no resource warning.

Don't you only want to do this if disable_existing_loggers == True?

This change is causing the socket on configured SysLogHandlers to be closed, throwing an error when they're used, for example:

import logging.config
import socket
from logging.handlers import SysLogHandler

log = logging.getLogger("logtest")
syslog_handler = SysLogHandler(("127.0.0.1", 12345), socktype=socket.SOCK_DGRAM)
log.addHandler(syslog_handler)

log.warning("Works")

logging.config.dictConfig(
    {
        "version": 1,
        "disable_existing_loggers": False,
    }
)

log.warning("Breaks")

Causes:

--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib/python3.8/logging/handlers.py", line 940, in emit
    self.socket.sendto(msg, self.address)
OSError: [Errno 9] Bad file descriptor
Call stack:
  File "/home/flyte/workspaces/python/test.py", line 18, in <module>
    log.warning("Breaks")

It's a valid case where multiple calls to dictConfig are legitimately done by separate parts of a system (gunicorn and Django for example), so one call (by Django) should not affect the existing resources configured by the other (gunicorn).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy