diff options
author | Garrett Regier <garrettregier@gmail.com> | 2015-01-05 07:39:33 -0800 |
---|---|---|
committer | Garrett Regier <garrettregier@gmail.com> | 2015-01-20 01:23:07 -0800 |
commit | 83a44f3c0e610814923a29c419b4a1ba16fbee08 (patch) | |
tree | 8a557275b482ecbae25a6295ecc824c2cf204aa3 /loaders/python | |
parent | b95aebb38b317c9a33db80e0c0caee7accb60a2b (diff) | |
download | libpeas-83a44f3c0e610814923a29c419b4a1ba16fbee08.tar.gz |
Use Python to implement the plugin loader's logic
This allows us to avoid the CPython API and have a
more understandable implementation.
https://bugzilla.gnome.org/show_bug.cgi?id=742349
Diffstat (limited to 'loaders/python')
-rw-r--r-- | loaders/python/peas-plugin-loader-python.c | 394 | ||||
-rw-r--r-- | loaders/python/peas-python-internal.c | 196 | ||||
-rw-r--r-- | loaders/python/peas-python-internal.h | 16 | ||||
-rw-r--r-- | loaders/python/peas-python-internal.py | 221 |
4 files changed, 379 insertions, 448 deletions
diff --git a/loaders/python/peas-plugin-loader-python.c b/loaders/python/peas-plugin-loader-python.c index ce77ed6..7b6d1e9 100644 --- a/loaders/python/peas-plugin-loader-python.c +++ b/loaders/python/peas-plugin-loader-python.c @@ -33,23 +33,15 @@ */ #undef _POSIX_C_SOURCE #include <pygobject.h> -#include <Python.h> -#include <signal.h> - -#if PY_VERSION_HEX < 0x02050000 -typedef int Py_ssize_t; -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif typedef struct { - GHashTable *loaded_plugins; + PeasPythonInternal *internal; + PyThreadState *py_thread_state; + guint n_loaded_plugins; - guint idle_gc; + guint init_failed : 1; guint must_finalize_python : 1; - PyThreadState *py_thread_state; - PeasPythonInternal *internal; } PeasPluginLoaderPythonPrivate; G_DEFINE_TYPE_WITH_PRIVATE (PeasPluginLoaderPython, @@ -70,102 +62,33 @@ peas_register_types (PeasObjectModule *module) PEAS_TYPE_PLUGIN_LOADER_PYTHON); } -/* NOTE: This must be called with the GIL held */ -static PyTypeObject * -find_python_extension_type (GType exten_type, - PyObject *pymodule) +static GType +find_python_extension_type (PeasPluginLoaderPython *pyloader, + GType exten_type, + PyObject *pymodule) { - PyObject *pygtype, *pytype; - PyObject *locals, *key, *value; - Py_ssize_t pos = 0; + PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader); + PyObject *pyexten_type, *pytype; + GType the_type = G_TYPE_INVALID; - locals = PyModule_GetDict (pymodule); + pyexten_type = pyg_type_wrapper_new (exten_type); - pygtype = pyg_type_wrapper_new (exten_type); - pytype = PyObject_GetAttrString (pygtype, "pytype"); - g_warn_if_fail (pytype != NULL); + pytype = peas_python_internal_call (priv->internal, + "find_extension_type", + &PyType_Type, "(OO)", + pyexten_type, pymodule); + Py_DECREF (pyexten_type); - if (pytype != NULL && pytype != Py_None) + if (pytype != NULL) { - while (PyDict_Next (locals, &pos, &key, &value)) - { - if (!PyType_Check (value)) - continue; - - switch (PyObject_IsSubclass (value, pytype)) - { - case 1: - Py_DECREF (pytype); - Py_DECREF (pygtype); - return (PyTypeObject *) value; - case 0: - continue; - case -1: - default: - PyErr_Print (); - continue; - } - } - } - - Py_DECREF (pytype); - Py_DECREF (pygtype); - - return NULL; -} - -/* C equivalent of - * import sys - * sys.path.insert(0, module_path) - */ -/* NOTE: This must be called with the GIL held */ -static gboolean -add_module_path (PeasPluginLoaderPython *pyloader, - const gchar *module_path) -{ - PyObject *pathlist, *pathstring; - gboolean success = TRUE; + the_type = pyg_type_from_object (pytype); + Py_DECREF (pytype); - g_return_val_if_fail (PEAS_IS_PLUGIN_LOADER_PYTHON (pyloader), FALSE); - g_return_val_if_fail (module_path != NULL, FALSE); - - pathlist = PySys_GetObject ((char *) "path"); - if (pathlist == NULL) - return FALSE; - -#if PY_VERSION_HEX < 0x03000000 - pathstring = PyString_FromString (module_path); -#else - pathstring = PyUnicode_FromString (module_path); -#endif - - if (pathstring == NULL) - return FALSE; - - switch (PySequence_Contains (pathlist, pathstring)) - { - case 0: - success = PyList_Insert (pathlist, 0, pathstring) >= 0; - break; - case 1: - break; - case -1: - default: - success = FALSE; - break; + g_return_val_if_fail (g_type_is_a (the_type, exten_type), + G_TYPE_INVALID); } - Py_DECREF (pathstring); - return success; -} - -/* NOTE: This must be called with the GIL held */ -static void -destroy_python_info (gpointer data) -{ - PyObject *pymodule = data; - - Py_XDECREF (pymodule); + return the_type; } static gboolean @@ -173,14 +96,15 @@ peas_plugin_loader_python_provides_extension (PeasPluginLoader *loader, PeasPluginInfo *info, GType exten_type) { + PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader); PyObject *pymodule = info->loader_data; - PyTypeObject *extension_type; + GType the_type; PyGILState_STATE state = PyGILState_Ensure (); - extension_type = find_python_extension_type (exten_type, pymodule); + the_type = find_python_extension_type (pyloader, exten_type, pymodule); PyGILState_Release (state); - return extension_type != NULL; + return the_type != G_TYPE_INVALID; } static PeasExtension * @@ -190,33 +114,20 @@ peas_plugin_loader_python_create_extension (PeasPluginLoader *loader, guint n_parameters, GParameter *parameters) { + PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader); PyObject *pymodule = info->loader_data; - PyTypeObject *pytype; GType the_type; GObject *object = NULL; PyObject *pyobject; PyObject *pyplinfo; PyGILState_STATE state = PyGILState_Ensure (); - pytype = find_python_extension_type (exten_type, pymodule); - - if (pytype == NULL) - goto out; - - the_type = pyg_type_from_object ((PyObject *) pytype); - + the_type = find_python_extension_type (pyloader, exten_type, pymodule); if (the_type == G_TYPE_INVALID) goto out; - if (!g_type_is_a (the_type, exten_type)) - { - g_warn_if_fail (g_type_is_a (the_type, exten_type)); - goto out; - } - object = g_object_newv (the_type, n_parameters, parameters); - - if (!object) + if (object == NULL) goto out; /* We have to remember which interface we are instantiating @@ -229,7 +140,7 @@ peas_plugin_loader_python_create_extension (PeasPluginLoader *loader, pyplinfo = pyg_boxed_new (PEAS_TYPE_PLUGIN_INFO, info, TRUE, TRUE); /* Set the plugin info as an attribute of the instance */ - if (PyObject_SetAttrString (pyobject, "plugin_info", pyplinfo) == -1) + if (PyObject_SetAttrString (pyobject, "plugin_info", pyplinfo) != 0) { g_warning ("Failed to set 'plugin_info' for '%s'", g_type_name (the_type)); @@ -255,51 +166,17 @@ peas_plugin_loader_python_load (PeasPluginLoader *loader, { PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader); PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader); + const gchar *module_dir, *module_name; + PyObject *pymodule; PyGILState_STATE state = PyGILState_Ensure (); - PyObject *pymodule = NULL; - if (!g_hash_table_lookup_extended (priv->loaded_plugins, - info->filename, - NULL, (gpointer *) &pymodule)) - { - const gchar *module_dir, *module_name; - - module_dir = peas_plugin_info_get_module_dir (info); - module_name = peas_plugin_info_get_module_name (info); - - /* We don't support multiple Python interpreter states */ - if (PyDict_GetItemString (PyImport_GetModuleDict (), module_name)) - { - g_warning ("Error loading plugin '%s': " - "module name '%s' has already been used", - info->filename, module_name); - } - else if (!add_module_path (pyloader, module_dir)) - { - g_warning ("Error loading plugin '%s': " - "failed to add module path '%s'", - module_name, module_dir); - } - else - { - PyObject *fromlist; - - /* We need a fromlist to be able to - * import modules with a '.' in the name - */ - fromlist = PyTuple_New (0); - - pymodule = PyImport_ImportModuleEx ((gchar *) module_name, - NULL, NULL, fromlist); - Py_DECREF (fromlist); - } - - if (PyErr_Occurred ()) - PyErr_Print (); + module_dir = peas_plugin_info_get_module_dir (info); + module_name = peas_plugin_info_get_module_name (info); - g_hash_table_insert (priv->loaded_plugins, - g_strdup (info->filename), pymodule); - } + pymodule = peas_python_internal_call (priv->internal, "load", + &PyModule_Type, "(sss)", + info->filename, + module_dir, module_name); if (pymodule != NULL) { @@ -319,33 +196,17 @@ peas_plugin_loader_python_unload (PeasPluginLoader *loader, PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader); PyGILState_STATE state = PyGILState_Ensure (); - /* Only unref the Python module when the - * loader is finalized as Python keeps a ref anyways - */ - /* We have to use this as a hook as the * loader will not be finalized by applications */ if (--priv->n_loaded_plugins == 0) - peas_python_internal_call (priv->internal, "all_plugins_unloaded"); - - info->loader_data = NULL; - PyGILState_Release (state); -} - -static gboolean -run_gc (PeasPluginLoaderPython *pyloader) -{ - PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader); - PyGILState_STATE state = PyGILState_Ensure (); - - while (PyGC_Collect ()) - ; - - priv->idle_gc = 0; + { + peas_python_internal_call (priv->internal, "all_plugins_unloaded", + NULL, NULL); + } + Py_CLEAR (info->loader_data); PyGILState_Release (state); - return FALSE; } static void @@ -355,64 +216,11 @@ peas_plugin_loader_python_garbage_collect (PeasPluginLoader *loader) PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader); PyGILState_STATE state = PyGILState_Ensure (); - /* We both run the GC right now and we schedule - * a further collection in the main loop. - */ - while (PyGC_Collect ()) - ; - - if (priv->idle_gc == 0) - { - priv->idle_gc = g_idle_add ((GSourceFunc) run_gc, pyloader); - g_source_set_name_by_id (priv->idle_gc, "[libpeas] run_gc"); - } + peas_python_internal_call (priv->internal, "garbage_collect", NULL, NULL); PyGILState_Release (state); } -#if PY_VERSION_HEX >= 0x03000000 -static wchar_t * -peas_wchar_from_str (const gchar *str) -{ - wchar_t *outbuf; - gsize argsize, count; - - argsize = mbstowcs (NULL, str, 0); - if (argsize == (gsize)-1) - { - g_warning ("Could not convert argument to wchar_t string."); - return NULL; - } - - outbuf = g_new (wchar_t, argsize + 1); - count = mbstowcs (outbuf, str, argsize + 1); - if (count == (gsize)-1) - { - g_warning ("Could not convert argument to wchar_t string."); - return NULL; - } - - return outbuf; -} -#endif - -#ifdef HAVE_SIGACTION -static void -default_sigint (int sig) -{ - struct sigaction sigint; - - /* Invoke default sigint handler */ - sigint.sa_handler = SIG_DFL; - sigint.sa_flags = 0; - sigemptyset (&sigint.sa_mask); - - sigaction (SIGINT, &sigint, NULL); - - raise (SIGINT); -} -#endif - static gboolean peas_plugin_loader_python_initialize (PeasPluginLoader *loader) { @@ -420,23 +228,11 @@ peas_plugin_loader_python_initialize (PeasPluginLoader *loader) PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader); PyGILState_STATE state = 0; long hexversion; - PyObject *gettext, *result; - const gchar *prgname; -#if PY_VERSION_HEX < 0x03000000 - const char *argv[] = { NULL, NULL }; -#else - wchar_t *argv[] = { NULL, NULL }; -#endif /* We can't support multiple Python interpreter states: * https://bugzilla.gnome.org/show_bug.cgi?id=677091 */ - /* We are trying to initialize Python for the first time, - set init_failed to FALSE only if the entire initialization process - ends with success */ - priv->init_failed = TRUE; - /* Python initialization */ if (Py_IsInitialized ()) { @@ -444,27 +240,6 @@ peas_plugin_loader_python_initialize (PeasPluginLoader *loader) } else { -#ifdef HAVE_SIGACTION - struct sigaction sigint; - - /* We are going to install a signal handler for SIGINT if the current - signal handler for sigint is SIG_DFL. We do this because even if - Py_InitializeEx will not set the signal handlers, the 'signal' module - (which can be used by plugins for various reasons) will install a - SIGINT handler when imported, if SIGINT is set to SIG_DFL. Our - override will simply call the default SIGINT handler in the end. */ - sigaction (SIGINT, NULL, &sigint); - - if (sigint.sa_handler == SIG_DFL) - { - sigemptyset (&sigint.sa_mask); - sigint.sa_flags = 0; - sigint.sa_handler = default_sigint; - - sigaction (SIGINT, &sigint, NULL); - } -#endif - Py_InitializeEx (FALSE); priv->must_finalize_python = TRUE; } @@ -482,38 +257,6 @@ peas_plugin_loader_python_initialize (PeasPluginLoader *loader) goto python_init_error; } - prgname = g_get_prgname (); - prgname = prgname == NULL ? "" : prgname; - -#if PY_VERSION_HEX < 0x03000000 - argv[0] = prgname; -#else - argv[0] = peas_wchar_from_str (prgname); -#endif - - /* See http://docs.python.org/c-api/init.html#PySys_SetArgvEx */ -#if PY_VERSION_HEX < 0x02060600 - PySys_SetArgv (1, (char**) argv); - PyRun_SimpleString ("import sys; sys.path.pop(0)\n"); -#elif PY_VERSION_HEX < 0x03000000 - PySys_SetArgvEx (1, (char**) argv, 0); -#elif PY_VERSION_HEX < 0x03010300 - PySys_SetArgv (1, argv); - PyRun_SimpleString ("import sys; sys.path.pop(0)\n"); - g_free (argv[0]); -#else - PySys_SetArgvEx (1, argv, 0); - g_free (argv[0]); -#endif - - if (!add_module_path (pyloader, PEAS_PYEXECDIR)) - { - g_warning ("Error initializing Python Plugin Loader: " - "failed to add the module path"); - - goto python_init_error; - } - /* Initialize PyGObject */ pygobject_init (PYGOBJECT_MAJOR_VERSION, PYGOBJECT_MINOR_VERSION, @@ -535,47 +278,18 @@ peas_plugin_loader_python_initialize (PeasPluginLoader *loader) if (!priv->must_finalize_python) pyg_disable_warning_redirections (); - /* i18n support */ - gettext = PyImport_ImportModule ("gettext"); - if (gettext == NULL) - { - g_warning ("Error initializing Python Plugin Loader: " - "failed to import gettext"); - - goto python_init_error; - } - - result = PyObject_CallMethod (gettext, "install", "ss", - GETTEXT_PACKAGE, PEAS_LOCALEDIR); - Py_XDECREF (result); - - if (PyErr_Occurred ()) - { - g_warning ("Error initializing Python Plugin Loader: " - "failed to install gettext"); - - goto python_init_error; - } - - priv->internal = peas_python_internal_new (); + priv->internal = peas_python_internal_new (!priv->must_finalize_python); if (priv->internal == NULL) { /* Already warned */ goto python_init_error; } - /* Python has been successfully initialized */ - priv->init_failed = FALSE; - if (!priv->must_finalize_python) PyGILState_Release (state); else priv->py_thread_state = PyEval_SaveThread (); - /* loaded_plugins maps PeasPluginInfo:filename to a PyObject */ - priv->loaded_plugins = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, destroy_python_info); - return TRUE; python_init_error: @@ -589,6 +303,7 @@ python_init_error: if (!priv->must_finalize_python) PyGILState_Release (state); + priv->init_failed = TRUE; return FALSE; } @@ -609,14 +324,7 @@ peas_plugin_loader_python_finalize (GObject *object) g_warn_if_fail (priv->n_loaded_plugins == 0); - if (priv->loaded_plugins != NULL) - { - state = PyGILState_Ensure (); - g_hash_table_destroy (priv->loaded_plugins); - PyGILState_Release (state); - } - - if (priv->internal != NULL && !priv->init_failed) + if (priv->internal != NULL) { state = PyGILState_Ensure (); peas_python_internal_free (priv->internal); @@ -626,12 +334,6 @@ peas_plugin_loader_python_finalize (GObject *object) if (priv->py_thread_state) PyEval_RestoreThread (priv->py_thread_state); - if (priv->idle_gc != 0) - g_source_remove (priv->idle_gc); - - if (!priv->init_failed) - run_gc (pyloader); - if (priv->must_finalize_python) { if (!priv->init_failed) diff --git a/loaders/python/peas-python-internal.c b/loaders/python/peas-python-internal.c index 6acd020..8175898 100644 --- a/loaders/python/peas-python-internal.c +++ b/loaders/python/peas-python-internal.c @@ -27,21 +27,54 @@ #include <gio/gio.h> -/* _POSIX_C_SOURCE is defined in Python.h and in limits.h included by - * glib-object.h, so we unset it here to avoid a warning. Yep, that's bad. - */ -#undef _POSIX_C_SOURCE -#include <Python.h> - typedef PyObject _PeasPythonInternal; +static PyObject *FailedError = NULL; + + +static PyObject * +failed_fn (PyObject *self, + PyObject *args) +{ + const char *msg; + + if (!PyArg_ParseTuple (args, "s:Hooks.failed", &msg)) + return NULL; + + g_warning ("%s", msg); + + /* peas_python_internal_call() knows to check for this exception */ + PyErr_SetObject (FailedError, NULL); + return NULL; +} + +static PyMethodDef failed_method_def = { + "failed", (PyCFunction) failed_fn, METH_VARARGS | METH_STATIC, + "Prints warning and raises an Exception" +}; PeasPythonInternal * -peas_python_internal_new (void) +peas_python_internal_new (gboolean already_initialized) { GBytes *internal_python; - PyObject *builtins_module, *code, *globals, *result, *internal; + const gchar *prgname; + PeasPythonInternal *internal = NULL; + PyObject *builtins_module, *globals; + PyObject *code = NULL, *module = NULL; + PyObject *result = NULL, *failed_method = NULL; + +#define goto_error_if_failed(cond) \ + G_STMT_START { \ + if (G_UNLIKELY (!(cond))) \ + { \ + g_warn_if_fail (cond); \ + goto error; \ + } \ + } G_STMT_END + + prgname = g_get_prgname (); + prgname = prgname == NULL ? "" : prgname; #if PY_MAJOR_VERSION < 3 builtins_module = PyImport_ImportModule ("__builtin__"); @@ -49,7 +82,7 @@ peas_python_internal_new (void) builtins_module = PyImport_ImportModule ("builtins"); #endif - g_return_val_if_fail (builtins_module != NULL, NULL); + goto_error_if_failed (builtins_module != NULL); /* We don't use the byte-compiled Python source * because glib-compile-resources cannot output @@ -57,7 +90,6 @@ peas_python_internal_new (void) * * https://bugzilla.gnome.org/show_bug.cgi?id=673101 */ - internal_python = g_resources_lookup_data ("/org/gnome/libpeas/loaders/" #if PY_MAJOR_VERSION < 3 "python/" @@ -67,72 +99,138 @@ peas_python_internal_new (void) "internal.py", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); - - g_return_val_if_fail (internal_python != NULL, NULL); + goto_error_if_failed (internal_python != NULL); /* Compile it manually so the filename is available */ code = Py_CompileString (g_bytes_get_data (internal_python, NULL), "peas-python-internal.py", Py_file_input); - g_bytes_unref (internal_python); - - g_return_val_if_fail (code != NULL, NULL); - - globals = PyDict_New (); - if (globals == NULL) - { - Py_DECREF (code); - g_return_val_if_fail (globals != NULL, NULL); - } - - if (PyDict_SetItemString (globals, "__builtins__", - PyModule_GetDict (builtins_module)) != 0) - { - Py_DECREF (globals); - Py_DECREF (code); - return NULL; - } - + goto_error_if_failed (code != NULL); + + module = PyModule_New ("libpeas-internal"); + goto_error_if_failed (module != NULL); + + goto_error_if_failed (PyModule_AddObject (module, "__builtins__", + builtins_module) == 0); + goto_error_if_failed (PyModule_AddObject (module, "ALREADY_INITIALIZED", + already_initialized ? + Py_True : Py_False) == 0); + goto_error_if_failed (PyModule_AddStringConstant (module, "PRGNAME", + prgname) == 0); + goto_error_if_failed (PyModule_AddStringMacro (module, + PEAS_PYEXECDIR) == 0); + goto_error_if_failed (PyModule_AddStringMacro (module, + GETTEXT_PACKAGE) == 0); + goto_error_if_failed (PyModule_AddStringMacro (module, + PEAS_LOCALEDIR) == 0); + + globals = PyModule_GetDict (module); result = PyEval_EvalCode ((gpointer) code, globals, globals); Py_XDECREF (result); - Py_DECREF (code); if (PyErr_Occurred ()) { - Py_DECREF (globals); - return NULL; + g_warning ("Failed to run internal Python code"); + goto error; } - internal = PyDict_GetItemString (globals, "hooks"); - Py_XINCREF (internal); - Py_DECREF (globals); + result = PyDict_GetItemString (globals, "hooks"); + goto_error_if_failed (result != NULL); + + goto_error_if_failed (PyObject_SetAttrString (result, + "__internal_module__", + module) == 0); + + FailedError = PyDict_GetItemString (globals, "FailedError"); + goto_error_if_failed (FailedError != NULL); + + failed_method = PyCFunction_NewEx (&failed_method_def, NULL, module); + goto_error_if_failed (failed_method != NULL); + goto_error_if_failed (PyObject_SetAttrString (result, "failed", + failed_method) == 0); - g_return_val_if_fail (internal != NULL, NULL); - return (PeasPythonInternal *) internal; + internal = (PeasPythonInternal *) result; + +#undef goto_error_if_failed + +error: + + if (internal == NULL) + Py_XDECREF (result); + + Py_XDECREF (failed_method); + Py_XDECREF (module); + Py_XDECREF (code); + g_clear_pointer (&internal_python, g_bytes_unref); + + return internal; } -/* NOTE: This must be called with the GIL held */ void peas_python_internal_free (PeasPythonInternal *internal) { PyObject *internal_ = (PyObject *) internal; - peas_python_internal_call (internal, "exit"); + peas_python_internal_call (internal, "exit", NULL, NULL); Py_DECREF (internal_); } -/* NOTE: This must be called with the GIL held */ -void +PyObject * peas_python_internal_call (PeasPythonInternal *internal, - const gchar *name) + const gchar *name, + PyTypeObject *return_type, + const gchar *format, + ...) { PyObject *internal_ = (PyObject *) internal; - PyObject *result; + PyObject *callable, *args; + PyObject *result = NULL; + va_list var_args; - result = PyObject_CallMethod (internal_, (gchar *) name, NULL); - Py_XDECREF (result); + /* The PyTypeObject for Py_None is not exposed directly */ + if (return_type == NULL) + return_type = Py_None->ob_type; + + callable = PyObject_GetAttrString (internal_, name); + g_return_val_if_fail (callable != NULL, NULL); + + va_start (var_args, format); + args = Py_VaBuildValue (format == NULL ? "()" : format, var_args); + va_end (var_args); + + if (args != NULL) + { + result = PyObject_CallObject (callable, args); + Py_DECREF (args); + } if (PyErr_Occurred ()) - PyErr_Print (); -} + { + /* Raised by failed_fn() to prevent printing the exception */ + if (PyErr_ExceptionMatches (FailedError)) + { + PyErr_Clear (); + } + else + { + g_warning ("Failed to run internal Python hook '%s'", name); + PyErr_Print (); + } + + return NULL; + } + + /* We always allow a None result */ + if (result == Py_None) + { + Py_CLEAR (result); + } + else if (!PyObject_TypeCheck (result, return_type)) + { + g_warning ("Failed to run internal Python hook '%s': ", name); + Py_CLEAR (result); + } + + return result; +} diff --git a/loaders/python/peas-python-internal.h b/loaders/python/peas-python-internal.h index e2c3124..df8735d 100644 --- a/loaders/python/peas-python-internal.h +++ b/loaders/python/peas-python-internal.h @@ -24,16 +24,26 @@ #include <glib.h> +/* _POSIX_C_SOURCE is defined in Python.h and in limits.h included by + * glib-object.h, so we unset it here to avoid a warning. Yep, that's bad. + */ +#undef _POSIX_C_SOURCE +#include <Python.h> + G_BEGIN_DECLS typedef struct _PeasPythonInternal PeasPythonInternal; PeasPythonInternal * - peas_python_internal_new (void); + peas_python_internal_new (gboolean already_initialized); void peas_python_internal_free (PeasPythonInternal *internal); -void peas_python_internal_call (PeasPythonInternal *internal, - const gchar *name); +PyObject * + peas_python_internal_call (PeasPythonInternal *internal, + const gchar *name, + PyTypeObject *return_type, + const gchar *format, + ...); G_END_DECLS diff --git a/loaders/python/peas-python-internal.py b/loaders/python/peas-python-internal.py index 27838a8..991d2f9 100644 --- a/loaders/python/peas-python-internal.py +++ b/loaders/python/peas-python-internal.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014 - Garrett Regier +# Copyright (C) 2014-2015 - Garrett Regier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Library General Public License as published by @@ -17,83 +17,204 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -import cProfile +import gc +import gettext +import importlib import os -import pstats +import signal import sys -import threading -import weakref +import traceback + +from gi.repository import GLib, GObject + + +# Derive from something not normally caught +class FailedError(BaseException): + pass class Hooks(object): def __init__(self): - self.profiling_enabled = os.getenv('PEAS_PYTHON_PROFILE') is not None - if not self.profiling_enabled: - return + if not ALREADY_INITIALIZED: + int_handler = signal.getsignal(signal.SIGINT) - sort = os.getenv('PEAS_PYTHON_PROFILE', default='time') - self.stat_sort = sort.split(';') + # Use the default handler instead of raising KeyboardInterrupt + if int_handler == signal.default_int_handler: + signal.signal(signal.SIGINT, signal.SIG_DFL) - self.stats = None - self.stats_lock = threading.Lock() + # See PySys_SetArgvEx() + sys.argv = [PRGNAME] + sys.path.pop(0) - self.thread_refs = [] - self.thread_local = threading.local() + sys.path.insert(0, PEAS_PYEXECDIR) + gettext.install(GETTEXT_PACKAGE, PEAS_LOCALEDIR) - threading.setprofile(self.init_thread) + self.__idle_gc = 0 + self.__module_cache = {} + self.__extension_cache = {} - self.profile = cProfile.Profile() - self.profile.enable() + @staticmethod + def failed(): + # This is implemented by the plugin loader + raise NotImplementedError('Hooks.failed()') - def add_stats(self, profile): - profile.disable() + def load(self, filename, module_dir, module_name): + try: + return self.__module_cache[filename] - with self.stats_lock: - if self.stats is None: - self.stats = pstats.Stats(profile) + except KeyError: + pass - else: - self.stats.add(profile) + if module_name in sys.modules: + self.__module_cache[filename] = None + self.failed("Error loading plugin '%s': " + "module name '%s' has already been used" % + (filename, module_name)) - def init_thread(self, *unused): - # Only call once per thread - sys.setprofile(None) + if module_dir not in sys.path: + sys.path.insert(0, module_dir) - thread_profile = cProfile.Profile() + try: + module = importlib.import_module(module_name) - def thread_finished(thread_ref): - self.add_stats(thread_profile) + except: + module = None + self.failed("Error importing plugin '%s':\n%s" % + (module_name, traceback.format_exc())) - self.thread_refs.remove(thread_ref) + else: + self.__extension_cache[module] = {} - # Need something to weakref, the - # current thread does not support it - thread_ref = set() - self.thread_local.ref = thread_ref + finally: + self.__module_cache[filename] = module - self.thread_refs.append(weakref.ref(thread_ref, thread_finished)) + return module - # Only enable the profile at the end - thread_profile.enable() + def find_extension_type(self, gtype, module): + module_gtypes = self.__extension_cache[module] - def all_plugins_unloaded(self): - if not self.profiling_enabled: - return + try: + return module_gtypes[gtype] + + except KeyError: + pass + + for key in module.__dict__: + value = getattr(module, key) + + try: + value_gtype = value.__gtype__ + + except AttributeError: + continue + + if GObject.type_is_a(value_gtype, gtype): + module_gtypes[gtype] = value + return value + + module_gtypes[gtype] = None + return None - self.add_stats(self.profile) + def __run_gc(self): + gc.collect() - with self.stats_lock: - self.stats.strip_dirs().sort_stats(*self.stat_sort).print_stats() + self.__idle_gc = 0 + return False - # Need to create a new profile to avoid adding the stats twice - self.profile = cProfile.Profile() - self.profile.enable() + def garbage_collect(self): + # We run the GC right now and we schedule + # a further collection in the main loop + gc.collect() + + if self.__idle_gc == 0: + self.__idle_gc = GLib.idle_add(self.__run_gc) + GLib.source_set_name_by_id(self.__idle_gc, '[libpeas] run_gc') + + def all_plugins_unloaded(self): + pass def exit(self): - if not self.profiling_enabled: - return + gc.collect() + + if self.__idle_gc != 0: + GLib.source_remove(self.__idle_gc) + + +if os.getenv('PEAS_PYTHON_PROFILE') is not None: + import cProfile + import pstats + import threading + import weakref + + + class Hooks(Hooks): + def __init__(self): + super(Hooks, self).__init__() + + sort = os.getenv('PEAS_PYTHON_PROFILE', default='time') + self.__stat_sort = sort.split(';') + + self.__stats = None + self.__stats_lock = threading.Lock() + + self.__thread_refs = [] + self.__thread_local = threading.local() + + threading.setprofile(self.__init_thread) + + self.__profile = cProfile.Profile() + self.__profile.enable() + + def __add_stats(self, profile): + profile.disable() + + with self.__stats_lock: + if self.__stats is None: + self.__stats = pstats.Stats(profile) + + else: + self.__stats.add(profile) + + def __init_thread(self, *unused): + # Only call once per thread + sys.setprofile(None) + + thread_profile = cProfile.Profile() + + def thread_finished(thread_ref): + self.__add_stats(thread_profile) + + self.__thread_refs.remove(thread_ref) + + # Need something to weakref, the + # current thread does not support it + thread_ref = set() + self.__thread_local.ref = thread_ref + + self.__thread_refs.append(weakref.ref(thread_ref, + thread_finished)) + + # Only enable the profile at the end + thread_profile.enable() + + def all_plugins_unloaded(self): + super(Hooks, self).all_plugins_unloaded() + + self.__add_stats(self.__profile) + + with self.__stats_lock: + stats = self.__stats.strip_dirs() + stats.sort_stats(*self.__stat_sort) + stats.print_stats() + + # Need to create a new profile to avoid adding the stats twice + self.__profile = cProfile.Profile() + self.__profile.enable() + + def exit(self): + super(Hooks, self).exit() - self.profile.disable() + self.__profile.disable() hooks = Hooks() |