[ 
http://issues.apache.org/jira/browse/MODPYTHON-77?page=comments#action_12356765 
] 

Graham Dumpleton commented on MODPYTHON-77:
-------------------------------------------

Just to make this problem even more complicated, due to how Mac OS X handles 
unloading of dynamically loaded modules, parts of the solution presented here 
so far may not actually work on that platform.

On Mac OS X, if one takes the initial basic solution of storing the first 
interpreter as the "main_interpreter" and nothing else, the original problem 
goes away, however, Apache will crash when "apachectl restart" is run.

The problem is that when a "restart" occurs, Apache doesn't actually kill the 
parent process. What it does is to unload any loaded Apache modules, such as 
"mod_python.so" and then it will be reload them again. This results in all 
global data within "mod_python.so" being thrown away. The "python_init()" 
function of the mod_python.so will be called again to reinitialise all the lost 
global data and start over, but it fails in doing this.

The reason it fails is that although mod_python.so is unloaded, the dynamically 
loaded Python framework isn't. The first consequence of this is that when 
calling python_init() the subsequent time, the Python framework is already 
initialised. Due to a previous workaround incorporated into python_init() 
specifically because of Mac OS X, this luckily doesn't stop us from at least 
trying to initialise mod_python again.

In initialising mod_python though, because the previous global data is lost, we 
need to store the initial interpreter state against main_interpreter again. To 
do this, "PyThreadState_Get()->interp" is used. For this to work though, it is 
necessary that the call to "PyEval_InitThreads()" just prior to that point in 
the code has acquired the global lock and set up a current thread state. This 
will only occur though for the very first call to PyEval_InitThreads(). Because 
the Python framework isn't unloaded and Python thus is already in an 
initialised state, the PyEval_InitThreads() call doesn't do anything. Because 
there is no valid active thread state, the call to PyThreadState_Get() fails.

One can see this when some extra debugging is added to mod_python. For Mac OS X 
we get:

# apachectl start

[Fri Nov 04 20:50:17 2005] [notice] mod_python: Creating 8 session mutexes 
based on 6 max processes and 25 max threads.
[Fri Nov 04 20:50:17 2005] [error] mp_initialized=0
[Fri Nov 04 20:50:17 2005] [error] Py_IsInitialized()=0
[Fri Nov 04 20:50:17 2005] [error] interpreters=0
[Fri Nov 04 20:50:18 2005] [error] save interpreter
[Fri Nov 04 20:50:18 2005] [error] pid=805
[Fri Nov 04 20:50:18 2005] [error] ppid=1
[Fri Nov 04 20:50:18 2005] [error] done save interpreter
[Fri Nov 04 20:50:18 2005] [error] get thread state
[Fri Nov 04 20:50:18 2005] [error] okay got thread state
[Fri Nov 04 20:50:18 2005] [error] released interpreter
[Fri Nov 04 20:50:18 2005] [notice] Apache/2.0.51 (Unix) mod_python/3.2.4b 
Python/2.3 configured -- resuming normal operations

# apachectl restart

[Fri Nov 04 20:50:26 2005] [warn] child process 807 still did not exit, sending 
a SIGTERM
[Fri Nov 04 20:50:27 2005] [notice] SIGHUP received.  Attempting to restart
[Fri Nov 04 20:50:27 2005] [notice] mod_python: Creating 8 session mutexes 
based on 2 max processes and 25 max threads.
[Fri Nov 04 20:50:27 2005] [error] mp_initialized=0
[Fri Nov 04 20:50:27 2005] [error] Py_IsInitialized()=1
[Fri Nov 04 20:50:27 2005] [error] interpreters=0
[Fri Nov 04 20:50:27 2005] [error] save interpreter
[Fri Nov 04 20:50:27 2005] [error] pid=805
[Fri Nov 04 20:50:27 2005] [error] ppid=1
Fatal Python error: PyThreadState_Get: no current thread
[Fri Nov 04 20:50:27 2005] [notice] seg fault or similar nasty error detected 
in the parent process

You can see how it is the same process ID, that Python is already marked as 
initailised, yet "interpreters" and the "initialized" flag which were global 
data in mod_python.so have gone back to being 0. Finally, the 
PyThreadState_Get() function dies.

If one looks at Linux, one instead sees:

# apachectl start

[Fri Nov 04 05:22:46 2005] [notice] mod_python: Creating 8 session mutexes 
based on 150 max processes and 0 max threads.
[Fri Nov 04 05:22:46 2005] [error] mp_initialized=0
[Fri Nov 04 05:22:46 2005] [error] Py_IsInitialized()=0
[Fri Nov 04 05:22:46 2005] [error] interpreters=0
[Fri Nov 04 05:22:46 2005] [error] save interpreter
[Fri Nov 04 05:22:46 2005] [error] pid=8757
[Fri Nov 04 05:22:46 2005] [error] ppid=1
[Fri Nov 04 05:22:46 2005] [error] done save interpreter
[Fri Nov 04 05:22:46 2005] [error] get thread state
[Fri Nov 04 05:22:46 2005] [error] okay got thread state
[Fri Nov 04 05:22:46 2005] [error] released interpreter
[Fri Nov 04 05:22:46 2005] [notice] Apache/2.0.55 (Unix) mod_python/3.2.4b 
Python/2.3.5 configured -- resuming normal operations

# apachectl restart

