mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 19:08:55 -04:00
285 lines
7.7 KiB
C++
285 lines
7.7 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file pythonThread.cxx
|
|
* @author drose
|
|
* @date 2007-04-13
|
|
*/
|
|
|
|
#include "pythonThread.h"
|
|
#include "pnotify.h"
|
|
|
|
#ifdef HAVE_PYTHON
|
|
#include "py_panda.h"
|
|
|
|
TypeHandle PythonThread::_type_handle;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PythonThread::
|
|
PythonThread(PyObject *function, PyObject *args,
|
|
const std::string &name, const std::string &sync_name) :
|
|
Thread(name, sync_name)
|
|
{
|
|
_function = function;
|
|
Py_INCREF(_function);
|
|
_args = nullptr;
|
|
_result = nullptr;
|
|
|
|
if (!PyCallable_Check(_function)) {
|
|
nassert_raise("Invalid function passed to PythonThread constructor");
|
|
}
|
|
|
|
set_args(args);
|
|
|
|
#if !defined(SIMPLE_THREADS) && defined(WITH_THREAD) && PY_VERSION_HEX < 0x03090000
|
|
// Ensure that the Python threading system is initialized and ready to go.
|
|
// WITH_THREAD symbol defined within Python.h
|
|
// PyEval_InitThreads is now a deprecated no-op in Python 3.9+
|
|
PyEval_InitThreads();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PythonThread::
|
|
~PythonThread() {
|
|
// Unfortunately, we need to grab the GIL to release these things,
|
|
// since the destructor could be called from any thread.
|
|
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
|
PyGILState_STATE gstate;
|
|
gstate = PyGILState_Ensure();
|
|
#endif
|
|
|
|
Py_DECREF(_function);
|
|
Py_XDECREF(_args);
|
|
Py_XDECREF(_result);
|
|
|
|
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
|
|
PyGILState_Release(gstate);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Blocks the calling process until the thread terminates. If the thread has
|
|
* already terminated, this returns immediately.
|
|
*
|
|
* The PythonThread flavor of this function returns the same value returned by
|
|
* the thread function.
|
|
*/
|
|
PyObject *PythonThread::
|
|
join() {
|
|
Thread::join();
|
|
|
|
if (_result == nullptr) {
|
|
// No result; return None.
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
Py_INCREF(_result);
|
|
return _result;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PyObject *PythonThread::
|
|
get_args() const {
|
|
return _args;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void PythonThread::
|
|
set_args(PyObject *args) {
|
|
Py_XDECREF(_args);
|
|
|
|
if (args == Py_None) {
|
|
// None means no arguments; create an empty tuple.
|
|
_args = PyTuple_New(0);
|
|
} else {
|
|
_args = nullptr;
|
|
if (PySequence_Check(args)) {
|
|
_args = PySequence_Tuple(args);
|
|
}
|
|
if (_args == nullptr) {
|
|
Dtool_Raise_TypeError("PythonThread args must be a tuple");
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_PYTHON
|
|
/**
|
|
* Internal function to safely call a Python function within a sub-thread,
|
|
* that might execute in parallel with existing Python code. The return value
|
|
* is the return value of the Python function, or NULL if there was an
|
|
* exception.
|
|
*/
|
|
PyObject *PythonThread::
|
|
call_python_func(PyObject *function, PyObject *args) {
|
|
Thread *current_thread = get_current_thread();
|
|
|
|
// Create a new Python thread state data structure, so Python can properly
|
|
// lock itself.
|
|
PyObject *result = nullptr;
|
|
|
|
if (current_thread == get_main_thread()) {
|
|
// In the main thread, just call the function.
|
|
result = PyObject_Call(function, args, nullptr);
|
|
|
|
if (result == nullptr) {
|
|
if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
|
|
// If we caught SystemExit, let it pass by without bothering to print
|
|
// a callback.
|
|
|
|
} else {
|
|
// Temporarily save and restore the exception state so we can print a
|
|
// callback on-the-spot.
|
|
PyObject *exc, *val, *tb;
|
|
PyErr_Fetch(&exc, &val, &tb);
|
|
|
|
Py_XINCREF(exc);
|
|
Py_XINCREF(val);
|
|
Py_XINCREF(tb);
|
|
PyErr_Restore(exc, val, tb);
|
|
PyErr_Print();
|
|
|
|
PyErr_Restore(exc, val, tb);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
#ifndef HAVE_THREADS
|
|
// Shouldn't be possible to come here without having some kind of
|
|
// threading support enabled.
|
|
nassert_raise("threading support disabled");
|
|
return nullptr;
|
|
#else
|
|
|
|
#ifdef SIMPLE_THREADS
|
|
// We can't use the PyGILState interface, which assumes we are using true
|
|
// OS-level threading. PyGILState enforces policies like only one thread
|
|
// state per OS-level thread, which is not true in the case of
|
|
// SIMPLE_THREADS.
|
|
|
|
// For some reason I don't fully understand, I'm getting a crash when I
|
|
// clean up old PyThreadState objects with PyThreadState_Delete(). It
|
|
// appears that the thread state is still referenced somewhere at the time
|
|
// I call delete, and the crash occurs because I've deleted an active
|
|
// pointer.
|
|
|
|
// Storing these pointers in a vector for permanent recycling seems to
|
|
// avoid this problem. I wish I understood better what's going wrong, but
|
|
// I guess this workaround will do.
|
|
static pvector<PyThreadState *> thread_states;
|
|
|
|
PyThreadState *orig_thread_state = PyThreadState_Get();
|
|
PyInterpreterState *istate = orig_thread_state->interp;
|
|
PyThreadState *new_thread_state;
|
|
if (thread_states.empty()) {
|
|
new_thread_state = PyThreadState_New(istate);
|
|
} else {
|
|
new_thread_state = thread_states.back();
|
|
thread_states.pop_back();
|
|
}
|
|
PyThreadState_Swap(new_thread_state);
|
|
|
|
// Call the user's function.
|
|
result = PyObject_Call(function, args, nullptr);
|
|
if (result == nullptr && PyErr_Occurred()) {
|
|
// We got an exception. Move the exception from the current thread into
|
|
// the main thread, so it can be handled there.
|
|
PyObject *exc, *val, *tb;
|
|
PyErr_Fetch(&exc, &val, &tb);
|
|
|
|
thread_cat.error()
|
|
<< "Exception occurred within " << *current_thread << "\n";
|
|
|
|
// Temporarily restore the exception state so we can print a callback
|
|
// on-the-spot.
|
|
Py_XINCREF(exc);
|
|
Py_XINCREF(val);
|
|
Py_XINCREF(tb);
|
|
PyErr_Restore(exc, val, tb);
|
|
PyErr_Print();
|
|
|
|
PyThreadState_Swap(orig_thread_state);
|
|
thread_states.push_back(new_thread_state);
|
|
// PyThreadState_Clear(new_thread_state);
|
|
// PyThreadState_Delete(new_thread_state);
|
|
|
|
PyErr_Restore(exc, val, tb);
|
|
|
|
// Now attempt to force the main thread to the head of the ready queue,
|
|
// so it can respond to the exception immediately. This only works if
|
|
// the main thread is not blocked, of course.
|
|
Thread::get_main_thread()->preempt();
|
|
|
|
} else {
|
|
// No exception. Restore the thread state normally.
|
|
PyThreadState_Swap(orig_thread_state);
|
|
thread_states.push_back(new_thread_state);
|
|
// PyThreadState_Clear(new_thread_state);
|
|
// PyThreadState_Delete(new_thread_state);
|
|
}
|
|
|
|
#else // SIMPLE_THREADS
|
|
// With true threading enabled, we're better off using PyGILState.
|
|
PyGILState_STATE gstate;
|
|
gstate = PyGILState_Ensure();
|
|
|
|
// Call the user's function.
|
|
result = PyObject_Call(function, args, nullptr);
|
|
if (result == nullptr && PyErr_Occurred()) {
|
|
// We got an exception. Move the exception from the current thread into
|
|
// the main thread, so it can be handled there.
|
|
PyObject *exc, *val, *tb;
|
|
PyErr_Fetch(&exc, &val, &tb);
|
|
|
|
thread_cat.error()
|
|
<< "Exception occurred within " << *current_thread << "\n";
|
|
|
|
// Temporarily restore the exception state so we can print a callback
|
|
// on-the-spot.
|
|
Py_XINCREF(exc);
|
|
Py_XINCREF(val);
|
|
Py_XINCREF(tb);
|
|
PyErr_Restore(exc, val, tb);
|
|
PyErr_Print();
|
|
|
|
PyGILState_Release(gstate);
|
|
|
|
PyErr_Restore(exc, val, tb);
|
|
} else {
|
|
// No exception. Restore the thread state normally.
|
|
PyGILState_Release(gstate);
|
|
}
|
|
|
|
|
|
#endif // SIMPLE_THREADS
|
|
#endif // HAVE_THREADS
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif // HAVE_PYTHON
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void PythonThread::
|
|
thread_main() {
|
|
_result = call_python_func(_function, _args);
|
|
}
|
|
|
|
#endif // HAVE_PYTHON
|