Skip to content

Commit

Permalink
Enable Zend Opcache extension for opcode caching.
Browse files Browse the repository at this point in the history
Build it shared, and move it to the same directory as the node module
binary, so we can dynamically load it from PHP.  We also need to use
the RTLD_GLOBAL flag when loading the node module in order to allow
PHP extensions to use symbols from our embedded PHP.
  • Loading branch information
cscott committed Nov 4, 2015
1 parent b2e835a commit 4050e2b
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# php-embed x.x.x (not yet released)
* Add and enable Opcache extension for opcode caching (performance).

# php-embed 0.5.2 (2015-11-03)
* Ensure PHP private properties aren't writable.
Expand Down
16 changes: 13 additions & 3 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@
'conditions': [
['libphp5 != "internal"', {
'libraries': [ "<!@(php-config --ldflags) -lphp5" ],
'cflags': [ "<!@(php-config --includes)" ]
'cflags': [ "<!@(php-config --includes)" ],
'defines': [ 'EXTERNAL_LIBPHP5' ],
'direct_dependent_settings': {
'php_module_files': [],
},
},
{
'dependencies': [
'deps/libphp5.gyp:libphp5'
]
],
'export_dependent_settings': [
'deps/libphp5.gyp:libphp5'
],
}
]
],
Expand Down Expand Up @@ -45,7 +52,10 @@
"dependencies": [ "<(module_name)" ],
"copies": [
{
"files": [ "<(PRODUCT_DIR)/<(module_name).node" ],
"files": [
"<(PRODUCT_DIR)/<(module_name).node",
">@(_php_module_files)"
],
"destination": "<(module_path)"
}
]
Expand Down
13 changes: 9 additions & 4 deletions deps/libphp5.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
'<(SHARED_INTERMEDIATE_DIR)/php-<@(libphp5_version)/',
'--enable-maintainer-zts', '--enable-embed=static',
'--prefix', '<(SHARED_INTERMEDIATE_DIR)/build',
'--enable-opcache=static',
# opcache can only be built shared
'--enable-opcache=shared',
# mediawiki says this is necessary
'--with-zlib', '--enable-mbstring',
# turn off some unnecessary bits
Expand All @@ -105,7 +106,8 @@
'<(SHARED_INTERMEDIATE_DIR)/php-<@(libphp5_version)/Makefile'
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/build/lib/libphp5.a'
'<(SHARED_INTERMEDIATE_DIR)/build/lib/libphp5.a',
#'<(SHARED_INTERMEDIATE_DIR)/php-<@(libphp5_version)/modules/opcache.so'
],
'action': ['make', '-C', '<(SHARED_INTERMEDIATE_DIR)/php-<@(libphp5_version)/',
'-j', '2', 'all', 'install']
Expand All @@ -119,6 +121,9 @@
'build'
],
'direct_dependent_settings': {
'php_module_files': [
'<(SHARED_INTERMEDIATE_DIR)/php-<@(libphp5_version)/modules/opcache.so',
],
'include_dirs': [
# these match `php-config --includes`
'<(SHARED_INTERMEDIATE_DIR)/build/include/php',
Expand All @@ -132,13 +137,13 @@
'link_settings': {
'libraries': [
'<(SHARED_INTERMEDIATE_DIR)/build/lib/libphp5.a',
'<(SHARED_INTERMEDIATE_DIR)/php-<@(libphp5_version)/modules/opcache.a',
# these match `php-config --libs`
'<(libcrypt) -lresolv -lm -ldl -lxml2'
]
},
'sources': [
'<(SHARED_INTERMEDIATE_DIR)/build/lib/libphp5.a'
'<(SHARED_INTERMEDIATE_DIR)/build/lib/libphp5.a',
#'<(SHARED_INTERMEDIATE_DIR)/php-<@(libphp5_version)/modules/opcache.so'
]
}
]
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var bindingPath =
var bindings = require(bindingPath);
bindings.setIniPath(path.join(__dirname, 'php.ini'));
bindings.setStartupFile(path.join(__dirname, 'startup.php'));
bindings.setExtensionDir(path.dirname(bindingPath));

var packageJson = require('../package.json');
var Promise = require('prfun');
Expand Down
3 changes: 3 additions & 0 deletions lib/php.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
zend_extension=opcache.so
html_errors=1
register_argc_argv=1
implicit_flush=0
max_execution_time=0
max_input_time=-1
output_buffering=4096
opcache.enable=On
opcache.enable_cli=On

