概要
ほとんど気にならないレベルだけど一応バグです…
#72629 Caught exception assignment to variables ignores references
<?php
$var = null;
$e = &$var;
try {
throw new Exception;
} catch (Exception $e) { }
var_dump($var === $e); // bool(false)
報告から10分で修正されました!laruence氏仕事速すぎワロタ
Fixed bug #72629 (Caught exception assignment to variables ignores references)
困りそうなとき
cURLによるリクエストが内在するコルーチンをジェネレータで処理するmpyw/coというライブラリがあるんですが,それを利用して「ある処理が終わった時に止めたいタイマー」のような物を作りたくて,こういうコードを書いたんです.
use mpyw\Co\Co;
use mpyw\Co\CURLException;
class TerminatedException extends \RuntimeException {}
Co::wait(function () {
try {
yield [timer($e), main()];
} catch (TerminatedException $e) {
var_dump('Terminated.');
}
});
function curl_init_with($url, array $options = [CURLOPT_RETURNTRANSFER => true])
{
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function timer(&$e)
{
$ms = 0;
while (true) {
yield CO::DELAY => 0.2;
if ($e) return;
$ms += 200;
echo "[Timer]: $ms miliseconds passed\n";
}
}
function main()
{
var_dump(array_map('strlen', yield [
'Content-Length of github.com' => curl_init_with('https://github.com/mpyw'),
'Content-Length of twitter.com' => curl_init_with('https://twitter.com/mpyw'),
]));
throw new TerminatedException;
}
これでちゃんとタイマーが止まってくれるかと思ったんですが,スローされた後も止まらないんですね.ここでバグ気づいて
try {
yield [timer($e), main()];
- } catch (TerminatedException $e) {
+ } catch (TerminatedException $_) {
+ $e = $_;
var_dump('Terminated.');
}
としたところ直りました…
「そもそもスローされた段階ですべてのyield
中の処理を止めるべきじゃないの?」と思われる方もいるかもしれませんが,JavaScriptにおけるtj/coでも同じような挙動だったので,そこまでする必要はないとしてこれに関する修正はやめました.
ただし,ルートレベル,すなわちCo::wait()
のコールスタックまで這い上がってきた例外に関しては,オプションでthrow
が無効にされていない場合には,即座にスローするように変更しました.これを行っておかないと,使われない返り値のためにすべての処理が遅延させられることになるので.なお,ルートレベルでないものに関しては元から特に問題はありません.
Generator does not stop in spite of RuntimeException thrown into parent scope #11
余談
そもそも例外使う必要なかったですねw
- function main()
+ function main(&$e)
{
var_dump(array_map('strlen', yield [
'Content-Length of github.com' => curl_init_with('https://github.com/mpyw'),
'Content-Length of twitter.com' => curl_init_with('https://twitter.com/mpyw'),
]));
- throw new TerminatedException;
+ $e = true;
}
こういうふうにして,呼び出し側で
yield [timer($e), main($e)];
として変数$e
を共有するだけで良さそうです.