php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #43497 OCI8 XML/getClobVal leaks UGA memory
Submitted: 2007-12-04 13:09 UTC Modified: 2008-01-16 13:30 UTC
From: ghosh at q-one dot com Assigned: sixd (profile)
Status: Closed Package: OCI8 related
PHP Version: 5.2.5 OS: Linux 2.6.22-14-server
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: ghosh at q-one dot com
New email:
PHP Version: OS:

 

 [2007-12-04 13:09 UTC] ghosh at q-one dot com
Description:
------------
There is a memory leaking when using the OCI8 interface and querying 
XML columns. Demo code available via the url below. This creates a 
table with an XML column and queries this column. UGA memory is 
leaking. This does not happen when doing the same directly via 
SQLPlus.

Reproduce code:
---------------
http://oberon.q-one-hosting.com/6648051.txt

Expected result:
----------------
No UGA memory leaking

Actual result:
--------------
UGA memory going up. Table with temporary lobs filling up.

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2007-12-05 23:18 UTC] sixd@php.net
Confirmed.
 [2007-12-20 18:04 UTC] ghosh at q-one dot com
Would pay someone who resolves this bug. Feel free to contact me if you are interested.
 [2007-12-27 21:44 UTC] sixd@php.net
This is really an issue with temporary LOBS since getClobVal()
returns a temporary LOB.

There are two parts to the fix: changing the script and patching
the OCI8 extension.  Also don't forget to apply the patch for
http://bugs.php.net/bug.php?id=42496

Please test this suggestion and report any issues.

Thanks to Krishna & Shankar for the solution.

1. Change the test to get the results as LOBs, not as
strings. This allows the script to free temporary LOBs.

In the supplied testcase change:

    $query = "select extract(xml, '/').getclobval() from ugatest";
    $stmt = oci_parse($conn, $query);
    if (oci_execute($stmt))
        while ($result = oci_fetch_array($stmt, OCI_NUM+OCI_RETURN_LOBS))
            ;

to:

    $query = "select extract(xml, '/').getclobval() from ugatest";
    $stmt = oci_parse($conn, $query);
    if (oci_execute($stmt))
        while ($result = oci_fetch_array($stmt, OCI_NUM)) {
            // echo $result[0]->load(), "\n"; // do something with the XML
            $result[0]->free();  // free the temporary LOB
        }

The connection must be open when LOB->free() is called, as the
underlying OCILobFreeTemporary() call does a roundtrip to the
database.


2.  Patch oci8_lob.c.  The change copies some LOB freeing code
from php_oci_lob_close() into php_oci_lob_free():

--- oci8_lob.c.orig    2007-07-31 12:21:08.000000000 -0700
+++ oci8_lob.c    2007-12-27 12:33:19.000000000 -0800
@@ -647,6 +647,9 @@
  Close LOB descriptor and free associated resources */
 void php_oci_lob_free (php_oci_descriptor *descriptor TSRMLS_DC)
 {
+#ifdef HAVE_OCI8_TEMP_LOB
+    int is_temporary;
+#endif
 
     if (!descriptor || !descriptor->connection) {
         return;
@@ -662,6 +665,40 @@
         php_oci_lob_flush(descriptor, OCI_LOB_BUFFER_FREE TSRMLS_CC);
     }
 
+#ifdef HAVE_OCI8_TEMP_LOB
+    if (descriptor->type == OCI_DTYPE_LOB) {
+        PHP_OCI_CALL_RETURN(descriptor->connection->errcode,
+                OCILobIsTemporary,
+                (
+                    descriptor->connection->env,
+                    descriptor->connection->err,
+                    descriptor->descriptor,
+                    &is_temporary
+                 )
+        );
+        if (descriptor->connection->errcode != OCI_SUCCESS) {
+            php_oci_error(descriptor->connection->err, descriptor->connection->errcode TSRMLS_CC);
+            PHP_OCI_HANDLE_ERROR(descriptor->connection, descriptor->connection->errcode);
+            return 1;
+        }
+        if (is_temporary) {
+            PHP_OCI_CALL_RETURN(descriptor->connection->errcode,
+                    OCILobFreeTemporary,
+                    (
+                        descriptor->connection->svc,
+                        descriptor->connection->err,
+                        descriptor->descriptor
+                     )
+            );
+            if (descriptor->connection->errcode != OCI_SUCCESS) {
+                php_oci_error(descriptor->connection->err, descriptor->connection->errcode TSRMLS_CC);
+                PHP_OCI_HANDLE_ERROR(descriptor->connection, descriptor->connection->errcode);
+                return 1;
+            }
+        }
+    }
+#endif
+
     PHP_OCI_CALL(OCIDescriptorFree, (descriptor->descriptor, descriptor->type));
 
     zend_list_delete(descriptor->connection->rsrc_id);

 [2007-12-29 22:37 UTC] ghosh at q-one dot com
Really great! Thanks a lot!! This patch works. What I don't understand: I thought OCI_RETURN_LOBS is just a short-cut for those who don't want to write:

$s=$result[0]->load();
$result[0]->free();
$result[0]=$s;

If you use OCI_RETURN_LOBS you dont want to care about lobs but get the result as a string and forget about lobs altogether. So IMHO this should work as well. My specific problem is solved though.
 [2008-01-06 20:42 UTC] tony2001@php.net
>What I don't understand: I thought OCI_RETURN_LOBS is just a short-
>cut for those who don't want to write:

That's what I don't understand either: does the leak appear only on per-session basis or Oracle doesn't free those LOBs at all?
If the leak is only per-session, then users are not supposed even to notice it, since PHP requests are not supposed to take more than several seconds.
 [2008-01-06 23:17 UTC] ghosh at q-one dot com
Temporary LOBs are created in UGA memory. This is per-session, so 
the leak appears on a per-session basis. Nevertheless this is a 
problem, because PHP scripts dont necessarily have to run for a few 
seconds. PHP is a full-featured scripting language and can also be 
used from the command-line or to implement longer-running 
import-scripts. Even if not, the limit is quickly reached, when 
reading many rows like in my example.
 [2008-01-15 02:30 UTC] sixd@php.net
An enhanced patch was sent to the bug filer and Tony on 8th Jan.  I'm still waiting for feedback . . .
 [2008-01-15 17:30 UTC] ghosh at q-one dot com
Sorry. I have been on vacation. I will try to check the new patch 
this week if possible. Definitely next week otherwise.
 [2008-01-16 00:09 UTC] sixd@php.net
This bug has been fixed in CVS.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.
 
Thank you for the report, and for helping us make PHP better.


+----------------------------------------------------------
| Fixed in 5.2.6, 5.3, 6.  Queries with OCI_RETURN_LOBS will
| automatically free the DB's Temporary LOBs.  Fetches
| returning LOB locators need to use LOB->free().  See
| oci8/tests/bug43497.phpt for examples.
+-----------------------------------------------------------

 [2008-01-16 13:30 UTC] ghosh at q-one dot com
Confirmed. Fixed. OCI_RETURN_LOBS are freed automatically now. 
Thanks a lot to everyone involved!
 
PHP Copyright © 2001-2025 The PHP Group
All rights reserved.
Last updated: Thu Jan 30 10:01:30 2025 UTC