В однопоточном PHP мне нужно написать приложение, которое:
а) и б) должны работать, даже когда сервер ожидает / обслуживает запрос или HTTP-клиент ожидает ответа
У меня возникла идея использовать сервер PHP Amp. Работает отлично.
Однако для HTTP-клиента мне нужно использовать PHP curl.
Мой код выглядит так:
...
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch);
do {
AmpLoopHelper::asyncSleep(0.001);
$mrc = curl_multi_exec($mh, $isRunning);
} while ($isRunning && ($mrc == CURLM_CALL_MULTI_PERFORM || $mrc == CURLM_OK));
...
и обычай AmpLoopHelper
учебный класс:
<?php
namespace Mvorisek\Dsv;
use Amp\Loop;
use Amp\Loop\Driver;
class AmpLoopHelper {
/** @var int|null */
private static $dummyWatcherId;
/**
* Async sleep and keep processing of Loop tasks.<br>
* Loop\Driver::tick() is always called at least once even if
* the sleep delay is zero or negative.
*
* @param float $sleepSecs
*/
public static function asyncSleep(float $sleepSecs): void {
$t = microtime(true);
// add dummy function to repeat to prevent Loop\Driver::tick() to block
$maxCheckDelayMillis = min(max(1, $sleepSecs * 1000 / 50), 50);
$isNested = static::$dummyWatcherId !== null;
if (!$isNested) {
static::$dummyWatcherId = Loop::repeat($maxCheckDelayMillis, static function() {});
}
try {
do {
if (static::loopDriverIsRunning()) {
static::loopDriverTick();
} else {
usleep(($sleepSecs - (microtime(true) - $t)) * 1e6);
break;
}
usleep(40);
} while(microtime(true) - $t < $sleepSecs);
} finally {
if (!$isNested) {
Loop::cancel(static::$dummyWatcherId);
}
}
}
private static function loopDriverGet(): Driver {
return \Closure::bind(static function() {
return Loop::$driver;
}, null, Loop::class)();
}
private static function loopDriverIsRunning(): bool {
$driver = static::loopDriverGet();
return \Closure::bind(static function() use($driver) {
return $driver->running;
}, null, Driver::class)();
}
private static function loopDriverTick(): void {
$driver = static::loopDriverGet();
\Closure::bind(static function() use($driver) {
$driver->tick();
}, null, Driver::class)();
}
}
Но HTTP-сервер иногда недоступен. Вспомогательный класс использует некоторые закрытые методы классов Amp.
Является ли идея asyncSleep
правильный?
Проблема заключалась в том, что Driver->dispatch()
может вызываться с флагом блокировки, и потоки блокировки не были обновлены из моего вспомогательного класса.
Других решений пока нет …