Skip to content

Commit 8fad167

Browse files
committed
Issue #22166: clear codec caches in test_codecs
1 parent b85a976 commit 8fad167

File tree

5 files changed

+102
-0
lines changed

5 files changed

+102
-0
lines changed

Include/codecs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ PyAPI_FUNC(int) PyCodec_Register(
4949
PyAPI_FUNC(PyObject *) _PyCodec_Lookup(
5050
const char *encoding
5151
);
52+
53+
PyAPI_FUNC(int) _PyCodec_Forget(
54+
const char *encoding
55+
);
5256
#endif
5357

5458
/* Codec registry encoding check API.

Lib/test/test_codecs.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2578,6 +2578,14 @@ def _get_test_codec(codec_name):
25782578
return _TEST_CODECS.get(codec_name)
25792579
codecs.register(_get_test_codec) # Returns None, not usable as a decorator
25802580

2581+
try:
2582+
# Issue #22166: Also need to clear the internal cache in CPython
2583+
from _codecs import _forget_codec
2584+
except ImportError:
2585+
def _forget_codec(codec_name):
2586+
pass
2587+
2588+
25812589
class ExceptionChainingTest(unittest.TestCase):
25822590

25832591
def setUp(self):
@@ -2603,6 +2611,12 @@ def setUp(self):
26032611

26042612
def tearDown(self):
26052613
_TEST_CODECS.pop(self.codec_name, None)
2614+
# Issue #22166: Also pop from caches to avoid appearance of ref leaks
2615+
encodings._cache.pop(self.codec_name, None)
2616+
try:
2617+
_forget_codec(self.codec_name)
2618+
except KeyError:
2619+
pass
26062620

26072621
def set_codec(self, encode, decode):
26082622
codec_info = codecs.CodecInfo(encode, decode,

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,10 @@ IDLE
344344
Tests
345345
-----
346346

347+
- Issue #22166: with the assistance of a new internal _codecs._forget_codec
348+
helping function, test_codecs now clears the encoding caches to avoid the
349+
appearance of a reference leak
350+
347351
- Issue #22236: Tkinter tests now don't reuse default root window. New root
348352
window is created for every test class.
349353

Modules/_codecsmodule.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ Copyright (c) Corporation for National Research Initiatives.
4242
#include <windows.h>
4343
#endif
4444

45+
/*[clinic input]
46+
module _codecs
47+
[clinic start generated code]*/
48+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e1390e3da3cb9deb]*/
49+
50+
4551
/* --- Registry ----------------------------------------------------------- */
4652

4753
PyDoc_STRVAR(register__doc__,
@@ -134,6 +140,53 @@ codec_decode(PyObject *self, PyObject *args)
134140

135141
/* --- Helpers ------------------------------------------------------------ */
136142

143+
/*[clinic input]
144+
_codecs._forget_codec
145+
146+
encoding: str
147+
/
148+
149+
Purge the named codec from the internal codec lookup cache
150+
[clinic start generated code]*/
151+
152+
PyDoc_STRVAR(_codecs__forget_codec__doc__,
153+
"_forget_codec($module, encoding, /)\n"
154+
"--\n"
155+
"\n"
156+
"Purge the named codec from the internal codec lookup cache");
157+
158+
#define _CODECS__FORGET_CODEC_METHODDEF \
159+
{"_forget_codec", (PyCFunction)_codecs__forget_codec, METH_VARARGS, _codecs__forget_codec__doc__},
160+
161+
static PyObject *
162+
_codecs__forget_codec_impl(PyModuleDef *module, const char *encoding);
163+
164+
static PyObject *
165+
_codecs__forget_codec(PyModuleDef *module, PyObject *args)
166+
{
167+
PyObject *return_value = NULL;
168+
const char *encoding;
169+
170+
if (!PyArg_ParseTuple(args,
171+
"s:_forget_codec",
172+
&encoding))
173+
goto exit;
174+
return_value = _codecs__forget_codec_impl(module, encoding);
175+
176+
exit:
177+
return return_value;
178+
}
179+
180+
static PyObject *
181+
_codecs__forget_codec_impl(PyModuleDef *module, const char *encoding)
182+
/*[clinic end generated code: output=a75e631591702a5c input=18d5d92d0e386c38]*/
183+
{
184+
if (_PyCodec_Forget(encoding) < 0) {
185+
return NULL;
186+
};
187+
Py_RETURN_NONE;
188+
}
189+
137190
static
138191
PyObject *codec_tuple(PyObject *unicode,
139192
Py_ssize_t len)
@@ -1168,6 +1221,7 @@ static PyMethodDef _codecs_functions[] = {
11681221
register_error__doc__},
11691222
{"lookup_error", lookup_error, METH_VARARGS,
11701223
lookup_error__doc__},
1224+
_CODECS__FORGET_CODEC_METHODDEF
11711225
{NULL, NULL} /* sentinel */
11721226
};
11731227

Python/codecs.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,32 @@ PyObject *_PyCodec_Lookup(const char *encoding)
185185
return NULL;
186186
}
187187

188+
int _PyCodec_Forget(const char *encoding)
189+
{
190+
PyInterpreterState *interp;
191+
PyObject *v;
192+
int result;
193+
194+
interp = PyThreadState_GET()->interp;
195+
if (interp->codec_search_path == NULL) {
196+
return -1;
197+
}
198+
199+
/* Convert the encoding to a normalized Python string: all
200+
characters are converted to lower case, spaces and hyphens are
201+
replaced with underscores. */
202+
v = normalizestring(encoding);
203+
if (v == NULL) {
204+
return -1;
205+
}
206+
207+
/* Drop the named codec from the internal cache */
208+
result = PyDict_DelItem(interp->codec_search_cache, v);
209+
Py_DECREF(v);
210+
211+
return result;
212+
}
213+
188214
/* Codec registry encoding check API. */
189215

190216
int PyCodec_KnownEncoding(const char *encoding)

0 commit comments

Comments
 (0)