; The following are useful for debugging:
;
Expand Down
52 changes: 52 additions & 0 deletions src/node_php_embed.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// Copyright (c) 2015 C. Scott Ananian <cscott@cscott.net>
#include "src/node_php_embed.h"

#include <dlfcn.h> // for dlopen()

#include <string>
#include <unordered_map>

Expand Down Expand Up @@ -37,6 +39,7 @@ using node_php_embed::node_php_jsobject_call_method;
static void node_php_embed_ensure_init(void);

static char *node_php_embed_startup_file;
static char *node_php_embed_extension_dir;

ZEND_DECLARE_MODULE_GLOBALS(node_php_embed);

Expand All @@ -45,6 +48,25 @@ extern zend_module_entry node_php_embed_module_entry;

static int node_php_embed_startup(sapi_module_struct *sapi_module) {
TRACE(">");
// Remove the "hardcoded INI" entries
if (php_embed_module.ini_entries) {
free(php_embed_module.ini_entries);
}
php_embed_module.ini_entries = NULL;

#ifndef EXTERNAL_LIBPHP5
// Add an appropriate "extension_dir" directive.
// (Unless we're linking against an external libphp5.so, in which case
// we'll assume it knows best where its own extensions are.)
if (node_php_embed_extension_dir) {
std::string ini("extension_dir=");
ini += node_php_embed_extension_dir;
ini += "\n";
php_embed_module.ini_entries = strdup(ini.c_str());
}
#endif

// Proceed with startup.
if (php_module_startup(sapi_module, &node_php_embed_module_entry, 1) ==
FAILURE) {
return FAILURE;
Expand Down Expand Up @@ -241,6 +263,17 @@ NAN_METHOD(setStartupFile) {
TRACE("<");
}

NAN_METHOD(setExtensionDir) {
TRACE(">");
REQUIRE_ARGUMENT_STRING(0, ext_dir);
if (node_php_embed_extension_dir) {
free(node_php_embed_extension_dir);
}
node_php_embed_extension_dir =
(*ext_dir) ? strdup(*ext_dir) : nullptr;
TRACE("<");
}

NAN_METHOD(request) {
TRACE(">");
REQUIRE_ARGUMENTS(4);
Expand Down Expand Up @@ -333,18 +366,36 @@ static void node_php_embed_ensure_init(void) {
}
TRACE(">");
node_php_embed_inited = true;
// Module must be opened with RTLD_GLOBAL so that PHP can later
// load extensions. So re-invoke dlopen to twiddle the flags.
if (node_php_embed_extension_dir) {
std::string path(node_php_embed_extension_dir);
path += "/node_php_embed.node";
dlopen(path.c_str(), RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD);
}
// We also have to lie about the sapi name (!) in order to get opcache
// to start up. (Well, we could also patch PHP to fix this.)
char *old_name = php_embed_module.name;
// opcache is unhappy if we deallocate this, so keep it around forever-ish.
static char new_name[] = { "cli" };
php_embed_module.name = new_name;

php_embed_init(0, nullptr PTSRMLS_CC);
// Shutdown the initially-created request; we'll create our own request
// objects inside PhpRequestWorker.
php_request_shutdown(nullptr);
PhpRequestWorker::CheckRequestInfo(TSRMLS_C);
node::AtExit(ModuleShutdown, nullptr);
// Reset the SAPI module name now that all extensions (opcache in
// particular) are loaded.
php_embed_module.name = old_name;
TRACE("<");
}

NAN_MODULE_INIT(ModuleInit) {
TRACE(">");
node_php_embed_startup_file = NULL;
node_php_embed_extension_dir = NULL;
php_embed_module.php_ini_path_override = nullptr;
php_embed_module.php_ini_ignore = true;
php_embed_module.php_ini_ignore_cwd = true;
Expand All @@ -367,6 +418,7 @@ NAN_MODULE_INIT(ModuleInit) {
// Export functions
NAN_EXPORT(target, setIniPath);
NAN_EXPORT(target, setStartupFile);
NAN_EXPORT(target, setExtensionDir);
NAN_EXPORT(target, request);
TRACE("<");
}
Expand Down
11 changes: 11 additions & 0 deletions test/opcache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require('should');

describe('Opcache extension', function() {
var php = require('../');
it('should be loaded', function() {
return php.request({ source: 'opcache_get_status() !== false' })
.then(function(v) {
v.should.equal(true);
});
});
});

0 comments on commit 4050e2b

Please sign in to comment.