Skip to content

OCI_RETURN_LOBS flag causes oci8 to leak memory #18873

Closed
php/pecl-database-oci8
#32

Description

@mvorisek

Description

The following code:

$expectedStr = str_repeat('a', 1_001);
$sql = 'select concat(TO_CLOB(\'' . str_repeat('a', 1_000) . '\'), TO_CLOB(\'a\')) AS "v" from "DUAL"';

$memUsages = array_flip(range(0, 100 - 1));
foreach (array_keys($memUsages) as $k) {
    $stid = oci_parse($c, $sql);
    oci_execute($stid);
    $row = oci_fetch_array($stid, \OCI_ASSOC | \OCI_RETURN_LOBS);
    $res = $row['v'];
    if ($res !== $expectedStr) {
        var_dump([$expectedStr, $res]);
        throw new \Exception('unexpected result');
    }

    $memUsages[$k] = memory_get_usage();
}

$memUsages = array_slice($memUsages, 1, null, true);
$memUsages = array_unique($memUsages);

if (count($memUsages) !== 1) {
    var_dump($memUsages);
    throw new \Exception('memory leak detected');
}

echo "ok\n";

https://raw.githubusercontent.com/php/pecl-database-oci8/9a1241341cd02aa68c587046f11b0343bc713b8f/tests/gh-4-return-lob.phpt (2 lines changed of https://raw.githubusercontent.com/php/pecl-database-oci8/9a1241341cd02aa68c587046f11b0343bc713b8f/tests/gh-4.phpt already present test)

Resulted in this output:

array(9) {
  [1]=>
  int(6872016)
  [2]=>
  int(6872368)
  [3]=>
  int(6872400)
  [4]=>
  int(6872432)
  [5]=>
  int(6872464)
  ...
}
PHP Fatal error:  Uncaught Exception: memory leak detected in ...

But I expected this output instead:

ok

Notes:

php/pecl-database-oci8#29 fixed memory leak once after 8th descriptor create, and such fix can be backported into PHP 8.3 however it is not critical and maybe it can be even considered a feature.

This issue is about a clear memory leak after every iteration when the data are fetched using OCI_RETURN_LOBS flag. Such flag imply the data are not returned as descriptor, but as a string instead. So it seems the descriptor is somehow not released after the data are read.

PHP Version

PHP 8.2 - master

Operating System

any (tested linux and Windows)

Activity

  1. mvorisek commented on Jun 19, 2025

    @mvorisek
    ContributorAuthor

    The only usafe of the problematic OCI_RETURN_LOBS flag is in https://github.com/php/php-src/blob/php-8.3.22/ext/oci8/oci8.c#L1571. Any idea what is wrong with this part of code?

  2. SakiTakamachi commented on Jun 27, 2025

    @SakiTakamachi
    Member

    This is a matter of when the descriptor, which is a zend_resource, gets freed.

    First, regardless of the OCI_RETURN_LOBS flag, the descriptor's reference count is decremented in php_oci_column_hash_dtor(). If the reference count is 1 at that point, zend_list_close() is called.

    However, since it is called when the reference count is 1, this alone does not fully destroy the resource. As a result, remnants remain until the end of the script, leading to increased memory usage.

    if (GC_REFCOUNT(column->descid) == 1)

    When the OCI_RETURN_LOBS flag is not used, memory usage remains stable because the descriptor is registered as a property of a zend_object (which increases the reference count by 1). In this case, zend_list_close() is not called in php_oci_column_hash_dtor(), it simply decrements the reference count.

    Subsequently, the descriptor is destroyed along with the destruction of the zend_object, so it does not persist until the end of the script.

    add_property_resource(value, "descriptor", column->descid);

  3. added 3 commits that reference this issue on Jul 3, 2025
    c161bb0
    f512cff
    7fcdf83
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions