From d8abd1a67a302c4dde4ace91052d6188beb292da Mon Sep 17 00:00:00 2001 From: Jakub Vrana Date: Tue, 8 Sep 2026 08:56:04 +0200 Subject: [PATCH] Create the OCI environment on first use instead of on every request PHP_RINIT_FUNCTION() called OCIEnvCreate() on every request in every process that had the extension enabled, even when no Oracle connection was ever opened. Initializing the Oracle client library is not free and, on Windows, has process-wide side effects: any child process spawned afterwards crashes with an access violation (0xC0000005) at exit if it loads sqlsrv or pdo_sqlsrv. That makes merely enabling pdo_oci break unrelated tooling -- every Composer script fails, because Composer spawns children. oci8 does not have this problem because it initializes the client only when a connection is actually opened. This restores that parity: pdo_oci_Env is used only from pdo_oci_handle_factory(), so creating it there is enough. This does not fix the underlying access violation, which reproduces with oci8 alone once a real connection has been made. It removes the case where a process pays the cost and the side effects without ever using Oracle. Deferring the call out of MINIT is preserved -- connections are still opened during a request, so NLS_LANG is available under php-fpm. Tested on Windows 11 x64 with PHP 8.5.8 NTS VS17 x64 and an Oracle Database 21c Express Edition client, against a DLL built by this repository's CI (php/php-windows-builder, artifact php_pdo_oci-8.5-nts-vs17-x86_64). The reproduction is a parent script that spawns a child with a php.ini enabling only sqlsrv: $d = array(0 => array("pipe","r"), 1 => array("pipe","w"), 2 => array("pipe","w")); $p = proc_open('php -c child.ini -r "echo 7;"', $d, $pipes); fclose($pipes[0]); stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); echo proc_close($p), "\n"; The child prints 7 correctly either way; only its exit code differs. before after child spawned, no Oracle connection -1073741819 0 composer check (3 runs) -1073741819 0 composer test -1073741819 0 child spawned after a real connection -1073741819 -1073741819 The last row is the underlying conflict this commit does not address; it behaves identically with oci8 once oci_new_connect() has run. PDO_OCI itself is unaffected: connecting with oci:dbname=//localhost:1521/XEPDB1;charset=AL32UTF8 and running SELECT 1 FROM DUAL works before and after. Co-Authored-By: Claude Opus 5 --- oci_driver.c | 3 +++ pdo_oci.c | 15 +++++++++------ php_pdo_oci.h | 2 -- php_pdo_oci_int.h | 2 ++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/oci_driver.c b/oci_driver.c index 7216fc8..97f0dc5 100644 --- a/oci_driver.c +++ b/oci_driver.c @@ -741,6 +741,9 @@ static int pdo_oci_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ * H->prefetch = PDO_OCI_PREFETCH_DEFAULT; + /* create the true global environment on first use */ + pdo_oci_init_env(); + /* allocate an environment */ #ifdef HAVE_OCIENVNLSCREATE if (vars[0].optval) { diff --git a/pdo_oci.c b/pdo_oci.c index 49bc846..904848f 100644 --- a/pdo_oci.c +++ b/pdo_oci.c @@ -43,7 +43,7 @@ zend_module_entry pdo_oci_module_entry = { NULL, PHP_MINIT(pdo_oci), PHP_MSHUTDOWN(pdo_oci), - PHP_RINIT(pdo_oci), + NULL, NULL, PHP_MINFO(pdo_oci), PHP_PDO_OCI_VERSION, @@ -101,7 +101,7 @@ PHP_MINIT_FUNCTION(pdo_oci) return FAILURE; } - // Defer OCI init to PHP_RINIT_FUNCTION because with php-fpm, + // Defer OCI init to pdo_oci_init_env() because with php-fpm, // NLS_LANG is not yet available here. #ifdef ZTS @@ -112,8 +112,13 @@ PHP_MINIT_FUNCTION(pdo_oci) } /* }}} */ -/* {{{ PHP_RINIT_FUNCTION */ -PHP_RINIT_FUNCTION(pdo_oci) +/* {{{ pdo_oci_init_env + * Create the true global OCI environment on first use. + * Called from pdo_oci_handle_factory() rather than from MINIT (NLS_LANG is not + * yet available there under php-fpm) or from RINIT (initializing the Oracle + * client library has process-wide side effects, so a process that never opens + * an Oracle connection must not pay for it). */ +void pdo_oci_init_env(void) { if (!pdo_oci_Env) { #ifdef ZTS @@ -126,8 +131,6 @@ PHP_RINIT_FUNCTION(pdo_oci) tsrm_mutex_unlock(pdo_oci_env_mutex); #endif } - - return SUCCESS; } /* }}} */ diff --git a/php_pdo_oci.h b/php_pdo_oci.h index 7a44b67..8e15f0a 100644 --- a/php_pdo_oci.h +++ b/php_pdo_oci.h @@ -29,8 +29,6 @@ extern zend_module_entry pdo_oci_module_entry; PHP_MINIT_FUNCTION(pdo_oci); PHP_MSHUTDOWN_FUNCTION(pdo_oci); -PHP_RINIT_FUNCTION(pdo_oci); -PHP_RSHUTDOWN_FUNCTION(pdo_oci); PHP_MINFO_FUNCTION(pdo_oci); #endif /* PHP_PDO_OCI_H */ diff --git a/php_pdo_oci_int.h b/php_pdo_oci_int.h index dd513ff..04c462d 100644 --- a/php_pdo_oci_int.h +++ b/php_pdo_oci_int.h @@ -91,6 +91,8 @@ extern const ub4 PDO_OCI_INIT_MODE; extern const pdo_driver_t pdo_oci_driver; extern OCIEnv *pdo_oci_Env; +void pdo_oci_init_env(void); + ub4 _oci_error(OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); #define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) #define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__)