Skip to content

Commit 3d5e91b

Browse files
Merge branch 'main' into doc-improvements-sqlite3-binary
2 parents a9b10b0 + 28937d3 commit 3d5e91b

File tree

7 files changed

+73
-17
lines changed

7 files changed

+73
-17
lines changed

Doc/library/sys.monitoring.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ Registering callback functions
333333
it is unregistered and returned.
334334
Otherwise :func:`register_callback` returns ``None``.
335335

336+
.. audit-event:: sys.monitoring.register_callback func sys.monitoring.register_callback
337+
336338
Functions can be unregistered by calling
337339
``sys.monitoring.register_callback(tool_id, event, None)``.
338340

@@ -343,8 +345,6 @@ globally and locally. As such, if an event could be turned on for both global
343345
and local events by your code then the callback needs to be written to handle
344346
either trigger.
345347

346-
Registering or unregistering a callback function will generate a :func:`sys.audit` event.
347-
348348

349349
Callback function arguments
350350
'''''''''''''''''''''''''''
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import unittest
2+
3+
from test.support import threading_helper
4+
from test.support.threading_helper import run_concurrently
5+
6+
from test import test_pwd
7+
8+
9+
NTHREADS = 10
10+
11+
12+
@threading_helper.requires_working_threading()
13+
class TestPwd(unittest.TestCase):
14+
def setUp(self):
15+
self.test_pwd = test_pwd.PwdTest()
16+
17+
def test_racing_test_values(self):
18+
# test_pwd.test_values() calls pwd.getpwall() and checks the entries
19+
run_concurrently(
20+
worker_func=self.test_pwd.test_values, nthreads=NTHREADS
21+
)
22+
23+
def test_racing_test_values_extended(self):
24+
# test_pwd.test_values_extended() calls pwd.getpwall(), pwd.getpwnam(),
25+
# pwd.getpwduid() and checks the entries
26+
run_concurrently(
27+
worker_func=self.test_pwd.test_values_extended,
28+
nthreads=NTHREADS,
29+
)
30+
31+
32+
if __name__ == "__main__":
33+
unittest.main()

Lib/test/test_pydoc/test_pydoc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,11 @@ def test_apropos_with_unreadable_dir(self):
13031303
@os_helper.skip_unless_working_chmod
13041304
def test_apropos_empty_doc(self):
13051305
pkgdir = os.path.join(TESTFN, 'walkpkg')
1306+
if support.is_emscripten:
1307+
# Emscripten's readdir implementation is buggy on directories
1308+
# with read permission but no execute permission.
1309+
old_umask = os.umask(0)
1310+
self.addCleanup(os.umask, old_umask)
13061311
os.mkdir(pkgdir)
13071312
self.addCleanup(rmtree, pkgdir)
13081313
init_path = os.path.join(pkgdir, '__init__.py')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make functions in :mod:`pwd` thread-safe on the :term:`free threaded <free threading>` build.

Modules/clinic/pwdmodule.c.h

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/pwdmodule.c

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11

22
/* UNIX password file access module */
33

4-
// Need limited C API version 3.13 for PyMem_RawRealloc()
5-
#include "pyconfig.h" // Py_GIL_DISABLED
6-
#ifndef Py_GIL_DISABLED
7-
# define Py_LIMITED_API 0x030d0000
8-
#endif
9-
104
#include "Python.h"
115
#include "posixmodule.h"
126

@@ -69,6 +63,11 @@ get_pwd_state(PyObject *module)
6963

7064
static struct PyModuleDef pwdmodule;
7165

66+
/* Mutex to protect calls to getpwuid(), getpwnam(), and getpwent().
67+
* These functions return pointer to static data structure, which
68+
* may be overwritten by any subsequent calls. */
69+
static PyMutex pwd_db_mutex = {0};
70+
7271
#define DEFAULT_BUFFER_SIZE 1024
7372

7473
static PyObject *
@@ -182,9 +181,15 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)
182181

183182
Py_END_ALLOW_THREADS
184183
#else
184+
PyMutex_Lock(&pwd_db_mutex);
185+
// The getpwuid() function is not required to be thread-safe.
186+
// https://pubs.opengroup.org/onlinepubs/009604499/functions/getpwuid.html
185187
p = getpwuid(uid);
186188
#endif
187189
if (p == NULL) {
190+
#ifndef HAVE_GETPWUID_R
191+
PyMutex_Unlock(&pwd_db_mutex);
192+
#endif
188193
PyMem_RawFree(buf);
189194
if (nomem == 1) {
190195
return PyErr_NoMemory();
@@ -200,6 +205,8 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)
200205
retval = mkpwent(module, p);
201206
#ifdef HAVE_GETPWUID_R
202207
PyMem_RawFree(buf);
208+
#else
209+
PyMutex_Unlock(&pwd_db_mutex);
203210
#endif
204211
return retval;
205212
}
@@ -265,9 +272,15 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)
265272

266273
Py_END_ALLOW_THREADS
267274
#else
275+
PyMutex_Lock(&pwd_db_mutex);
276+
// The getpwnam() function is not required to be thread-safe.
277+
// https://pubs.opengroup.org/onlinepubs/009604599/functions/getpwnam.html
268278
p = getpwnam(name_chars);
269279
#endif
270280
if (p == NULL) {
281+
#ifndef HAVE_GETPWNAM_R
282+
PyMutex_Unlock(&pwd_db_mutex);
283+
#endif
271284
if (nomem == 1) {
272285
PyErr_NoMemory();
273286
}
@@ -278,6 +291,9 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)
278291
goto out;
279292
}
280293
retval = mkpwent(module, p);
294+
#ifndef HAVE_GETPWNAM_R
295+
PyMutex_Unlock(&pwd_db_mutex);
296+
#endif
281297
out:
282298
PyMem_RawFree(buf);
283299
Py_DECREF(bytes);
@@ -302,12 +318,12 @@ pwd_getpwall_impl(PyObject *module)
302318
if ((d = PyList_New(0)) == NULL)
303319
return NULL;
304320

305-
#ifdef Py_GIL_DISABLED
306-
static PyMutex getpwall_mutex = {0};
307-
PyMutex_Lock(&getpwall_mutex);
308-
#endif
321+
PyMutex_Lock(&pwd_db_mutex);
309322
int failure = 0;
310323
PyObject *v = NULL;
324+
// The setpwent(), getpwent() and endpwent() functions are not required to
325+
// be thread-safe.
326+
// https://pubs.opengroup.org/onlinepubs/009696799/functions/setpwent.html
311327
setpwent();
312328
while ((p = getpwent()) != NULL) {
313329
v = mkpwent(module, p);
@@ -321,9 +337,7 @@ pwd_getpwall_impl(PyObject *module)
321337

322338
done:
323339
endpwent();
324-
#ifdef Py_GIL_DISABLED
325-
PyMutex_Unlock(&getpwall_mutex);
326-
#endif
340+
PyMutex_Unlock(&pwd_db_mutex);
327341
if (failure) {
328342
Py_XDECREF(v);
329343
Py_CLEAR(d);

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ Python/sysmodule.c - _preinit_xoptions -
168168
Modules/faulthandler.c faulthandler_dump_traceback reentrant -
169169
Modules/faulthandler.c faulthandler_dump_c_stack reentrant -
170170
Modules/grpmodule.c - group_db_mutex -
171+
Modules/pwdmodule.c - pwd_db_mutex -
171172
Python/pylifecycle.c _Py_FatalErrorFormat reentrant -
172173
Python/pylifecycle.c fatal_error reentrant -
173174

0 commit comments

Comments
 (0)