Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

MAINT: use an atomic load/store and a mutex to initialize the argparse and runtime import caches #26780

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
merged 8 commits into from
Jul 12, 2024
Prev Previous commit
MAINT: respond to review comments
  • Loading branch information
ngoldbaum committed Jul 9, 2024
commit dabfe59827e89fc94636e6218cdf9024e0fa8c0d
2 changes: 1 addition & 1 deletion 2 numpy/_core/src/common/npy_argparse.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ NPY_NO_EXPORT int init_argparse_mutex(void);
* The sole purpose of this macro is to hide the argument parsing cache.
ngoldbaum marked this conversation as resolved.
Show resolved Hide resolved
* Since this cache must be static, this also removes a source of error.
*/
#define NPY_PREPARE_ARGPARSER static _NpyArgParserCache __argparse_cache = {-1}
#define NPY_PREPARE_ARGPARSER static _NpyArgParserCache __argparse_cache;

/**
* Macro to help with argument parsing.
Expand Down
10 changes: 6 additions & 4 deletions 10 numpy/_core/src/common/npy_import.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,16 @@ npy_import(const char *module, const char *attr)
static inline int
npy_cache_import_runtime(const char *module, const char *attr, PyObject **obj) {
if (!npy_atomic_load_ptr(obj)) {
PyObject* value = npy_import(module, attr);
if (value == NULL) {
return -1;
}
PyThread_acquire_lock(npy_runtime_imports.import_mutex, WAIT_LOCK);
if (!npy_atomic_load_ptr(obj)) {
npy_atomic_store_ptr(obj, npy_import(module, attr));
if (obj == NULL) {
return -1;
}
npy_atomic_store_ptr(obj, Py_NewRef(value));
}
PyThread_release_lock(npy_runtime_imports.import_mutex);
Copy link
Member

Choose a reason for hiding this comment

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

Move the release before the error check, or we will dead-lock on error!

Copy link

@colesbury colesbury Jul 9, 2024

Choose a reason for hiding this comment

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

Good catch! Also, the obj == NULL check doesn't make sense -- it would need to be *obj == NULL to check the result.

But I think it would be better not to hold the lock around the npy_import:

  • Imports in general can run arbitrary code, which may reentrantly trigger npy_cache_import_runtime.
  • Imports (i.e., PyImport_ImportModule) are already thread-safe internally

So I'd suggest writing this as:

if (!npy_atomic_load_ptr(obj)) {
    PyObject *value = npy_import(module, attr);
    if (value == NULL) {
        return -1;
    }
    PyThread_acquire_lock(npy_runtime_imports.import_mutex, WAIT_LOCK);
    if (!npy_atomic_load_ptr(obj)) {
        npy_atomic_store_ptr(obj, Py_NewRef(value));
    }
    PyThread_release_lock(npy_runtime_imports.import_mutex);
    Py_DECREF(value);
}

Or you can get rid of the lock if you implement compare-exchange:

if (!npy_atomic_load_ptr(obj)) {
    PyObject *value = npy_import(module, attr);
    if (value == NULL) {
        return -1;
    }
    PyObject *exepected = NULL;
    if (!npy_atomic_compare_exchange_ptr(obj, &expected, value)) {
        Py_DECREF(value);
    }
}

Copy link
Member Author

@ngoldbaum ngoldbaum Jul 9, 2024

Choose a reason for hiding this comment

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

Thanks! I kept the lock since the next iteration will use PyMutex and I'd like to make sure this gets in for NumPy 2.1. Thankfully deadsnakes was updated over the weekend so we can test against a version with a public PyMutex now.

Py_DECREF(value);
}
return 0;
}
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.