Python in PHP: Internals

Author: Jon Parise
Contact: jon@php.net
Date: $Date: $
Revision: $Revision: $

Contents

1   Embedded Python

1.1   Thread State

The Python in PHP extension uses Python's native thread state API to manage thread state within the process's main Python interpreter.

Each PHP request has its own, private Python sub-interpreter. This interpreter and its associated thread state is created at the start of the request via a call to Py_NewInterpreter(). The interpreter's thread state is stored in a request global for access throughout the lifetime of the request. When the request is shut down, the interpreter and thread state are destroyed via a call to Py_EndInterpreter().

The request's sub-interpreter is a private execution environment within the process's main Python interpreter. This means that each request's interpreter has its own, unique set of imported modules (including __builtin__, __main__, and sys). It also means that state from one request's Python environment won't "leak" into other requests' environments.

Whenever the Python extension accesses Python's C API, it must first acquire the current request's thread state. Once it's done interacting with the Python C API, the thread state is released. These operations are handled by the PHP_PYTHON_THREAD_ACQUIRE() and PHP_PYTHON_THREAD_RELEASE() macros:

PHP_FUNCTION(python_version)
{
    const char *version;

    PHP_PYTHON_THREAD_ACQUIRE();
    version = Py_GetVersion();
    PHP_PYTHON_THREAD_RELEASE();

    RETURN_STRING((char *)version, 0);
}

Some of the extension's internal helper functions (notably, the type conversion functions) also require the thread state to be held while they access the Python C API. These functions simply assert that the appropriate thread state has already been acquired by their calling context.

int pyobject_to_zval_string(PyObject *o, zval *zv TSRMLS_DC)
{
    PHP_PYTHON_THREAD_ASSERT();

    if (PyString_Check(o)) {
        ZVAL_STRINGL(zv, PyString_AS_STRING(o), PyString_GET_SIZE(o), 1);
        return SUCCESS;
    }

    return FAILURE;
}

Note that calls to PHP_PYTHON_THREAD_ACQUIRE() and PHP_PYTHON_THREAD_RELEASE() do not nest. Also, failure to release thread state after it is acquire may lead to unexpected behavior. It is important to ensure that PHP_PYTHON_THREAD_RELEASE() is called by all code paths that exit a function while the thread state is still being held.