Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Doc/library/sys.monitoring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ Registering callback functions
it is unregistered and returned.
Otherwise :func:`register_callback` returns ``None``.

.. audit-event:: sys.monitoring.register_callback func sys.monitoring.register_callback

Functions can be unregistered by calling
``sys.monitoring.register_callback(tool_id, event, None)``.

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

Registering or unregistering a callback function will generate a :func:`sys.audit` event.


Callback function arguments
'''''''''''''''''''''''''''
Expand Down
33 changes: 33 additions & 0 deletions Lib/test/test_free_threading/test_pwd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import unittest

from test.support import threading_helper
from test.support.threading_helper import run_concurrently

from test import test_pwd


NTHREADS = 10


@threading_helper.requires_working_threading()
class TestPwd(unittest.TestCase):
def setUp(self):
self.test_pwd = test_pwd.PwdTest()

def test_racing_test_values(self):
# test_pwd.test_values() calls pwd.getpwall() and checks the entries
run_concurrently(
worker_func=self.test_pwd.test_values, nthreads=NTHREADS
)

def test_racing_test_values_extended(self):
# test_pwd.test_values_extended() calls pwd.getpwall(), pwd.getpwnam(),
# pwd.getpwduid() and checks the entries
run_concurrently(
worker_func=self.test_pwd.test_values_extended,
nthreads=NTHREADS,
)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make functions in :mod:`pwd` thread-safe on the :term:`free threaded <free threading>` build.
6 changes: 4 additions & 2 deletions Modules/clinic/pwdmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 27 additions & 13 deletions Modules/pwdmodule.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@

/* UNIX password file access module */

// Need limited C API version 3.13 for PyMem_RawRealloc()
#include "pyconfig.h" // Py_GIL_DISABLED
#ifndef Py_GIL_DISABLED
# define Py_LIMITED_API 0x030d0000
#endif

#include "Python.h"
#include "posixmodule.h"

Expand Down Expand Up @@ -69,6 +63,11 @@ get_pwd_state(PyObject *module)

static struct PyModuleDef pwdmodule;

/* Mutex to protect calls to getpwuid(), getpwnam(), and getpwent().
* These functions return pointer to static data structure, which
* may be overwritten by any subsequent calls. */
static PyMutex pwd_db_mutex = {0};

#define DEFAULT_BUFFER_SIZE 1024

static PyObject *
Expand Down Expand Up @@ -182,9 +181,15 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)

Py_END_ALLOW_THREADS
#else
PyMutex_Lock(&pwd_db_mutex);
// The getpwuid() function is not required to be thread-safe.
// https://pubs.opengroup.org/onlinepubs/009604499/functions/getpwuid.html
p = getpwuid(uid);
#endif
if (p == NULL) {
#ifndef HAVE_GETPWUID_R
PyMutex_Unlock(&pwd_db_mutex);
#endif
PyMem_RawFree(buf);
if (nomem == 1) {
return PyErr_NoMemory();
Expand All @@ -200,6 +205,8 @@ pwd_getpwuid(PyObject *module, PyObject *uidobj)
retval = mkpwent(module, p);
#ifdef HAVE_GETPWUID_R
PyMem_RawFree(buf);
#else
PyMutex_Unlock(&pwd_db_mutex);
#endif
return retval;
}
Expand Down Expand Up @@ -265,9 +272,15 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)

Py_END_ALLOW_THREADS
#else
PyMutex_Lock(&pwd_db_mutex);
// The getpwnam() function is not required to be thread-safe.
// https://pubs.opengroup.org/onlinepubs/009604599/functions/getpwnam.html
p = getpwnam(name_chars);
#endif
if (p == NULL) {
#ifndef HAVE_GETPWNAM_R
PyMutex_Unlock(&pwd_db_mutex);
#endif
if (nomem == 1) {
PyErr_NoMemory();
}
Expand All @@ -278,6 +291,9 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name)
goto out;
}
retval = mkpwent(module, p);
#ifndef HAVE_GETPWNAM_R
PyMutex_Unlock(&pwd_db_mutex);
#endif
out:
PyMem_RawFree(buf);
Py_DECREF(bytes);
Expand All @@ -302,12 +318,12 @@ pwd_getpwall_impl(PyObject *module)
if ((d = PyList_New(0)) == NULL)
return NULL;

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

done:
endpwent();
#ifdef Py_GIL_DISABLED
PyMutex_Unlock(&getpwall_mutex);
#endif
PyMutex_Unlock(&pwd_db_mutex);
if (failure) {
Py_XDECREF(v);
Py_CLEAR(d);
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/ignored.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ Python/sysmodule.c - _preinit_xoptions -
Modules/faulthandler.c faulthandler_dump_traceback reentrant -
Modules/faulthandler.c faulthandler_dump_c_stack reentrant -
Modules/grpmodule.c - group_db_mutex -
Modules/pwdmodule.c - pwd_db_mutex -
Python/pylifecycle.c _Py_FatalErrorFormat reentrant -
Python/pylifecycle.c fatal_error reentrant -

Expand Down
Loading