Table of Contents

PHP RFC: Add support for stream-wrapped URLs in opcode cache

Introduction

This RFC proposes a way to opcode-cache the PHP code generated via a stream wrapper.

It is an extension of the 'is_cacheable' concept developed in a previous RFC.

When stream wrappers were introduced in PHP, their relationship with opcode caches was not a problem, as they were mostly used to access remote data. The need for a better interaction arose with package systems, like phar and phk, as these sytems use stream wrappers to expose virtual trees of PHP code. If the scripts distributed by such systems cannot be opcode-cached, they loose a great part of their value.

Historically, the issue was addressed using different workarounds :

  1. every URL may be considered as opcode-cacheable, whatever wrapper it is coming from. This approach is wrong because most URLs are intrinsically dynamic and NOT cacheable.
  2. In order to avoid this, opcache implements another workaround : 'file' and 'phar' are hardcoded in the opcache code as the only 'cacheable' stream wrappers. Obviously, such tight coupling cannot be considered as a satisfactory solution.

So, I consider that we need a more generic system, which must allow any PHP code handled via a stream wrapper ('core' or 'userspace') to be opcode-cached.

Proposal

My previous RFC proposed the implementation of an 'is_cacheable' operation, returning a boolean value. This implied that the cache key to use would always be the URL itself. Some users expressed concerns that the stream wrapper should also have the power to determine the cache key. So, this is what I am now proposing :

Using a (zend_string *) for input and output allows the mechanism to be very fast because, in most cases, when an URL is cacheable, the key to use will be the path itself. Look at the phar example :

/**
 * Called by the opcode cache to get the key to use when caching this URL
 */
static zend_string *phar_wrapper_cache_key(php_stream_wrapper *wrapper
    , zend_string *url, int options, php_stream_context *context) /* {{{ */
{
	/* Phar URLs are always cacheable */
	zend_string_addref(url);
	return url;
}
/* }}} */

In order to ensure key unicity, the returned string must start with the same '<scheme>://' prefix as the input URL. For performance reasons, this is checked in debug mode only on C-level wrappers.

The stream wrapper has the responsibility to ensure that the data associated with a given key will always be the same. If this is not the case (e.g. if a 'non-cacheable' URL is declared as 'cacheable'), PHP won't fail but, from a user's point of view, the behavior will be unpredictable.

Backward Incompatible Changes

None

Proposed PHP Version(s)

7.2

RFC Impact

To SAPIs

None

To Existing Extensions

Phar implements a new cache_key() operation, always returning the input path.

The same for the plain files wrapper.

No other core extension needs to be modified.

When 3rd-party extensions decide to have the benefit of the new feature, they will just implement an additional 'cache_key' operation. This is not mandatory and only stream wrappers distributing PHP source code have a reason to do that. In order to remain compatible with previous PHP versions, the declaration for this operation must be enclosed in an appropriate '#if PHP_API_VERSION >= 20160731' block.

Userspace stream wrappers may just define a new 'cache_key()' method. On previous PHP versions, this method will never be called, but won't harm.

To Opcache

Opcode cache must implement the following logic :

These changes are NOT included yet in the PR below.

New Constants

None

Open Issues

Unaffected PHP Functionality

Stream wrappers not using the feature are not modified in any way.

Proposed Voting Choices

Required majority: 50%+1

Patches and Tests

Pull Request: https://github.com/php/php-src/pull/1711

This PR includes every modification described in this RFC, except changes to the opcache code. It includes phar and plain files cache_key() handlers and contains a set of tests.

PHP documentation additions (file_cache_key(), streamWrapper::cache_key()) not written yet.

Implementation

References

Rejected Features