shestero
Новичок
Задача весьма тривиальна:
Через Apache/PHP пользователем производится некий довольно долгий (нормальное время 1-2 минуты, но может и до 5 минут) запрос данных, в данном случае к СУБД MySQL.
Проблема: если пользователь передумал ждать и нажал на кнопку «отмена» либо соединение прекратилось по какой-то другой причине хочется прервать работу скрипта PHP и запроса в MySQL, что бы они бесполезно не тормозили сервер.
Что я узнал:
PHP не умеет определять обрыв соединения с браузером, если ничего не посылать.
Что я перепробовал:
1.a) Переодическая посылка байт в браузер из отдельного потока:
Результат: при раскомментировании flush() скрипт не работает; в браузере ошибка:
Баг? Фича?
1.b) Посылка тестовых байт в основном потоке, запрос — во втором:
То же какая-то ошибка... :-(
2) Думал использовать mysql_unbuffered_query, но не нашёл, как определять, выполнился ли запрос или нет, не вызвав блокирующее чтение.
3) Переписал на MySQLi, сделал асинхронный запрос со сканированием готовности результата в цикле:
(Посылку нуля пришлось заменить на «\n» так как похоже нули портят формат JSON, в котором транспортируются данные в браузер).
Запрос работает, но обрыва соединения не чувствует! Возможно что-то где-то ещё кешируется? Что 10k переводов строки каждую секунду посылать??
А есть ли решения поэлегантней?
Через Apache/PHP пользователем производится некий довольно долгий (нормальное время 1-2 минуты, но может и до 5 минут) запрос данных, в данном случае к СУБД MySQL.
Проблема: если пользователь передумал ждать и нажал на кнопку «отмена» либо соединение прекратилось по какой-то другой причине хочется прервать работу скрипта PHP и запроса в MySQL, что бы они бесполезно не тормозили сервер.
Что я узнал:
PHP не умеет определять обрыв соединения с браузером, если ничего не посылать.
Что я перепробовал:
1.a) Переодическая посылка байт в браузер из отдельного потока:
PHP:
class Ping0 extends Thread {
public function run() {
echo("<!-- 0 -->\n"); // or: echo(0);
//flush();
sleep(1);
}
}
function db_query_long($qstring,$conn)
{
ignore_user_abort(false);
$ping0 = new Ping0();
$ping0->start();
$ret = db_query($qstring,$conn);
// $ping0->stop();
return $ret;
}
Баг? Фича?
1.b) Посылка тестовых байт в основном потоке, запрос — во втором:
PHP:
class T extends Thread {
protected $arg;
protected $conn;
protected $done = false;
protected $res = null;
public function __construct($arg,$conn) {
$this->arg = $arg;
$this->conn = $conn;
}
public function isCompleated() {
return $this->done;
}
public function getResult() {
return $this->res;
}
public function run() {
$this->res = db_query($this->arg,$this->conn);
$this->done = true;
}
}
function db_query_long($qstring,$conn)
{
$tout = 300; // timeout, sec ***
ignore_user_abort(false);
$thread = new T($qstring,$conn);
$thread->start();
for ($i=1; $i<=$tout; $i++)
{
sleep(1);
if ($thread->isCompleated())
{
return $thread->getResult();
}
echo(0);
flush();
}
return false; // timeout
}
2) Думал использовать mysql_unbuffered_query, но не нашёл, как определять, выполнился ли запрос или нет, не вызвав блокирующее чтение.
3) Переписал на MySQLi, сделал асинхронный запрос со сканированием готовности результата в цикле:
PHP:
function shutdown_mysqli()
{
global $gl_mysqli1;
if ($gl_mysqli1)
{
$thread_id = $gl_mysqli1->thread_id;
if ($thread_id)
{
$gl_mysqli1->kill($thread_id);
}
$gl_mysqli1->close();
//mysqli_close();
$gl_mysqli1 = null;
}
}
register_shutdown_function('shutdown_mysqli');
function db_query_long($qstring,$conn)
{
global $strLastSQL,$dDebug;
global $gl_mysqli1;
if (false) // ($gl_mysqli1==null)
{
return db_query($qstring,$conn); // using old mysql interface
}
$tout = 300; // timeout 300 sec = 5 min
if ($dDebug===true)
echo $qstring."<br>";
$strLastSQL=$qstring;
$r = $gl_mysqli1->query($qstring, MYSQLI_ASYNC ); // MYSQLI_USE_RESULT );
// $thread_id = $gl_mysqli1->thread_id; // checked: has right value
ignore_user_abort(false);
for ($i=0; $i<$tout*2; $i++)
{
$ready = $error = $reject = array($gl_mysqli1);
// $ready[] = $error[] = $reject[] = $gl_mysqli1;
mysqli_poll( $ready,$error,$reject, 0,500000); // wait 1/2 sec
if (count($ready)>0)
{
// ready
$r = $gl_mysqli1->reap_async_query();
if ($r)
{
// normal exit
return $r;
}
// some error
return $r;
}
if ( count($error)>0 || count($reject)>0 )
{
trigger_error("(" . $gl_mysqli1->connect_errno . ") "
. $gl_mysqli1->connect_error, E_USER_ERROR);
// error
return null;
}
// test connection
echo("\n"); // was: (0);
flush();
ob_flush();
if (connection_status()!=CONNECTION_NORMAL)
{
shutdown_mysqli();
return null;
}
}
// time out
return null;
}
Запрос работает, но обрыва соединения не чувствует! Возможно что-то где-то ещё кешируется? Что 10k переводов строки каждую секунду посылать??
А есть ли решения поэлегантней?
Последнее редактирование модератором: