simple-swoole / simps

🚀 A simple, lightweight and high-performance PHP coroutine framework.
https://simps.io
Apache License 2.0
471 stars 48 forks source link

Simps\DB\BaseModel 用于协程服务报错 #30

Closed hiwhm closed 3 years ago

hiwhm commented 3 years ago

报错日志

string(19) "onWorkerStart error"
array(4) {
  ["type"]=>
  int(1)
  ["message"]=>
  string(936) "Uncaught PDOException: Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in @swoole-src/library/core/Database/PDOProxy.php:64
Stack trace:
#0 /alidata/www/tournament/vendor/simple-swoole/db/src/BaseModel.php(1352): Swoole\Database\PDOProxy->__call()
#1 /alidata/www/tournament/vendor/simple-swoole/db/src/BaseModel.php(131): Simps\DB\BaseModel->realGetConn()
#2 /alidata/www/tournament/vendor/simple-swoole/db/src/BaseModel.php(491): Simps\DB\BaseModel->exec()
#3 /alidata/www/tournament/app/Gateway.php(104): Simps\DB\BaseModel->get()
#4 /alidata/www/tournament/app/Server.php(480): Game\App\Gateway::getPlayerData()
#5 /alidata/www/tournament/app/Server.php(319): Game\App\Server->joinGameRoom()
#6 {main}
  thrown"
  ["file"]=>
  string(46) "@swoole-src/library/core/Database/PDOProxy.php"
  ["line"]=>
  int(64)
}
errCode: 0, errMsg: Success
string(15) "debug_backtrace"
array(1) {
  [0]=>
  array(5) {
    ["function"]=>
    string(18) "Game\App\{closure}"
    ["class"]=>
    string(15) "Game\App\Server"
    ["object"]=>
    object(Game\App\Server)#3 (1) {
      ["server":"Game\Core\BaseServer":private]=>
      object(Swoole\WebSocket\Server)#2 (26) {
        ["onStart":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(7) "onStart"
        }
        ["onShutdown":"Swoole\Server":private]=>
        NULL
        ["onWorkerStart":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(13) "onWorkerStart"
        }
        ["onWorkerStop":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(12) "onWorkerStop"
        }
        ["onBeforeReload":"Swoole\Server":private]=>
        NULL
        ["onAfterReload":"Swoole\Server":private]=>
        NULL
        ["onWorkerExit":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(12) "onWorkerExit"
        }
        ["onWorkerError":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(13) "onWorkerError"
        }
        ["onTask":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(6) "onTask"
        }
        ["onFinish":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(8) "onFinish"
        }
        ["onManagerStart":"Swoole\Server":private]=>
        array(2) {
          [0]=>
          *RECURSION*
          [1]=>
          string(14) "onManagerStart"
        }
        ["onManagerStop":"Swoole\Server":private]=>
        NULL
        ["onPipeMessage":"Swoole\Server":private]=>
        NULL
        ["setting"]=>
        array(16) {
          ["max_request"]=>
          int(0)
          ["worker_num"]=>
          int(1)
          ["task_worker_num"]=>
          int(2)
          ["dispatch_mode"]=>
          int(5)
          ["hook_flags"]=>
          int(2147479551)
          ["enable_coroutine"]=>
          bool(true)
          ["task_enable_coroutine"]=>
          bool(true)
          ["max_wait_time"]=>
          int(15)
          ["reload_async"]=>
          bool(true)
          ["output_buffer_size"]=>
          int(4294967295)
          ["max_connection"]=>
          int(1024)
          ["open_http_protocol"]=>
          bool(true)
          ["open_mqtt_protocol"]=>
          bool(false)
          ["open_eof_check"]=>
          bool(false)
          ["open_length_check"]=>
          bool(false)
          ["open_websocket_protocol"]=>
          bool(true)
        }
        ["connections"]=>
        object(Swoole\Connection\Iterator)#6 (0) {
        }
        ["host"]=>
        string(7) "0.0.0.0"
        ["port"]=>
        int(9502)
        ["type"]=>
        int(1)
        ["mode"]=>
        int(2)
        ["ports"]=>
        array(1) {
          [0]=>
          object(Swoole\Server\Port)#4 (16) {
            ["onConnect":"Swoole\Server\Port":private]=>
            NULL
            ["onReceive":"Swoole\Server\Port":private]=>
            NULL
            ["onClose":"Swoole\Server\Port":private]=>
            array(2) {
              [0]=>
              *RECURSION*
              [1]=>
              string(7) "onClose"
            }
            ["onPacket":"Swoole\Server\Port":private]=>
            NULL
            ["onBufferFull":"Swoole\Server\Port":private]=>
            NULL
            ["onBufferEmpty":"Swoole\Server\Port":private]=>
            NULL
            ["onRequest":"Swoole\Server\Port":private]=>
            NULL
            ["onHandShake":"Swoole\Server\Port":private]=>
            NULL
            ["onOpen":"Swoole\Server\Port":private]=>
            array(2) {
              [0]=>
              *RECURSION*
              [1]=>
              string(6) "onOpen"
            }
            ["onMessage":"Swoole\Server\Port":private]=>
            array(2) {
              [0]=>
              *RECURSION*
              [1]=>
              string(9) "onMessage"
            }
            ["host"]=>
            string(7) "0.0.0.0"
            ["port"]=>
            int(9502)
            ["type"]=>
            int(1)
            ["sock"]=>
            int(3)
            ["setting"]=>
            array(9) {
              ["max_request"]=>
              int(0)
              ["worker_num"]=>
              int(1)
              ["task_worker_num"]=>
              int(2)
              ["dispatch_mode"]=>
              int(5)
              ["hook_flags"]=>
              int(2147479551)
              ["enable_coroutine"]=>
              bool(true)
              ["task_enable_coroutine"]=>
              bool(true)
              ["max_wait_time"]=>
              int(15)
              ["reload_async"]=>
              bool(true)
            }
            ["connections"]=>
            object(Swoole\Connection\Iterator)#5 (0) {
            }
          }
        }
        ["master_pid"]=>
        int(29448)
        ["manager_pid"]=>
        int(29449)
        ["worker_id"]=>
        int(0)
        ["taskworker"]=>
        bool(false)
        ["worker_pid"]=>
        int(29453)
        ["stats_timer"]=>
        NULL
      }
    }
    ["type"]=>
    string(2) "->"
    ["args"]=>
    array(0) {
    }
  }
}
[2021-08-09 19:30:23 *29453.0]  ERROR   php_swoole_server_rshutdown (ERRNO 503): Fatal error: Uncaught PDOException: Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. in @swoole-src/library/core/Database/PDOProxy.php:64
Stack trace:
#0 /alidata/www/tournament/vendor/simple-swoole/db/src/BaseModel.php(1352): Swoole\Database\PDOProxy->__call()
#1 /alidata/www/tournament/vendor/simple-swoole/db/src/BaseModel.php(131): Simps\DB\BaseModel->realGetConn()
#2 /alidata/www/tournament/vendor/simple-swoole/db/src/BaseModel.php(491): Simps\DB\BaseModel->exec()
#3 /alidata/www/tournament/app/Gateway.php(104): Simps\DB\BaseModel->get()
#4 /alidata/www/tournament/app/Server.php(480): Game\App\Gateway::getPlayerData()
#5 /alidata/www/tournament/app/Server.php(319): Game\App\Server->joinGameRoom()
#6 {main}
  thrown in @swoole-src/library/core/Database/PDOProxy.php on line 64
