次は PHP 8.1 へ(2)
テスト全滅 orz
4. テスト全滅対応
これまでの修正でビルド(configure, make )を突破できたので、make test で動作チェックを行なわせたところ、
Fatal Error Unable to allocate shared memory segment of 134217728 bytes: shm_open: Invalid argument (22)
|
とのエラーメッセージが出て全テスト項目が失敗してしまいました。どうやら shm_open() でエラー(22:EINVAL)が起きて共有メモリの確保ができないと言っているようですね。
どんな状態で shm_open() が呼び出されているのか gdb で追いかけてみたところ、
$ cd tests/run-test
$ ./test001.sh gdb
GNU gdb 6.3.50-20050815 (Apple version gdb-573) (Fri Oct 20 15:54:33 GMT 2006)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "powerpc-apple-darwin"...Reading symbols for shared libraries .................... done
(gdb) b shm_open
Breakpoint 1 at 0x9001b7c4
(gdb) r
Starting program: /home/hoge/php-8.1.15/sapi/cli/php -n -c /home/hoge/php-8.1.15/tmp-php.ini 〜 -d memory_limit=128M 〜 -f /home/hoge/php-8.1.15/tests/run-test/test001.php
Reading symbols for shared libraries .++....+++.+.++++.++..+.........+...........++ done
Reading symbols for shared libraries . done
Reading symbols for shared libraries . done
Breakpoint 1, 0x9001b7c4 in shm_open ()
(gdb) info reg
r0 0x2698740 40470336
r1 0xbfffcc30 3221212208
r2 0xd2 210
r3 0xbfffccc8 3221212360 ←第一引数
r4 0x602 1538 ←第二引数(O_RDWR|O_CREAT|O_TRUNC)
r5 0x180 384 ←第三引数(0600)
r6 0x38 56
r7 0x64416363 1682006883
r8 0x656c6572 1701602674
r9 0x208 520
r10 0x5 5
r11 0x24000222 603980322
r12 0x9001b7a0 2416031648
r13 0x0 0
r14 0x0 0
r15 0xf 15
r16 0x3102f8f 51392399
r17 0x32b 811
r18 0x22 34
r19 0xd26a6c 13789804
r20 0xbfffcde0 3221212640
r21 0x269cf88 40488840
r22 0x8000000 134217728
r23 0x269c728 40486696
r24 0xbfffcdb8 3221212600
r25 0x0 0
r26 0x180 384
r27 0x602 1538
r28 0xbfffccc8 3221212360
r29 0xbfffccc8 3221212360
r30 0x31fa3c0 52405184
r31 0x9001b7a8 2416031656
pc 0x9001b7c4 2416031684
ps 0x2f030 192560
cr 0x82000222 2181038626
lr 0x9001b7a8 2416031656
ctr 0x9001b7a0 2416031648
xer 0x4 4
mq 0x0 0
fpscr 0x82024000 2181185536
vscr 0x10000 65536
vrsave 0x0 0
(gdb) x/s 0xbfffccc8
0xbfffccc8: "/ZendAccelerator.16228" ←第一引数で渡された共有メモリオブジェクト
(gdb) bt
#0 0x9001b7c4 in shm_open ()
#1 0x02698740 in create_segments ()
#2 0x026963c8 in zend_shared_alloc_try ()
#3 0x02696b9c in zend_shared_alloc_startup ()
#4 0x0267f954 in accel_post_startup ()
#5 0x0038c024 in zend_post_startup ()
#6 0x00317184 in php_module_startup ()
#7 0x0049ad54 in php_cli_startup ()
#8 0x00d36cc4 in main ()
(gdb) c
Continuing.
Sun Feb 12 05:32:55 2023 (2684415368): Fatal Error Unable to allocate shared memory segment of 134217728 bytes: shm_open: Invalid argument (22)
Program exited with code 0376.
|
当該ソースは "ext/opcache/shared_alloc_posix.c" 内の以下の場所です。
static int create_segments(size_t requested_size, zend_shared_segment_posix ***shared_segments_p, int *shared_segments_count, char **error_in)
{
zend_shared_segment_posix *shared_segment;
char shared_segment_name[sizeof("/ZendAccelerator.") + 20];
*shared_segments_count = 1;
*shared_segments_p = (zend_shared_segment_posix **) calloc(1, sizeof(zend_shared_segment_posix) + sizeof(void *));
if (!*shared_segments_p) {
*error_in = "calloc";
return ALLOC_FAILURE;
}
shared_segment = (zend_shared_segment_posix *)((char *)(*shared_segments_p) + sizeof(void *));
(*shared_segments_p)[0] = shared_segment;
sprintf(shared_segment_name, "/ZendAccelerator.%d", getpid());
shared_segment->shm_fd = shm_open(shared_segment_name, O_RDWR|O_CREAT|O_TRUNC, 0600);
if (shared_segment->shm_fd == -1) {
*error_in = "shm_open";
return ALLOC_FAILURE;
}
if (ftruncate(shared_segment->shm_fd, requested_size) != 0) {
*error_in = "ftruncate";
shm_unlink(shared_segment_name);
return ALLOC_FAILURE;
}
shared_segment->common.p = mmap(0, requested_size, PROT_READ | PROT_WRITE, MAP_SHARED, shared_segment->shm_fd, 0);
if (shared_segment->common.p == MAP_FAILED) {
*error_in = "mmap";
shm_unlink(shared_segment_name);
return ALLOC_FAILURE;
}
shm_unlink(shared_segment_name);
shared_segment->common.pos = 0;
shared_segment->common.size = requested_size;
return ALLOC_SUCCESS;
}
|
上記の shm_open() で EINVAL(22) が発生していると言うことになります。EINVAL(Invalid argument) なので shm_open() に無効(Invalid)な引数が指定されたのであり、第一引数に指定した共有メモリオブジェクトの名前が不正である事が原因と考えられるのだけど、見たところ特に問題ないように思えます。もちろん第二引数、第三引数にも正しい値が渡されて来ていますね。
これで行き詰まってしまい、数日間う〜んと唸ってばかりでした。何かがおかしい、何かを見落としているのか?ある時、ふと実機(Mac mini G4)で man shm_open を見たところ、こんな事が書いてあることに気付いたのです。
NAME
shm_open -- open a shared memory object
SYNOPSIS
#include
int
shm_open(const char *name, int flags, mode_t mode);
ERRORS
The named object is opened unless:
[EINVAL] The shm_open() operation is not supported.
|
ウチの Mac OS X では EINVAL は shm_open() がサポートされていないという意味だと?これじゃどうやってもダメじゃないか。では、何故このサポートされていない shm_open() で共有メモリを確保しようとするようになったのでしょうか?以前の PHP ではこのエラーは起きてないが、共有メモリは使っているはずです。
色々とソースを眺めていたところ、"ext/opcache/zend_shared_alloc.c" の中に以下のテーブルがあるのを見つけました。
static const zend_shared_memory_handler_entry handler_table[] = {
#ifdef USE_MMAP
{ "mmap", &zend_alloc_mmap_handlers },
#endif
#ifdef USE_SHM
{ "shm", &zend_alloc_shm_handlers },
#endif
#ifdef USE_SHM_OPEN
{ "posix", &zend_alloc_posix_handlers },
#endif
#ifdef ZEND_WIN32
{ "win32", &zend_alloc_win32_handlers },
#endif
{ NULL, NULL}
};
|
共有メモリ獲得エントリのハンドラテーブルです。このテーブルを上から順に読んで共有メモリ獲得処理を呼び出し、共有メモリ獲得が成功すればそこで獲得処理終了。失敗したら次の共有メモリ獲得処理を呼び出すという仕組みらしいですな。ウチの Mac mini G4 では USE_MMAP と USE_SHM_OPEN の2つが有効になっているはずなので、
zend_alloc_mmap_handlers → "ext/opcache/shared_alloc_mmap.c" の "create_segments()" → mmap()
zend_alloc_posix_handlers → "ext/opcache/shared_alloc_posix.c" の "create_segments()" → shm_open()
という呼び出し経路で共有メモリ獲得が試されます。そして、その何れもが失敗していることになります。
調べたところ、以前の PHPでは最初の mmapルートで共有メモリ獲得が成功しているので、shm_openルートには行かないのだと言う事が分かりました。では、何故 PHP 8.1では mmap() が失敗するようになったのでしょう?
そこで "shared_alloc_mmap.c" の以前のソースとの差分を調べてみたら、
$ diff ./shared_alloc_mmap.c /home/hoge/php-8.0.27/ext/opcache/shared_alloc_mmap.c
32,35d31
< #ifdef __APPLE__
< #include <mach/vm_statistics.h>
< #endif
<
46c42
< int flags = PROT_READ | PROT_WRITE, fd = -1;
---
> int flags = PROT_READ | PROT_WRITE;
49,53c45
< flags |= PROT_MPROTECT(PROT_EXEC);
< #endif
< #ifdef VM_MAKE_TAG
< /* allows tracking segments via tools such as vmmap */
< fd = VM_MAKE_TAG(251U);
---
〜以下略〜
|
何のことはない、PHP 7.4で "Zend/zend_alloc.c" に行ったのと同じ事を、PHP 8.1 のタイミングで "ext/opcache/shared_alloc_mmap.c" にも行ったという事のようです。mmap() の第5引数(ファイルディスクリプタ)に特別な値を指定することで特殊なメモリページ上に共有メモリを獲得できるっぽいのだけど、ウチの Mac OS X でこれをやられると mmap() が失敗してしまいます。単にメモリ領域を獲得したいだけなので、ファイルディスクリプタには -1 を指定するようにようにすれば良い筈。そのためには、
//#ifdef __APPLE__
//#include <mach/vm_statistics.h>
//#endif
|
32行目からの3行を上のようにコメントアウトしてしまえば良いですね。この修正によって正常に共有メモリが獲得できるようになり、テスト項目が全滅なんて事はなくなりました。
とは言うものの、テストが100%成功などと言うことにはならず、いくつかのテスト項目は失敗(FAIL)しています。案の定 fiber機能はほぼダメ。他にもいくつか失敗(FAIL)していますが、使わない機能ならどうってことないよね。ウチの Blogが使っている機能さえ動きゃ良いという強い(?)気持ちで行くしかないですね。
[続く]
|