Skip to content

Commit 9868443

Browse files
authored
Merge pull request tornadoweb#2544 from bdarnell/httpclient-del
httpclient: Fix warning logged by sync HTTPClient destructor
2 parents 940fd87 + aa622e7 commit 9868443

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ script:
8484
# run it with nightly cpython. Coverage is very slow on pypy.
8585
- if [[ $TRAVIS_PYTHON_VERSION != nightly && $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then export RUN_COVERAGE=1; fi
8686
- if [[ "$RUN_COVERAGE" == 1 ]]; then export TARGET="-m coverage run $TARGET"; fi
87-
# See comments in tox.ini
88-
- export PYTHONWARNINGS=error,ignore:::site
87+
# See comments in tox.ini. Disabled on py35 because ResourceWarnings are too noisy there.
88+
- if [[ $TRAVIS_PYTHON_VERSION != '3.5' ]]; then export PYTHONWARNINGS=error,ignore:::site; fi
8989
- python -bb $TARGET
9090
- python -O $TARGET
9191
- LANG=C python $TARGET

tornado/httpclient.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,14 @@ def close(self) -> None:
233233
return
234234
self._closed = True
235235
if self._instance_cache is not None:
236-
if self._instance_cache.get(self.io_loop) is not self:
236+
cached_val = self._instance_cache.pop(self.io_loop, None)
237+
# If there's an object other than self in the instance
238+
# cache for our IOLoop, something has gotten mixed up. A
239+
# value of None appears to be possible when this is called
240+
# from a destructor (HTTPClient.__del__) as the weakref
241+
# gets cleared before the destructor runs.
242+
if cached_val is not None and cached_val is not self:
237243
raise RuntimeError("inconsistent AsyncHTTPClient cache")
238-
del self._instance_cache[self.io_loop]
239244

240245
def fetch(
241246
self,

tornado/test/httpclient_test.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
import threading
77
import datetime
88
from io import BytesIO
9+
import subprocess
10+
import sys
911
import time
1012
import typing # noqa: F401
1113
import unicodedata
1214
import unittest
1315

14-
from tornado.escape import utf8, native_str
16+
from tornado.escape import utf8, native_str, to_unicode
1517
from tornado import gen
1618
from tornado.httpclient import (
1719
HTTPRequest,
@@ -676,6 +678,35 @@ def test_sync_client_error(self):
676678
self.assertEqual(assertion.exception.code, 404)
677679

678680

681+
class SyncHTTPClientSubprocessTest(unittest.TestCase):
682+
def test_destructor_log(self):
683+
# Regression test for
684+
# https://github.com/tornadoweb/tornado/issues/2539
685+
#
686+
# In the past, the following program would log an
687+
# "inconsistent AsyncHTTPClient cache" error from a destructor
688+
# when the process is shutting down. The shutdown process is
689+
# subtle and I don't fully understand it; the failure does not
690+
# manifest if that lambda isn't there or is a simpler object
691+
# like an int (nor does it manifest in the tornado test suite
692+
# as a whole, which is why we use this subprocess).
693+
proc = subprocess.run(
694+
[
695+
sys.executable,
696+
"-c",
697+
"from tornado.httpclient import HTTPClient; f = lambda: None; c = HTTPClient()",
698+
],
699+
stdout=subprocess.PIPE,
700+
stderr=subprocess.STDOUT,
701+
check=True,
702+
)
703+
if proc.stdout:
704+
print("STDOUT:")
705+
print(to_unicode(proc.stdout))
706+
if proc.stdout:
707+
self.fail("subprocess produced unexpected output")
708+
709+
679710
class HTTPRequestTestCase(unittest.TestCase):
680711
def test_headers(self):
681712
request = HTTPRequest("http://example.com", headers={"foo": "bar"})

0 commit comments

Comments
 (0)