Skip to content

Create the OCI environment on first use instead of on every request - #42

Open
vrana wants to merge 1 commit into
php:mainfrom
vrana:lazy-oci-env
Open

vrana wants to merge 1 commit into
php:mainfrom
vrana:lazy-oci-env

Conversation

@vrana

@vrana vrana commented Sep 8, 2026

Copy link
Copy Markdown

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.

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 <noreply@anthropic.com>
vrana added a commit to vrana/adminer that referenced this pull request Sep 8, 2026
Both projects pass without a single extension specific branch.

On Windows this needs php/pecl-database-pdo_oci#42, without it a PHP
process with pdo_oci loaded makes the children it spawns crash if they
load sqlsrv, which breaks every Composer script:
php/pecl-database-pdo_oci#42

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant