PHP Elephant by scaldra2 from https://www.flickr.com/photos/7237499@N02/6458472309/sizes/l/. Used under a Creative Commons Attribution-NonCommercial-NoDerivs 2.0 Generic (CC BY-NC-ND 2.0) license.

Upgrading to PHP 7.1

Two weeks ago, I noted that I was preparing to switch from PHP 7.0 to 7.1. It took me a bit more time than expected, thanks to a segmentation fault that appeared in 7.1 when using OPcache.

Much of the process follows what I wrote about in 2015 when I initially switched to 7.0. Since then, the only notable change is the addition of the GeoIP module alongside Redis.

Building PHP 7.1

Download the latest source from http://php.net/downloads.php and expand the archive. Next, configure the build; in my case:

./configure --prefix=/usr/local/php7/7.1.0 --with-pdo-pgsql --with-zlib-dir --with-freetype-dir --enable-mbstring --with-libxml-dir=/usr --enable-soap --enable-calendar --with-curl --with-mcrypt --with-zlib --with-gd --with-pgsql --disable-rpath --enable-inline-optimization --with-bz2 --with-zlib --enable-sockets --enable-sysvsem --enable-sysvshm --enable-pcntl --enable-mbregex --enable-exif --enable-bcmath --with-mhash --enable-zip --with-pcre-regex --with-pdo-mysql --with-mysqli --with-mysql-sock=/var/run/mysqld/mysqld.sock --with-jpeg-dir=/usr --with-png-dir=/usr --with-webp-dir --enable-gd-native-ttf --with-openssl --with-fpm-user=www-data --with-fpm-group=www-data --with-libdir=/lib/x86_64-linux-gnu --enable-ftp --with-imap --with-imap-ssl --with-kerberos --with-gettext --with-xmlrpc --with-xsl --enable-opcache --enable-fpm --enable-intl --enable-mysqlnd --with-pear --with-gmp --with-tidy

Note: added --with-gmp on March 21, 2017 to satisfy a new piece of software’s dependency, while --with-tidy was added the following day.

The build can now be compiled:

make
make install

Building modules

Both Redis and GeoIP are available as PECL modules for 7.1: https://pecl.php.net/package/redis/3.1.0 and https://pecl.php.net/package/geoip/1.1.1. Since I need to maintain PHP 5.6 alongside 7.x, I don’t use the PECL installer; instead, I follow the “Compiling shared PECL extensions with phpize” approach.

Redis

Download the archive from https://pecl.php.net/get/redis and expand it, then prepare and build the module:

cd redis
/usr/local/php7/7.1.0/bin/phpize
./configure --prefix=/usr/local/php7/phpredis --with-php-config=/usr/local/php7/7.1.0/bin/php-config --enable-redis
make
make install

GeoIP

Download the archive from https://pecl.php.net/get/geoip and expand it, then prepare and build the module:

cd geoip
/usr/local/php7/7.1.0/bin/phpize
./configure --prefix=/usr/local/php7/geoip --with-php-config=/usr/local/php7/7.1.0/bin/php-config
make
make install

Switching Versions

As I described in “Hello PHP 7!,” I use a symlink from /usr/local/php7/7.0.0 to point to the latest build in the 7.x family. In hindsight, I should’ve named the symlink simply 7, or perhaps 7.x, but I didn’t and I’m loathe to change it now. Symlinks in /usr/local/bin, in cron entries, and in myriad other places now point to that symlink to invoke PHP 7, so I’ll live with my decision until I migrate to a new server instance.

Before updating the symlink, it’s wise to stop PHP-FPM otherwise you’ll need to manually kill the old PHP 7 processes.

/etc/init.d/php7 stop
rm /usr/local/php7/7.0.0
ln -s /usr/local/php7/7.1.0 /usr/local/php7/7.0.0
/etc/init.d/php7 start

Debugging the segfault

Shortly after switching to 7.1, Jetpack’s POST requests to the xmlrpc.php endpoint began failing with a segmentation fault: child exited on signal 11 (SIGSEGV). I left PHP 7.1 in place, as no other requests were impacted, which allowed me to grab a core dump as described at https://bugs.php.net/bugs-generating-backtrace.php. From that, I retrieved the following backtrace:

#0  _zend_hash_add_or_update_i (flag=1, pData=0x7fbaf5361400, key=0x7fbaf5364458, ht=0x7fbaf0299e00) at /usr/local/src/php-7.1.0/Zend/zend_hash.c:600
#1  _zend_hash_update (ht=0x7fbaf0299e00, key=0x7fbaf5364458, pData=0x7fbaf5361400) at /usr/local/src/php-7.1.0/Zend/zend_hash.c:627
#2  0x0000000000901eb1 in ZEND_ADD_ARRAY_ELEMENT_SPEC_CONST_CONST_HANDLER (execute_data=0x7fbaf64157a0) at /usr/local/src/php-7.1.0/Zend/zend_vm_execute.h:5884
#3  0x0000000000903db8 in execute_ex (ex=<optimized out>) at /usr/local/src/php-7.1.0/Zend/zend_vm_execute.h:432
#4  0x000000000095aa10 in zend_execute (op_array=op_array@entry=0x7fbaf646d000, return_value=return_value@entry=0x0) at /usr/local/src/php-7.1.0/Zend/zend_vm_execute.h:474
#5  0x00000000008b7fe4 in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, file_count=file_count@entry=3) at /usr/local/src/php-7.1.0/Zend/zend.c:1474
#6  0x0000000000856640 in php_execute_script (primary_file=primary_file@entry=0x7ffca4bc3680) at /usr/local/src/php-7.1.0/main/main.c:2533
#7  0x0000000000461df6 in main (argc=<optimized out>, argv=<optimized out>) at /usr/local/src/php-7.1.0/sapi/fpm/fpm/fpm_main.c:1967

A bit of Googling and a review of my php7.ini indicated that OPcache was likely to blame. To confirm this, I disabled OPcache for all PHP 7 requests by modifying php7.ini, then undid this change after the segfaults stopped. I then modified WordPress Core’s xmlrpc.php to disable OPcache for requests to it, by adding ini_set( 'opcache.enable', 0 ); to the top of the file; again, I removed this modification after the segfaults stopped.

With the cause confirmed, I modified my nginx configuration to disable OPcache for Core’s XML-RPC requests. My configuration is already modular, which made this rather straightforward. To an existing common include, I added the following:

# PHP 7.1 segfaults with Opcache
location = /xmlrpc.php {
	fastcgi_param PHP_ADMIN_VALUE "opcache.enable=0";
	include common/wp-php.conf;
}

Since testing and reloading nginx, no segmentation faults have been logged for php-fpm.

If I can sort out how to reproduce the issue in a self-contained way, I’ll report this to https://bugs.php.net/, but it’s not clear to me what the conflict is, just where it occurred and how to avoid it. Either way, I’ll need to remember to disable the above fix after each subsequent PHP 7.1 release, in case the issue is resolved independently.

3 thoughts on “Upgrading to PHP 7.1”

Leave a Reply