[Fri Nov 04 05:22:55 2005] [notice] SIGHUP received.  Attempting to restart
[Fri Nov 04 05:22:55 2005] [notice] mod_python: Creating 8 session mutexes 
based on 150 max processes and 0 max threads.
[Fri Nov 04 05:22:55 2005] [error] mp_initialized=0
[Fri Nov 04 05:22:55 2005] [error] Py_IsInitialized()=0
[Fri Nov 04 05:22:55 2005] [error] interpreters=0
[Fri Nov 04 05:22:55 2005] [error] save interpreter
[Fri Nov 04 05:22:55 2005] [error] pid=8757
[Fri Nov 04 05:22:55 2005] [error] ppid=1
[Fri Nov 04 05:22:55 2005] [error] done save interpreter
[Fri Nov 04 05:22:55 2005] [error] get thread state
[Fri Nov 04 05:22:55 2005] [error] okay got thread state
[Fri Nov 04 05:22:55 2005] [error] released interpreter
[Fri Nov 04 05:22:55 2005] [notice] Apache/2.0.55 (Unix) mod_python/3.2.4b 
Python/2.3.5 configured -- resuming normal operations

Note how Python isn't initialised on second time through. This indicates that 
any instance of Python in Apache is also being unloaded and thus everything 
starts over from a totally clean slate.

Anyway, that be an additional problem on Mac OS X that will have to be dealt 
with in finding a solution. Since the issue is in the parent process, am going 
to investigate whether the initial interpreter should only be stored as 
main_interpreter in the child process. Ie., in PythonChildInitHandler(). This 
might avoid this issue.

More reports later. :-)

> The multiple interpreter concept of mod_python is broken for Python extension 
> modules since Python 2.3
> ------------------------------------------------------------------------------------------------------
>
>          Key: MODPYTHON-77
>          URL: http://issues.apache.org/jira/browse/MODPYTHON-77
>      Project: mod_python
>         Type: Bug
>   Components: core
>     Versions: 3.1.4
>  Environment: Python >= 2.3
>     Reporter: Boyan Boyadjiev
>  Attachments: diff.txt, diff2.txt, diff3.txt, gil_test.c, gilstate.tar.gz, 
> mod_python.c, mod_python.c.diff, mod_python.h.diff, src.zip
>
> The multiple interpreter concept of mod_python is broken for Python extension 
> modules since Python 2.3 because of the PEP 311 (Simplified Global 
> Interpreter Lock Acquisition for Extensions):
> ...
> Limitations and Exclusions
>     This proposal identifies a solution for extension authors with
>     complex multi-threaded requirements, but that only require a
>     single "PyInterpreterState".  There is no attempt to cater for
>     extensions that require multiple interpreter states.  At the time
>     of writing, no extension has been identified that requires
>     multiple PyInterpreterStates, and indeed it is not clear if that
>     facility works correctly in Python itself.
> ...
> For mod_python this means, that complex Python extensions won't work any more 
> with Python >= 2.3, because they are supposed to work only with the first 
> interpreter state initialized for the current process (a problem we 
> experienced). The first interpreter state is not used by mod_python after the 
> python_init is called. 
> One solution, which works fine for me, is to save the first interpreter state 
> into the "interpreters" dictionary in the function python_init 
> (MAIN_INTERPRETER is used as a key):
> static int python_init(apr_pool_t *p, apr_pool_t *ptemp,
>                        apr_pool_t *plog, server_rec *s)
> {
>     ...
>     /* initialize global Python interpreter if necessary */
>     if (! Py_IsInitialized())
>     {
>         /* initialze the interpreter */
>         Py_Initialize();
> #ifdef WITH_THREAD
>         /* create and acquire the interpreter lock */
>         PyEval_InitThreads();
> #endif
>         /* create the obCallBack dictionary */
>         interpreters = PyDict_New();
>         if (! interpreters) {
>             ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, s,
>                          "python_init: PyDict_New() failed! No more memory?");
>             exit(1);
>         }
>         {   
>             /*
>             Workaround PEP 311 - Simplified Global Interpreter Lock 
> Acquisition for Extensions
>             BEGIN
>             */
>             PyObject *p = 0;
>             interpreterdata * idata = (interpreterdata 
> *)malloc(sizeof(interpreterdata));
>             PyThreadState* currentThreadState = PyThreadState_Get();
>             PyInterpreterState *istate = currentThreadState->interp;
>             idata->istate = istate;
>             /* obcallback will be created on first use */
>             idata->obcallback = NULL;
>             p = PyCObject_FromVoidPtr((void ) idata, NULL); /*p->refcout = 1*/
>             PyDict_SetItemString(interpreters, MAIN_INTERPRETER, p); 
> /*p->refcout = 2*/
>             Py_DECREF(p); /*p->refcout = 1*/
>             /*
>             END
>             Workaround PEP 311 - Simplified Global Interpreter Lock 
> Acquisition for Extensions
>             */
>         }
>         /* Release the thread state because we will never use
>          * the main interpreter, only sub interpreters created later. */
>         PyThreadState_Swap(NULL);
> #ifdef WITH_THREAD
>         /* release the lock; now other threads can run */
>         PyEval_ReleaseLock();
> #endif
>     }
>     return OK;
> }
> Another change I've made in the attached file is to Py_DECREF(p) in 
> get_interpreter, which will remove leaky reference to the PyCObject with the 
> interpreter data. This was not a real problem, but now I see fewer leaks in 
> BoundsChecker :-).

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://issues.apache.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see:
   http://www.atlassian.com/software/jira

Reply via email to