[Python-Dev] Problems with Python's default dlopen flags

Mark Hammond mhammond@skippinet.com.au
Sun, 5 May 2002 14:08:44 +1000


> I'm hoping I can raise some interest in resolving this problem:

Or at least documenting it well :)

I don't understand the issues at all, other than my belief that Linux makes
it harder than it needs to be.  Eg, in the recent PyXPCOM changes, I found
that I needed to add the following line of code:

	// *sob* - seems necessary to open the .so as RTLD_GLOBAL
	dlopen(PYTHON_SO,RTLD_NOW | RTLD_GLOBAL);

Where PYTHON_SO is the base-name of the Python shared library.  I wont
pretend to understand the issues, but without this flag *my* code worked
fine, and I could call Python fine.  However, as soon as Python attempted to
import a module in a dynamic library, it would fail with "_PyNone_Struct" as
unresolved.  This includes standard Python extension modules (cStringIO,
new, etc) which obviously import fine normally.

Now, in this case, "my code" above is actually itself implemented in a .so.
(and has had the Python .so linked against it as I can call and use Python
itself fine without the extra dlopen()).  This .so has been previously
dynamically loaded by Mozilla.  Presumably this impacts the situation as
otherwise my code is doing a bog-standard Python initialization that works
perfectly well in its own executable.

As I said, I wont even pretend to understand the issues, but I do know that:
* The line above made my code work.
* Windows works perfectly in this situation, and always has.
* It shouldn't be this hard.

In the interests of moving this forward, I would be happy to build some test
or example code that implements some "interesting but reasonable" solutions
using dynamic linking.  Eg, Martin's mail in this thread documents how
symbol sharing should be implemented using shims etc - however, in my
particular situation, I have no special symbol sharing requirements - all I
have is a dynamically loaded .so that itself needs to use Python in a .so,
but I am having problems with Python itself using other .so modules.  Again,
note that my .so works fine, and can use Python itself (linked as a .so)
just fine, but Python itself is having problems loading .so modules.  All
this is ActivePython, as Python doesn't (didn't?) support .so creation by
default.

<aside>
On the plus-side, that was *nothing* compared to the thread-state hacks I
had to pull to get this working.  In my particular situation, I may either
be faced with Python already having been loaded, initialized, and
thread-state released, or Python never been initialized at all.

	PRBool bDidInitPython = !Py_IsInitialized(); // well, I will next line,
anyway :-)
	if (bDidInitPython) {
		Py_Initialize();
		if (!Py_IsInitialized()) {
			LogError("Python initialization failed!\n");
			return NS_ERROR_FAILURE;
		}
		PyEval_InitThreads();
	}
	// Get the Python interpreter state
	PyThreadState *threadStateCreated = NULL;
	PyThreadState *threadState = PyThreadState_Swap(NULL);
	if (threadState==NULL) {
		// no thread-state - set one up.
		// *sigh* - what I consider a bug is that Python
		// will deadlock unless we own the lock before creating
		// a new interpreter (it appear Py_NewInterpreter has
		// really only been tested/used with no thread lock
		PyEval_AcquireLock();
		threadState = threadStateCreated = Py_NewInterpreter();
		PyThreadState_Swap(NULL);
	}
	PyEval_ReleaseLock();
	PyEval_AcquireThread(threadState);
// finally I can now reliably use Python.
// cleanup
	// Abandon the thread-lock, as the first thing Python does
	// is re-establish the lock (the Python thread-state story SUCKS!!!)
	if (threadStateCreated) {
		Py_EndInterpreter(threadStateCreated);
		PyEval_ReleaseLock(); // see Py_NewInterpreter call above
	} else {
		PyEval_ReleaseThread(threadState);
		PyThreadState *threadStateSave = PyThreadState_Swap(NULL);
		if (threadStateSave)
			PyThreadState_Delete(threadStateSave);
	}

I discovered that if no Python thread-state is current, there is no
reasonable way to get at a PyInterpreterState :(  PyInterpreterState_Head()
is too recent to rely on.  Further, if you call PyInterpreterState_New()
there is no reasonable way to set it up (eg, populate builtins etc) - this
logic is in Py_Initialize(), which relies on a global "initialized"
variable, rather than checking a value in PyInterpreterState.  Thus,
PyInterpreterState_New, as a public symbol, appears useless.

I hope to make some specific thread-state cleanup proposals soon <wink>
</aside>

Mark.