Приступил к реализации проверки сборса соединения через поток.
(Ожидаемое поведение: при разрыве соединения с браузером 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;
}
Раскомментирование flush() приводит к ошибке в браузере:
This webpage is not found
No webpage was found for the web address: http://client.d-inform.com/gtk1/trans_list.php
Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found.
Что это значит??
Баг PHP?
PS Пришлось перейти на PHP 5.4.23, т.к 5.3 не компилился:
Bug #65685 PHP 5.3 git fails to compile with ZTS
https://bugs.php.net/bug.php?id=65685
http://grokbase.com/t/php/php-bugs/139g3x2xgw/php-bug-bug-65685-new-php-5-3-git-fails-to-compile-with-zts
Задачу сделал.
Одного байта посылать для проверки соединения не достаточно — информация походит через каскад буферов, в том числе через gzip-паковщих. Эксперимент показал, что в моём случае достаточно 32 байт.
Вот код:
// global variables
$gl_tout = 240; // timeout 240 sec = 4 min
$gl_longsql = "";
function shutdown_mysqli($cause, $tout, $sql = "")
{
global $gl_mysqli1;
if ($gl_mysqli1)
{
$thread_id = $gl_mysqli1->thread_id;
if ($thread_id)
{
$gl_mysqli1->kill($thread_id);
// Note from http://php.ru/manual/mysqli.kill.html :
// Be careful using this before mysqli::close.
// Killing the thread before actually closing the connection will leave the connection open!
// And depending on your max_connections and max_user_connections (by default the same),
// this could result in a "Max connections reached for **** user" message.
}
$gl_mysqli1->close(); // or mysqli_close();
$gl_mysqli1 = null;
// it look's like $gl_mysqli1->kill($thread_id); and closing MySQLi connection above
// doesn't actually KILLs the request sometimes. Why?
if ($thread_id)
{
mysql_query("KILL $thread_id"); // KILLing using $conn - the old default connection
}
// just log event into special table
log_long_query($cause, $tout, $thread_id, $sql );
}
}
register_shutdown_function("shutdown_mysqli", "shutdown", $gl_toutm, $gl_longsql);
function db_query_long($qstring,$conn)
{
global $strLastSQL,$dDebug;
global $gl_mysqli1;
global $gl_tout;
global $gl_longsql; $gl_longsql = $qstring;
if (false) // ($gl_mysqli1==null)
{
return db_query($qstring,$conn); // using old mysql interface
}
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
ob_implicit_flush(true);
ignore_user_abort(false);
for ($i=0; $i<$gl_tout; $i++)
{
$ready = $error = $reject = array($gl_mysqli1);
// $ready[] = $error[] = $reject[] = $gl_mysqli1;
mysqli_poll( $ready,$error,$reject, 0, 1000000); // wait 1 sec
if (count($ready)>0)
{
// ready
$r = $gl_mysqli1->reap_async_query();
if ($r)
{
// normal exit
$gl_longsql = ""; // no log needed
return $r;
}
// some error ??
return $r;
}
if ( count($error)>0 || count($reject)>0 )
{
// error
trigger_error("(" . $gl_mysqli1->connect_errno . ") "
. $gl_mysqli1->connect_error, E_USER_ERROR);
shutdown_mysqli("error", $gl_tout, $qstring);
return null;
}
// test connection
echo str_repeat("\n",32); // was: (0);
flush();
ob_flush();
if (connection_status()!=CONNECTION_NORMAL)
{
shutdown_mysqli("disconnect", $gl_tout, $qstring);
return null;
}
// normal stage, but results not ready yet
// log_long_query("test",0,$gl_mysqli1->thread_id,$qstring);
}
// time over
shutdown_mysqli("time out", $gl_tout, $qstring);
return null;
}
Для непонятливых — соединения старым и новым интерфейсом с MySQL используются параллельно.
Вроде всё работает как надо, но осталось много вопросов, основной: неужели нельзя попроще?
Например, а почему нельзя сделать способом №1, используя Thread? Не баг ли там? Ошибка (вид из браузера): Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found.
Напомню, она возникает если вызвать функию flush в фоновом потоке. Это что так и должно быть? :-O Только что словил ту же ошибку с тем же PHP 5.4.23 на мирной функции readfile, выдающий бинарный файл пользователю (файл, разумеется, на месте). Выяснилось что это происходило из-за его большого размера и при причина соответственно в переполнении какого-то буфера, излечилось if (ob_get_level()) ob_end_clean(); непосредственно перед readfile. Вот такие чудеса.
имхо странная какая то задача, ну оборвалось соединение и Бог с ним, зачем еще что-то такое наворачивать?
Зачем какие-то пассы делать с майскл?!
И еще такой момент — на некоторых моих хостингах буфер PHP не 32 байта а 1 МБ

да-да
на других 100кб, еще на одном 40КБ так что ...
Несмотря на странность задачи я бы предложил ее решать на ПЕРЛ
там можно организовать встречные потоки на ввод и вывод,
также там можно повесить кол-бэк на входной поток от браузера
и там каждые скажем 1 сек или N пришедших байт выдавать порцию на выходной поток
так как перл обычно в cgi режиме и там такая схема как бы родная! и буфера не мешают
на большинстве хостингов.