summaryrefslogtreecommitdiff
path: root/loaders/python
diff options
context:
space:
mode:
authorGarrett Regier <garrettregier@gmail.com>2015-01-05 07:39:33 -0800
committerGarrett Regier <garrettregier@gmail.com>2015-01-20 01:23:07 -0800
commit83a44f3c0e610814923a29c419b4a1ba16fbee08 (patch)
tree8a557275b482ecbae25a6295ecc824c2cf204aa3 /loaders/python
parentb95aebb38b317c9a33db80e0c0caee7accb60a2b (diff)
downloadlibpeas-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.c394
-rw-r--r--loaders/python/peas-python-internal.c196
-rw-r--r--loaders/python/peas-python-internal.h16
-rw-r--r--loaders/python/peas-python-internal.py221
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()