运行环境:
CentOS5.5
nginx0.8.54
PHP5.3.5
MySQL 5.5.8
APC3.1.7

我有一个程序是用MySQL来保存Session的,在没有启用APC扩展的时候工作正常,当启用APC之后就出现了无法写入Session的问题,为便于找出问题,专门写了一段类似的代码,如下:

完整示例: http://code.google.com/p/mixedlab/source/browse/trunk/linux/php/apc_error/session_handler.php

/**
 * Session handler functions
 * @package apc_error
 * @filesource
 */

function session_connection ($func_name='') {
    global $mysql;

    if (!is_object($mysql)) {

        if (class_exists('MySQL')) {
            $mysql = 
                new MySQL(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB);
        } else {
            die('Failed to connect to database for Session[' . 
                $func_name . ']');
        }
    }   
    
    return $mysql;
}

function on_session_write ($key, $val) {

    if (($session_db = session_connection(__FUNCTION__))) {
 if (($session_db = session_connection(__FUNCTION__))) {

        $expire = time() + SESSION_MAX_LIFETIME;

        $sql = "REPLACE INTO " . SESSION_TABLE .
            " VALUES ('" . $key . "', '" . $val . "', '$expire')";

        $session_db->Query($sql);

        if ($session_db->AffectedRows() > 0) {
            return true;
        }
    }

    return false;
}

//其它函数...

session_set_save_handler(
    "on_session_start", "on_session_end",
    "on_session_read", "on_session_write",
    "on_session_destroy", "on_session_gc"
);

session_start();

当启用APC之后就会输出:

Failed to connect to database for Session[on_session_write]

即在写入数据库的时候全局变量$mysql已经不存在,并在试图重新连接数据库的时候发现MySQL类也是未定义状态。

解决方法

最后Google一下,发现早就有人遇到类似的问题(http://pecl.php.net/bugs/bug.php?id=16745),其原因都是一样的:即on_session_write和on_session_end是在销毁之后调用的,在PHP的session_set_save_handler文档的中有如下警告:

As of PHP 5.0.5 the write and close handlers are called after object destruction and therefore cannot use objects or throw exceptions. The object destructors can however use sessions.

It is possible to call session_write_close() from the destructor to solve this chicken and egg problem.

其解决方法是在程序结束之前调用call session_write_close() 函数。

但为什么在没有启用APC的时候没有这样的问题呢?

Execution point of session save handler on shutdown
作出了解释,概括地说原因就是:
APC作为动态扩展总是在Session模块之后加载,而在取消初始化(deinitialization)的时候是反序的,因此APC会在Session模块执行之前将所有类删除,当Session模块运行的on_session_write等函数的时候,需要访问的类已经不存在了。

Reference

http://pecl.php.net/bugs/bug.php?id=16745
http://php.net/manual/en/function.session-set-save-handler.php
http://news.php.net/php.internals/46999