[2021-08-09 19:30:23 $29449.0]  WARNING check_worker_exit_status: worker#0[pid=29453] abnormal exit, status=255, signal=0
onWorkerError worker_id: 0, worker_pid: 29453, exit_code: 255, signal: 0

PDO::MYSQL_ATTR_USE_BUFFERED_QUERY=true 已开启 BaseModel 实例方法

new BaseModel([
                'host' => $config['host'],
                'port' => $config['port'],
                'database' => $config['database'],
                'username' => $config['username'],
                'password' => $config['password'],
                'charset' => $config['charset'],
                'prefix' => $config['prefix'],
                // 'unixSocket' => null,
                'options' => [
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
                    PDO::ATTR_STRINGIFY_FETCHES => false,
                    PDO::ATTR_EMULATE_PREPARES => false,
                ],
                'size' => $config['pool_size'] // 连接池size
            ]);

swoole 配置

        'max_request'       =>   0, // 设置 worker 进程的最大任务数。【默认值:0 即不会退出进程】
    'worker_num'        =>   1,
        'task_worker_num'   =>   2,
        'dispatch_mode'     =>   5,
        'hook_flags'        => SWOOLE_HOOK_ALL, // 设置一键协程化 Hook 的函数范围。【默认值:不 hook】
        'enable_coroutine'  => true,
        'task_enable_coroutine' => true, // Task 开启协程
        'max_wait_time'     => 15, // 设置 Worker 进程收到停止服务通知后最大等待时间【默认值:3】
        'reload_async'      => true, // 设置异步重启开关, 保证服务重载时,协程或异步任务能正常结束。

我排查了下,发现 quote 方法不能正确的返回,我使用了medoo2.1的 quote处理方法进行替换,发现报错次数有明显减少。 但是以上的报错问题还是存在,我想到的可能是上条查询语句 exec PDO::prepare 实际执行了但没完全返回或者报错,导致后一条语句出现上面问题。

sy-records commented 3 years ago

这里应该是跨协程调用了

hiwhm commented 3 years ago

这里应该是跨协程调用了

这是在 BaseModel 获取连接时的处理

    public function setPdo($pdo) {
        $cuid = Coroutine::getuid();
        if (empty($this->pdos[$cuid])) {
            $this->pdos[$cuid] = $pdo;
        }
    }

    public function getPdo() {
        $cuid = Coroutine::getuid();
        if (empty($this->pdos[$cuid])) {
            echo "BaseModel getPdo null".PHP_EOL;
            return null;
        }
        return $this->pdos[$cuid];
    }

难道是这里的代码没有生效....,在开启一键协程化后,同一cpu时间内有多个连接进来是不是可以用go方法加多一层协程来隔离,或者有更好的解决方法,求思路

hiwhm commented 3 years ago

已找到解决方法, 问题原因可能是一个进程内多个协程共用一个 BaseModel 实例导致的, 解决办法 BaseModel 内区分协程上下文。

mysteriouss commented 2 years ago

每次 new 一个 Model 对象应该就好了吧

我就是掉坑里了, 加了个 单例方法。。