summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2024-04-02 20:35:52 (GMT)
committerGitHub <noreply@github.com>2024-04-02 20:35:52 (GMT)
commitf341d6017dd4e80509b69b5a9e2625b71b70f205 (patch)
treefee03c2fb5ec944ca20b41e5a390c73f1d03a241 /Python
parentcae4cdd07ddfcd8bcc05d683bac53815391c9907 (diff)
downloadcpython-f341d6017dd4e80509b69b5a9e2625b71b70f205.zip
cpython-f341d6017dd4e80509b69b5a9e2625b71b70f205.tar.gz
cpython-f341d6017dd4e80509b69b5a9e2625b71b70f205.tar.bz2
gh-76785: Add PyInterpreterConfig Helpers (gh-117170)
These helpers make it easier to customize and inspect the config used to initialize interpreters. This is especially valuable in our tests. I found inspiration from the PyConfig API for the PyInterpreterConfig dict conversion stuff. As part of this PR I've also added a bunch of tests.
Diffstat (limited to 'Python')
-rw-r--r--Python/config_common.h36
-rw-r--r--Python/initconfig.c25
-rw-r--r--Python/interpconfig.c266
3 files changed, 305 insertions, 22 deletions
diff --git a/Python/config_common.h b/Python/config_common.h
new file mode 100644
index 0000000..e749bd4
--- /dev/null
+++ b/Python/config_common.h
@@ -0,0 +1,36 @@
+
+static inline int
+_config_dict_get(PyObject *dict, const char *name, PyObject **p_item)
+{
+ PyObject *item;
+ if (PyDict_GetItemStringRef(dict, name, &item) < 0) {
+ return -1;
+ }
+ if (item == NULL) {
+ // We do not set an exception.
+ return -1;
+ }
+ *p_item = item;
+ return 0;
+}
+
+
+static PyObject*
+config_dict_get(PyObject *dict, const char *name)
+{
+ PyObject *item;
+ if (_config_dict_get(dict, name, &item) < 0) {
+ if (!PyErr_Occurred()) {
+ PyErr_Format(PyExc_ValueError, "missing config key: %s", name);
+ }
+ return NULL;
+ }
+ return item;
+}
+
+
+static void
+config_dict_invalid_type(const char *name)
+{
+ PyErr_Format(PyExc_TypeError, "invalid config type: %s", name);
+}
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 215d6a1..d91a819 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -24,6 +24,9 @@
# endif
#endif
+#include "config_common.h"
+
+
/* --- PyConfig spec ---------------------------------------------- */
typedef enum {
@@ -1098,21 +1101,6 @@ _PyConfig_AsDict(const PyConfig *config)
}
-static PyObject*
-config_dict_get(PyObject *dict, const char *name)
-{
- PyObject *item;
- if (PyDict_GetItemStringRef(dict, name, &item) < 0) {
- return NULL;
- }
- if (item == NULL) {
- PyErr_Format(PyExc_ValueError, "missing config key: %s", name);
- return NULL;
- }
- return item;
-}
-
-
static void
config_dict_invalid_value(const char *name)
{
@@ -1120,13 +1108,6 @@ config_dict_invalid_value(const char *name)
}
-static void
-config_dict_invalid_type(const char *name)
-{
- PyErr_Format(PyExc_TypeError, "invalid config type: %s", name);
-}
-
-
static int
config_dict_get_int(PyObject *dict, const char *name, int *result)
{
diff --git a/Python/interpconfig.c b/Python/interpconfig.c
new file mode 100644
index 0000000..419f40a
--- /dev/null
+++ b/Python/interpconfig.c
@@ -0,0 +1,266 @@
+/* PyInterpreterConfig API */
+
+#include "Python.h"
+#include "pycore_pylifecycle.h"
+
+#include <stdbool.h>
+
+#include "config_common.h"
+
+
+static const char *
+gil_flag_to_str(int flag)
+{
+ switch (flag) {
+ case PyInterpreterConfig_DEFAULT_GIL:
+ return "default";
+ case PyInterpreterConfig_SHARED_GIL:
+ return "shared";
+ case PyInterpreterConfig_OWN_GIL:
+ return "own";
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "invalid interpreter config 'gil' value");
+ return NULL;
+ }
+}
+
+static int
+gil_flag_from_str(const char *str, int *p_flag)
+{
+ int flag;
+ if (str == NULL) {
+ flag = PyInterpreterConfig_DEFAULT_GIL;
+ }
+ else if (strcmp(str, "default") == 0) {
+ flag = PyInterpreterConfig_DEFAULT_GIL;
+ }
+ else if (strcmp(str, "shared") == 0) {
+ flag = PyInterpreterConfig_SHARED_GIL;
+ }
+ else if (strcmp(str, "own") == 0) {
+ flag = PyInterpreterConfig_OWN_GIL;
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "unsupported interpreter config .gil value '%s'", str);
+ return -1;
+ }
+ *p_flag = flag;
+ return 0;
+}
+
+PyObject *
+_PyInterpreterConfig_AsDict(PyInterpreterConfig *config)
+{
+ PyObject *dict = PyDict_New();
+ if (dict == NULL) {
+ return NULL;
+ }
+
+#define ADD(NAME, OBJ) \
+ do { \
+ int res = PyDict_SetItemString(dict, NAME, (OBJ)); \
+ Py_DECREF(OBJ); \
+ if (res < 0) { \
+ goto error; \
+ } \
+ } while (0)
+#define ADD_BOOL(FIELD) \
+ ADD(#FIELD, Py_NewRef(config->FIELD ? Py_True : Py_False))
+#define ADD_STR(FIELD, STR) \
+ do { \
+ if (STR == NULL) { \
+ goto error; \
+ } \
+ PyObject *obj = PyUnicode_FromString(STR); \
+ if (obj == NULL) { \
+ goto error; \
+ } \
+ ADD(#FIELD, obj); \
+ } while (0)
+
+ ADD_BOOL(use_main_obmalloc);
+ ADD_BOOL(allow_fork);
+ ADD_BOOL(allow_exec);
+ ADD_BOOL(allow_threads);
+ ADD_BOOL(allow_daemon_threads);
+ ADD_BOOL(check_multi_interp_extensions);
+
+ ADD_STR(gil, gil_flag_to_str(config->gil));
+
+#undef ADD_STR
+#undef ADD_BOOL
+#undef ADD
+
+ return dict;
+
+error:
+ Py_DECREF(dict);
+ return NULL;
+}
+
+static int
+_config_dict_get_bool(PyObject *dict, const char *name, int *p_flag)
+{
+ PyObject *item;
+ if (_config_dict_get(dict, name, &item) < 0) {
+ return -1;
+ }
+ // For now we keep things strict, rather than using PyObject_IsTrue().
+ int flag = item == Py_True;
+ if (!flag && item != Py_False) {
+ Py_DECREF(item);
+ config_dict_invalid_type(name);
+ return -1;
+ }
+ Py_DECREF(item);
+ *p_flag = flag;
+ return 0;
+}
+
+static int
+_config_dict_copy_str(PyObject *dict, const char *name,
+ char *buf, size_t bufsize)
+{
+ PyObject *item;
+ if (_config_dict_get(dict, name, &item) < 0) {
+ return -1;
+ }
+ if (!PyUnicode_Check(item)) {
+ Py_DECREF(item);
+ config_dict_invalid_type(name);
+ return -1;
+ }
+ strncpy(buf, PyUnicode_AsUTF8(item), bufsize-1);
+ buf[bufsize-1] = '\0';
+ Py_DECREF(item);
+ return 0;
+}
+
+static int
+interp_config_from_dict(PyObject *origdict, PyInterpreterConfig *config,
+ bool missing_allowed)
+{
+ PyObject *dict = PyDict_New();
+ if (dict == NULL) {
+ return -1;
+ }
+ if (PyDict_Update(dict, origdict) < 0) {
+ goto error;
+ }
+
+#define CHECK(NAME) \
+ do { \
+ if (PyErr_Occurred()) { \
+ goto error; \
+ } \
+ else { \
+ if (!missing_allowed) { \
+ (void)config_dict_get(dict, NAME); \
+ assert(PyErr_Occurred()); \
+ goto error; \
+ } \
+ } \
+ } while (0)
+#define COPY_BOOL(FIELD) \
+ do { \
+ int flag; \
+ if (_config_dict_get_bool(dict, #FIELD, &flag) < 0) { \
+ CHECK(#FIELD); \
+ } \
+ else { \
+ config->FIELD = flag; \
+ (void)PyDict_PopString(dict, #FIELD, NULL); \
+ } \
+ } while (0)
+
+ COPY_BOOL(use_main_obmalloc);
+ COPY_BOOL(allow_fork);
+ COPY_BOOL(allow_exec);
+ COPY_BOOL(allow_threads);
+ COPY_BOOL(allow_daemon_threads);
+ COPY_BOOL(check_multi_interp_extensions);
+
+ // PyInterpreterConfig.gil
+ char buf[20];
+ if (_config_dict_copy_str(dict, "gil", buf, 20) < 0) {
+ CHECK("gil");
+ }
+ else {
+ int flag;
+ if (gil_flag_from_str(buf, &flag) < 0) {
+ goto error;
+ }
+ config->gil = flag;
+ (void)PyDict_PopString(dict, "gil", NULL);
+ }
+
+#undef COPY_BOOL
+#undef CHECK
+
+ Py_ssize_t unused = PyDict_GET_SIZE(dict);
+ if (unused == 1) {
+ PyErr_Format(PyExc_ValueError,
+ "config dict has 1 extra item (%R)", dict);
+ goto error;
+ }
+ else if (unused > 0) {
+ PyErr_Format(PyExc_ValueError,
+ "config dict has %d extra items (%R)", unused, dict);
+ goto error;
+ }
+ return 0;
+
+error:
+ Py_DECREF(dict);
+ return -1;
+}
+
+int
+_PyInterpreterConfig_InitFromDict(PyInterpreterConfig *config, PyObject *dict)
+{
+ if (!PyDict_Check(dict)) {
+ PyErr_SetString(PyExc_TypeError, "dict expected");
+ return -1;
+ }
+ if (interp_config_from_dict(dict, config, false) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int
+_PyInterpreterConfig_UpdateFromDict(PyInterpreterConfig *config, PyObject *dict)
+{
+ if (!PyDict_Check(dict)) {
+ PyErr_SetString(PyExc_TypeError, "dict expected");
+ return -1;
+ }
+ if (interp_config_from_dict(dict, config, true) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int
+_PyInterpreterConfig_InitFromState(PyInterpreterConfig *config,
+ PyInterpreterState *interp)
+{
+ // Populate the config by re-constructing the values from the interpreter.
+ *config = (PyInterpreterConfig){
+#define FLAG(flag) \
+ (interp->feature_flags & Py_RTFLAGS_ ## flag)
+ .use_main_obmalloc = FLAG(USE_MAIN_OBMALLOC),
+ .allow_fork = FLAG(FORK),
+ .allow_exec = FLAG(EXEC),
+ .allow_threads = FLAG(THREADS),
+ .allow_daemon_threads = FLAG(DAEMON_THREADS),
+ .check_multi_interp_extensions = FLAG(MULTI_INTERP_EXTENSIONS),
+#undef FLAG
+ .gil = interp->ceval.own_gil
+ ? PyInterpreterConfig_OWN_GIL
+ : PyInterpreterConfig_SHARED_GIL,
+ };
+ return 0;
+}