sixd                                     Wed, 06 Jan 2010 18:58:16 +0000

Revision: http://svn.php.net/viewvc?view=revision&revision=293180

Log:
Fixed bug #49560 (oci8: using LOBs causes slow PHP shutdown)
 - Improved descriptor refcounting to remove unneeded items sooner
 - Replaced n^2 list traversal during descriptor list destruction

Bug: http://bugs.php.net/49560 (Assigned) OCI_RETURN_LOBS causes php_shutdown 
to take very long
      
Changed paths:
    U   php/php-src/branches/PHP_5_3/NEWS
    U   php/php-src/branches/PHP_5_3/ext/oci8/oci8.c
    U   php/php-src/branches/PHP_5_3/ext/oci8/oci8_interface.c
    U   php/php-src/branches/PHP_5_3/ext/oci8/oci8_lob.c
    U   php/php-src/branches/PHP_5_3/ext/oci8/oci8_statement.c
    U   php/php-src/branches/PHP_5_3/ext/oci8/php_oci8_int.h
    U   php/php-src/branches/PHP_5_3/ext/oci8/tests/bug43497.phpt
    A   php/php-src/branches/PHP_5_3/ext/oci8/tests/lob_043.phpt
    U   php/php-src/trunk/ext/oci8/oci8.c
    U   php/php-src/trunk/ext/oci8/oci8_interface.c
    U   php/php-src/trunk/ext/oci8/oci8_lob.c
    U   php/php-src/trunk/ext/oci8/oci8_statement.c
    U   php/php-src/trunk/ext/oci8/php_oci8_int.h
    U   php/php-src/trunk/ext/oci8/tests/bug43497.phpt
    A   php/php-src/trunk/ext/oci8/tests/lob_043.phpt

Modified: php/php-src/branches/PHP_5_3/NEWS
===================================================================
--- php/php-src/branches/PHP_5_3/NEWS	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/branches/PHP_5_3/NEWS	2010-01-06 18:58:16 UTC (rev 293180)
@@ -16,6 +16,7 @@
 - Fixed bug #50632 (filter_input() does not return default value if the
   variable does not exist). (Ilia)
 - Fixed bug #50576 (XML_OPTION_SKIP_TAGSTART option has no effect). (Pierrick)
+- Fixed bug #49560 (oci8: using LOBs causes slow PHP shutdown). (Oracle Corp.)
 - Fixed bug #48590 (SoapClient does not honor max_redirects). (Sriram)
 - Fixed bug #48190 (Content-type parameter "boundary" is not case-insensitive
   in HTTP uploads). (Ilia)
@@ -23,7 +24,6 @@
   (Ilia, chrisstocktonaz at gmail dot com)
 - Fixed bug #44827 (define() allows :: in constant names). (Ilia)

-
 ?? ??? 20??, PHP 5.3.2
 - Upgraded bundled sqlite to version 3.6.21. (Ilia)
 - Upgraded bundled PCRE to version 8.00. (Scott)

Modified: php/php-src/branches/PHP_5_3/ext/oci8/oci8.c
===================================================================
--- php/php-src/branches/PHP_5_3/ext/oci8/oci8.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/branches/PHP_5_3/ext/oci8/oci8.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -1530,23 +1530,21 @@
 }
 /* }}} */

-/* {{{ php_oci_descriptor_delete_from_hash()
+/* }}} */
+
+/* {{{ php_oci_connection_descriptors_free()
  *
- * Delete descriptor from the hash
+ * Free descriptors for a connection
  */
-int php_oci_descriptor_delete_from_hash(void *data, void *id TSRMLS_DC)
+void php_oci_connection_descriptors_free(php_oci_connection *connection TSRMLS_DC)
 {
-	php_oci_descriptor *descriptor = *(php_oci_descriptor **)data;
-	int *desc_id = (int *) id;
-
-	if (descriptor && desc_id && descriptor->id == *desc_id) {
-		return 1;
-	}
-	return 0;
+	zend_hash_destroy(connection->descriptors);
+	efree(connection->descriptors);
+	connection->descriptors = NULL;
+	connection->descriptor_count = 0;
 }
 /* }}} */

-/* }}} */

 /* {{{ php_oci_error()
  *
@@ -2271,9 +2269,7 @@
 	}

 	if (connection->descriptors) {
-		zend_hash_destroy(connection->descriptors);
-		efree(connection->descriptors);
-		connection->descriptors = NULL;
+		php_oci_connection_descriptors_free(connection TSRMLS_CC);
 	}

 	if (connection->svc) {

Modified: php/php-src/branches/PHP_5_3/ext/oci8/oci8_interface.c
===================================================================
--- php/php-src/branches/PHP_5_3/ext/oci8/oci8_interface.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/branches/PHP_5_3/ext/oci8/oci8_interface.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -1066,9 +1066,7 @@
 	PHP_OCI_ZVAL_TO_CONNECTION(z_connection, connection);

 	if (connection->descriptors) {
-		zend_hash_destroy(connection->descriptors);
-		efree(connection->descriptors);
-		connection->descriptors = NULL;
+		php_oci_connection_descriptors_free(connection TSRMLS_CC);
 	}

 	if (php_oci_connection_rollback(connection TSRMLS_CC)) {
@@ -1092,9 +1090,7 @@
 	PHP_OCI_ZVAL_TO_CONNECTION(z_connection, connection);

 	if (connection->descriptors) {
-		zend_hash_destroy(connection->descriptors);
-		efree(connection->descriptors);
-		connection->descriptors = NULL;
+		php_oci_connection_descriptors_free(connection TSRMLS_CC);
 	}

 	if (php_oci_connection_commit(connection TSRMLS_CC)) {

Modified: php/php-src/branches/PHP_5_3/ext/oci8/oci8_lob.c
===================================================================
--- php/php-src/branches/PHP_5_3/ext/oci8/oci8_lob.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/branches/PHP_5_3/ext/oci8/oci8_lob.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -95,9 +95,17 @@
 		if (!connection->descriptors) {
 			ALLOC_HASHTABLE(connection->descriptors);
 			zend_hash_init(connection->descriptors, 0, NULL, php_oci_descriptor_flush_hash_dtor, 0);
+			connection->descriptor_count = 0;
 		}
+
+		descriptor->index = (connection->descriptor_count)++;
+		if (connection->descriptor_count == LONG_MAX) {
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Internal descriptor counter has reached limit");
+			php_oci_connection_descriptors_free(connection TSRMLS_CC);
+			return NULL;
+		}

-		zend_hash_next_index_insert(connection->descriptors,&descriptor,sizeof(php_oci_descriptor *),NULL);
+		zend_hash_index_update(connection->descriptors,descriptor->index,&descriptor,sizeof(php_oci_descriptor *),NULL);
 	}
 	return descriptor;

@@ -669,7 +677,25 @@

 	if (descriptor->connection->descriptors) {
 		/* delete descriptor from the hash */
-		zend_hash_apply_with_argument(descriptor->connection->descriptors, php_oci_descriptor_delete_from_hash, (void *)&descriptor->id TSRMLS_CC);
+		zend_hash_index_del(descriptor->connection->descriptors, descriptor->index);
+		if (zend_hash_num_elements(descriptor->connection->descriptors) == 0) {
+			descriptor->connection->descriptor_count = 0;
+		} else {
+			if (descriptor->index + 1 == descriptor->connection->descriptor_count) {
+				/* If the descriptor being freed is the end-most one
+				 * allocated, then the descriptor_count is reduced so
+				 * a future descriptor can reuse the hash table index.
+				 * This can prevent the hash index range increasing in
+				 * the common case that each descriptor is
+				 * allocated/used/freed before another descriptor is
+				 * needed.  However it is possible that a script frees
+				 * descriptors in arbitrary order which would prevent
+				 * descriptor_count ever being reduced to zero until
+				 * zend_hash_num_elements() returns 0.
+				 */
+				descriptor->connection->descriptor_count--;
+			}
+		}
 	}

 	/* flushing Lobs & Files with buffering enabled */

Modified: php/php-src/branches/PHP_5_3/ext/oci8/oci8_statement.c
===================================================================
--- php/php-src/branches/PHP_5_3/ext/oci8/oci8_statement.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/branches/PHP_5_3/ext/oci8/oci8_statement.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -93,6 +93,7 @@

 	statement->connection = connection;
 	statement->has_data = 0;
+	statement->has_descr = 0;
 	statement->parent_stmtid = 0;
 	zend_list_addref(statement->connection->rsrc_id);

@@ -131,6 +132,40 @@
 }
 /* }}} */

+/* {{{ php_oci_cleanup_pre_fetch()
+   Helper function to cleanup ref-cursors and descriptors from the previous row */
+int php_oci_cleanup_pre_fetch(void *data TSRMLS_DC)
+{
+	php_oci_out_column *outcol = data;
+
+	if (!outcol->is_descr && !outcol->is_cursor)
+		return ZEND_HASH_APPLY_KEEP;
+
+	switch(outcol->data_type) {
+		case SQLT_CLOB:
+		case SQLT_BLOB:
+		case SQLT_RDD:
+		case SQLT_BFILE:
+			if (outcol->descid) {
+				zend_list_delete(outcol->descid);
+				outcol->descid = 0;
+			}
+			break;
+		case SQLT_RSET:
+			if (outcol->stmtid) {
+				zend_list_delete(outcol->stmtid);
+				outcol->stmtid = 0;
+				outcol->nested_statement = NULL;
+			}
+			break;
+		default:
+			break;
+	}
+	return ZEND_HASH_APPLY_KEEP;
+
+} /* }}} */
+
+
 /* {{{ php_oci_statement_fetch()
  Fetch a row from the statement */
 int php_oci_statement_fetch(php_oci_statement *statement, ub4 nrows TSRMLS_DC)
@@ -143,6 +178,10 @@

 	php_oci_out_column *column;

+	if (statement->has_descr && statement->columns) {
+		zend_hash_apply(statement->columns, (apply_func_t) php_oci_cleanup_pre_fetch TSRMLS_CC);
+    }
+
 	PHP_OCI_CALL_RETURN(statement->errcode, OCIStmtFetch, (statement->stmt, statement->err, nrows, OCI_FETCH_NEXT, OCI_DEFAULT));

 	if ( statement->errcode == OCI_NO_DATA || nrows == 0 ) {
@@ -570,6 +609,7 @@

 					define_type = SQLT_RSET;
 					outcol->is_cursor = 1;
+					outcol->statement->has_descr = 1;
 					outcol->storage_size4 = -1;
 					outcol->retlen = -1;
 					dynamic = OCI_DYNAMIC_FETCH;
@@ -583,6 +623,7 @@

 					define_type = outcol->data_type;
 					outcol->is_descr = 1;
+					outcol->statement->has_descr = 1;
 					outcol->storage_size4 = -1;
 					dynamic = OCI_DYNAMIC_FETCH;
 					break;

Modified: php/php-src/branches/PHP_5_3/ext/oci8/php_oci8_int.h
===================================================================
--- php/php-src/branches/PHP_5_3/ext/oci8/php_oci8_int.h	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/branches/PHP_5_3/ext/oci8/php_oci8_int.h	2010-01-06 18:58:16 UTC (rev 293180)
@@ -130,6 +130,7 @@
 	sword			errcode;					/* last errcode */

 	HashTable	   *descriptors;				/* descriptors hash, used to flush all the LOBs using this connection on commit */
+	ulong			descriptor_count;			/* used to index the descriptors hash table.  Not an accurate count */
 	unsigned		is_open:1;					/* hels to determine if the connection is dead or not */
 	unsigned		is_attached:1;				/* hels to determine if we should detach from the server when closing/freeing the connection */
 	unsigned		is_persistent:1;			/* self-descriptive */
@@ -146,6 +147,7 @@

 typedef struct { /* php_oci_descriptor {{{ */
 	int					 id;
+	ulong				 index;		            /* descriptors hash table index */
 	php_oci_connection	*connection;			/* parent connection handle */
 	dvoid				*descriptor;			/* OCI descriptor handle */
 	ub4					 type;					/* descriptor type (FILE/LOB) */
@@ -197,6 +199,7 @@
 	int					 ncolumns;				/* number of columns in the result */
 	unsigned			 executed:1;			/* statement executed flag */
 	unsigned			 has_data:1;			/* statement has more data flag */
+	unsigned			 has_descr:1;			/* statement has at least one descriptor or cursor column */
 	ub2					 stmttype;				/* statement type */
 } php_oci_statement; /* }}} */

@@ -367,8 +370,9 @@
 void php_oci_define_hash_dtor (void *data);
 void php_oci_bind_hash_dtor (void *data);
 void php_oci_descriptor_flush_hash_dtor (void *data);
-int php_oci_descriptor_delete_from_hash(void *data, void *id TSRMLS_DC);

+void php_oci_connection_descriptors_free(php_oci_connection *connection TSRMLS_DC);
+
 sb4 php_oci_error (OCIError *, sword TSRMLS_DC);
 sb4 php_oci_fetch_errmsg(OCIError *, text ** TSRMLS_DC);
 int php_oci_fetch_sqltext_offset(php_oci_statement *, text **, ub2 * TSRMLS_DC);
@@ -452,6 +456,7 @@
 sb4 php_oci_bind_in_callback(dvoid *, OCIBind *, ub4, ub4, dvoid **, ub4 *, ub1 *, dvoid **);
 sb4 php_oci_bind_out_callback(dvoid *, OCIBind *, ub4, ub4, dvoid **, ub4 **, ub1 *, dvoid **, ub2 **);
 php_oci_out_column *php_oci_statement_get_column_helper(INTERNAL_FUNCTION_PARAMETERS, int need_data);
+int php_oci_cleanup_pre_fetch(void *data TSRMLS_DC);

 int php_oci_statement_get_type(php_oci_statement *, ub2 * TSRMLS_DC);
 int php_oci_statement_get_numrows(php_oci_statement *, ub4 * TSRMLS_DC);

Modified: php/php-src/branches/PHP_5_3/ext/oci8/tests/bug43497.phpt
===================================================================
--- php/php-src/branches/PHP_5_3/ext/oci8/tests/bug43497.phpt	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/branches/PHP_5_3/ext/oci8/tests/bug43497.phpt	2010-01-06 18:58:16 UTC (rev 293180)
@@ -157,7 +157,7 @@
     readxmltab_im($c);
 }

-echo "\nExplicit LOB with no free (i.e. a temp lob leak)\n";
+echo "\nExplicit LOB with no free\n";
 for ($i = 1; $i <= 10; $i++) {
     echo "\nRun              = " . $i . "\n";
     echo "Temporary LOBs   = " . templobs($c, $sid) . "\n";
@@ -259,45 +259,45 @@
 Temporary LOBs   = 0
 Loop count check = 100

-Explicit LOB with no free (i.e. a temp lob leak)
+Explicit LOB with no free

 Run              = 1
 Temporary LOBs   = 0
 Loop count check = 100

 Run              = 2
-Temporary LOBs   = 99
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 3
-Temporary LOBs   = 198
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 4
-Temporary LOBs   = 297
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 5
-Temporary LOBs   = 396
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 6
-Temporary LOBs   = 495
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 7
-Temporary LOBs   = 594
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 8
-Temporary LOBs   = 693
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 9
-Temporary LOBs   = 792
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 10
-Temporary LOBs   = 891
+Temporary LOBs   = 0
 Loop count check = 100
 Done

Added: php/php-src/branches/PHP_5_3/ext/oci8/tests/lob_043.phpt
===================================================================
--- php/php-src/branches/PHP_5_3/ext/oci8/tests/lob_043.phpt	                        (rev 0)
+++ php/php-src/branches/PHP_5_3/ext/oci8/tests/lob_043.phpt	2010-01-06 18:58:16 UTC (rev 293180)
@@ -0,0 +1,101 @@
+--TEST--
+Bug #49560 (LOB resource destructor and refcount test)
+--SKIPIF--
+<?php if (!extension_loaded('oci8')) die ("skip no oci8 extension"); ?>
+--FILE--
+<?php
+
+require(dirname(__FILE__).'/connect.inc');
+
+// Initialization
+
+$stmtarray = array(
+	"drop table lob_043_tab",
+	"create table lob_043_tab(id number, c1 clob)",
+	"begin
+       for i in 1..50000 loop
+         insert into lob_043_tab (id, c1) values (i, i || ' abcdefghijklmnopq');
+      end loop;
+     end;",
+);
+
+foreach ($stmtarray as $stmt) {
+	$s = oci_parse($c, $stmt);
+	$r = @oci_execute($s);
+	if (!$r) {
+		$m = oci_error($s);
+		if (!in_array($m['code'], array(   // ignore expected errors
+			    942 // table or view does not exist
+			,  2289 // sequence does not exist
+			,  4080 // trigger does not exist
+                        , 38802 // edition does not exist
+                ))) {
+			echo $stmt . PHP_EOL . $m['message'] . PHP_EOL;
+		}
+	}
+}
+
+// Run Test
+
+function f1($c)
+{
+    $s = oci_parse($c, 'select id, c1 from lob_043_tab order by id');
+    oci_execute($s);
+    $r = array();
+    while (($row = oci_fetch_array($s, OCI_RETURN_NULLS+OCI_ASSOC+OCI_RETURN_LOBS)) !== false) {
+        $r[] = $row['C1'];
+    }
+    echo "f1 ended\n";
+    return $r;
+}
+
+function f2($c)
+{
+    $s = oci_parse($c, 'select id, c1 from lob_043_tab order by id');
+    oci_execute($s);
+    $r = array();
+    while (($row = oci_fetch_array($s, OCI_RETURN_NULLS+OCI_ASSOC)) !== false) {
+        $r[] = $row['C1'];
+    }
+    echo "f2 ended\n";
+    return $r;
+}
+
+echo "Test 1\n";
+$r = f1($c);
+/*
+  foreach ($r as $v) {
+  echo $v, "\n";
+  }
+*/
+
+echo "Test 2\n";
+$r = f2($c);
+/*
+  foreach ($r as $v) {
+  echo $v->load(), "\n";
+  }
+*/
+
+// Clean up
+
+$stmtarray = array(
+	"drop table lob_043_tab"
+);
+
+foreach ($stmtarray as $stmt) {
+	$s = oci_parse($c, $stmt);
+	oci_execute($s);
+}
+
+oci_close($c);
+
+?>
+===DONE===
+<?php exit(0); ?>
+--EXPECTF--
+Test 1
+f1 ended
+Test 2
+f2 ended
+===DONE===


Property changes on: php/php-src/branches/PHP_5_3/ext/oci8/tests/lob_043.phpt
___________________________________________________________________
Added: svn:keywords
   + Id Rev Revision
Added: svn:eol-style
   + native

Modified: php/php-src/trunk/ext/oci8/oci8.c
===================================================================
--- php/php-src/trunk/ext/oci8/oci8.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/trunk/ext/oci8/oci8.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -1368,24 +1368,21 @@
 }
 /* }}} */

-/* {{{ php_oci_descriptor_delete_from_hash()
+/* }}} */
+
+/* {{{ php_oci_connection_descriptors_free()
  *
- * Delete descriptor from the hash
+ * Free descriptors for a connection
  */
-int php_oci_descriptor_delete_from_hash(void *data, void *id TSRMLS_DC)
+void php_oci_connection_descriptors_free(php_oci_connection *connection TSRMLS_DC)
 {
-	php_oci_descriptor *descriptor = *(php_oci_descriptor **)data;
-	int *desc_id = (int *) id;
-
-	if (descriptor && desc_id && descriptor->id == *desc_id) {
-		return 1;
-	}
-	return 0;
+	zend_hash_destroy(connection->descriptors);
+	efree(connection->descriptors);
+	connection->descriptors = NULL;
+	connection->descriptor_count = 0;
 }
 /* }}} */

-/* }}} */
-
 /* {{{ php_oci_error()
  *
  * Fetch & print out error message if we get an error
@@ -2111,9 +2108,7 @@
 	}

 	if (connection->descriptors) {
-		zend_hash_destroy(connection->descriptors);
-		efree(connection->descriptors);
-		connection->descriptors = NULL;
+		php_oci_connection_descriptors_free(connection TSRMLS_CC);
 	}

 	if (connection->svc) {

Modified: php/php-src/trunk/ext/oci8/oci8_interface.c
===================================================================
--- php/php-src/trunk/ext/oci8/oci8_interface.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/trunk/ext/oci8/oci8_interface.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -1106,9 +1106,7 @@
 	PHP_OCI_ZVAL_TO_CONNECTION(z_connection, connection);

 	if (connection->descriptors) {
-		zend_hash_destroy(connection->descriptors);
-		efree(connection->descriptors);
-		connection->descriptors = NULL;
+		php_oci_connection_descriptors_free(connection TSRMLS_CC);
 	}

 	if (php_oci_connection_rollback(connection TSRMLS_CC)) {
@@ -1132,9 +1130,7 @@
 	PHP_OCI_ZVAL_TO_CONNECTION(z_connection, connection);

 	if (connection->descriptors) {
-		zend_hash_destroy(connection->descriptors);
-		efree(connection->descriptors);
-		connection->descriptors = NULL;
+		php_oci_connection_descriptors_free(connection TSRMLS_CC);
 	}

 	if (php_oci_connection_commit(connection TSRMLS_CC)) {

Modified: php/php-src/trunk/ext/oci8/oci8_lob.c
===================================================================
--- php/php-src/trunk/ext/oci8/oci8_lob.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/trunk/ext/oci8/oci8_lob.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -95,9 +95,17 @@
 		if (!connection->descriptors) {
 			ALLOC_HASHTABLE(connection->descriptors);
 			zend_hash_init(connection->descriptors, 0, NULL, php_oci_descriptor_flush_hash_dtor, 0);
+			connection->descriptor_count = 0;
 		}
+
+		descriptor->index = (connection->descriptor_count)++;
+		if (connection->descriptor_count == LONG_MAX) {
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Internal descriptor counter has reached limit");
+			php_oci_connection_descriptors_free(connection TSRMLS_CC);
+			return NULL;
+		}

-		zend_hash_next_index_insert(connection->descriptors,&descriptor,sizeof(php_oci_descriptor *),NULL);
+		zend_hash_index_update(connection->descriptors,descriptor->index,&descriptor,sizeof(php_oci_descriptor *),NULL);
 	}
 	return descriptor;

@@ -672,7 +680,25 @@

 	if (descriptor->connection->descriptors) {
 		/* delete descriptor from the hash */
-		zend_hash_apply_with_argument(descriptor->connection->descriptors, php_oci_descriptor_delete_from_hash, (void *)&descriptor->id TSRMLS_CC);
+		zend_hash_index_del(descriptor->connection->descriptors, descriptor->index);
+		if (zend_hash_num_elements(descriptor->connection->descriptors) == 0) {
+			descriptor->connection->descriptor_count = 0;
+		} else {
+			if (descriptor->index + 1 == descriptor->connection->descriptor_count) {
+				/* If the descriptor being freed is the end-most one
+				 * allocated, then the descriptor_count is reduced so
+				 * a future descriptor can reuse the hash table index.
+				 * This can prevent the hash index range increasing in
+				 * the common case that each descriptor is
+				 * allocated/used/freed before another descriptor is
+				 * needed.  However it is possible that a script frees
+				 * descriptors in arbitrary order which would prevent
+				 * descriptor_count ever being reduced to zero until
+				 * zend_hash_num_elements() returns 0.
+				 */
+				descriptor->connection->descriptor_count--;
+			}
+		}
 	}

 	/* flushing Lobs & Files with buffering enabled */

Modified: php/php-src/trunk/ext/oci8/oci8_statement.c
===================================================================
--- php/php-src/trunk/ext/oci8/oci8_statement.c	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/trunk/ext/oci8/oci8_statement.c	2010-01-06 18:58:16 UTC (rev 293180)
@@ -95,6 +95,7 @@

 	statement->connection = connection;
 	statement->has_data = 0;
+	statement->has_descr = 0;
 	statement->parent_stmtid = 0;
 	zend_list_addref(statement->connection->rsrc_id);

@@ -133,6 +134,40 @@
 }
 /* }}} */

+/* {{{ php_oci_cleanup_pre_fetch()
+   Helper function to cleanup ref-cursors and descriptors from the previous row */
+int php_oci_cleanup_pre_fetch(void *data TSRMLS_DC)
+{
+	php_oci_out_column *outcol = data;
+
+	if (!outcol->is_descr && !outcol->is_cursor)
+		return ZEND_HASH_APPLY_KEEP;
+
+	switch(outcol->data_type) {
+		case SQLT_CLOB:
+		case SQLT_BLOB:
+		case SQLT_RDD:
+		case SQLT_BFILE:
+			if (outcol->descid) {
+				zend_list_delete(outcol->descid);
+				outcol->descid = 0;
+			}
+			break;
+		case SQLT_RSET:
+			if (outcol->stmtid) {
+				zend_list_delete(outcol->stmtid);
+				outcol->stmtid = 0;
+				outcol->nested_statement = NULL;
+			}
+			break;
+		default:
+			break;
+	}
+	return ZEND_HASH_APPLY_KEEP;
+
+} /* }}} */
+
+
 /* {{{ php_oci_statement_fetch()
  Fetch a row from the statement */
 int php_oci_statement_fetch(php_oci_statement *statement, ub4 nrows TSRMLS_DC)
@@ -145,6 +180,10 @@
 	php_oci_out_column *column;
 	zstr zstr_data;

+	if (statement->has_descr && statement->columns) {
+		zend_hash_apply(statement->columns, (apply_func_t) php_oci_cleanup_pre_fetch TSRMLS_CC);
+    }
+
 	PHP_OCI_CALL_RETURN(statement->errcode, OCIStmtFetch, (statement->stmt, statement->err, nrows, OCI_FETCH_NEXT, OCI_DEFAULT));

 	if ( statement->errcode == OCI_NO_DATA || nrows == 0 ) {
@@ -577,6 +616,7 @@

 					define_type = SQLT_RSET;
 					outcol->is_cursor = 1;
+					outcol->statement->has_descr = 1;
 					outcol->storage_size4 = -1;
 					outcol->retlen = -1;
 					dynamic = OCI_DYNAMIC_FETCH;
@@ -590,6 +630,7 @@

 					define_type = outcol->data_type;
 					outcol->is_descr = 1;
+					outcol->statement->has_descr = 1;
 					outcol->storage_size4 = -1;
 					dynamic = OCI_DYNAMIC_FETCH;
 					break;

Modified: php/php-src/trunk/ext/oci8/php_oci8_int.h
===================================================================
--- php/php-src/trunk/ext/oci8/php_oci8_int.h	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/trunk/ext/oci8/php_oci8_int.h	2010-01-06 18:58:16 UTC (rev 293180)
@@ -136,6 +136,7 @@
 	sword			 errcode;					/* last errcode */

 	HashTable		*descriptors;				/* descriptors hash, used to flush all the LOBs using this connection on commit */
+ 	ulong			 descriptor_count;			/* used to index the descriptors hash table.  Not an accurate count */
 	unsigned		 is_open:1;					/* hels to determine if the connection is dead or not */
 	unsigned		 is_attached:1;				/* hels to determine if we should detach from the server when closing/freeing the connection */
 	unsigned		 is_persistent:1;			/* self-descriptive */
@@ -153,6 +154,7 @@

 typedef struct { /* php_oci_descriptor {{{ */
 	int					 id;
+	ulong				 index;		            /* descriptors hash table index */
 	php_oci_connection	*connection;			/* parent connection handle */
 	dvoid				*descriptor;			/* OCI descriptor handle */
 	ub4					 type;					/* descriptor type (FILE/LOB) */
@@ -206,6 +208,7 @@
 	int					 ncolumns;				/* number of columns in the result */
 	unsigned			 executed:1;			/* statement executed flag */
 	unsigned			 has_data:1;			/* statement has more data flag */
+	unsigned			 has_descr:1;			/* statement has at least one descriptor or cursor column */
 	ub2					 stmttype;				/* statement type */
 } php_oci_statement; /* }}} */

@@ -377,8 +380,9 @@
 void php_oci_define_hash_dtor (void *data);
 void php_oci_bind_hash_dtor (void *data);
 void php_oci_descriptor_flush_hash_dtor (void *data);
-int php_oci_descriptor_delete_from_hash(void *data, void *id TSRMLS_DC);

+void php_oci_connection_descriptors_free(php_oci_connection *connection TSRMLS_DC);
+
 sb4 php_oci_error (OCIError *, sword TSRMLS_DC);
 sb4 php_oci_fetch_errmsg(OCIError *, text ** TSRMLS_DC);
 int php_oci_fetch_sqltext_offset(php_oci_statement *, zstr *, ub2 * TSRMLS_DC);
@@ -463,6 +467,7 @@
 sb4 php_oci_bind_in_callback(dvoid *, OCIBind *, ub4, ub4, dvoid **, ub4 *, ub1 *, dvoid **);
 sb4 php_oci_bind_out_callback(dvoid *, OCIBind *, ub4, ub4, dvoid **, ub4 **, ub1 *, dvoid **, ub2 **);
 php_oci_out_column *php_oci_statement_get_column_helper(INTERNAL_FUNCTION_PARAMETERS, int need_data);
+int php_oci_cleanup_pre_fetch(void *data TSRMLS_DC);

 int php_oci_statement_get_type(php_oci_statement *, ub2 * TSRMLS_DC);
 int php_oci_statement_get_numrows(php_oci_statement *, ub4 * TSRMLS_DC);

Modified: php/php-src/trunk/ext/oci8/tests/bug43497.phpt
===================================================================
--- php/php-src/trunk/ext/oci8/tests/bug43497.phpt	2010-01-06 16:22:54 UTC (rev 293179)
+++ php/php-src/trunk/ext/oci8/tests/bug43497.phpt	2010-01-06 18:58:16 UTC (rev 293180)
@@ -157,7 +157,7 @@
     readxmltab_im($c);
 }

-echo "\nExplicit LOB with no free (i.e. a temp lob leak)\n";
+echo "\nExplicit LOB with no free\n";
 for ($i = 1; $i <= 10; $i++) {
     echo "\nRun              = " . $i . "\n";
     echo "Temporary LOBs   = " . templobs($c, $sid) . "\n";
@@ -259,45 +259,45 @@
 Temporary LOBs   = 0
 Loop count check = 100

-Explicit LOB with no free (i.e. a temp lob leak)
+Explicit LOB with no free

 Run              = 1
 Temporary LOBs   = 0
 Loop count check = 100

 Run              = 2
-Temporary LOBs   = 99
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 3
-Temporary LOBs   = 198
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 4
-Temporary LOBs   = 297
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 5
-Temporary LOBs   = 396
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 6
-Temporary LOBs   = 495
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 7
-Temporary LOBs   = 594
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 8
-Temporary LOBs   = 693
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 9
-Temporary LOBs   = 792
+Temporary LOBs   = 0
 Loop count check = 100

 Run              = 10
-Temporary LOBs   = 891
+Temporary LOBs   = 0
 Loop count check = 100
 Done

Added: php/php-src/trunk/ext/oci8/tests/lob_043.phpt
===================================================================
--- php/php-src/trunk/ext/oci8/tests/lob_043.phpt	                        (rev 0)
+++ php/php-src/trunk/ext/oci8/tests/lob_043.phpt	2010-01-06 18:58:16 UTC (rev 293180)
@@ -0,0 +1,101 @@
+--TEST--
+Bug #49560 (LOB resource destructor and refcount test)
+--SKIPIF--
+<?php if (!extension_loaded('oci8')) die ("skip no oci8 extension"); ?>
+--FILE--
+<?php
+
+require(dirname(__FILE__).'/connect.inc');
+
+// Initialization
+
+$stmtarray = array(
+	"drop table lob_043_tab",
+	"create table lob_043_tab(id number, c1 clob)",
+	"begin
+       for i in 1..50000 loop
+         insert into lob_043_tab (id, c1) values (i, i || ' abcdefghijklmnopq');
+      end loop;
+     end;",
+);
+
+foreach ($stmtarray as $stmt) {
+	$s = oci_parse($c, $stmt);
+	$r = @oci_execute($s);
+	if (!$r) {
+		$m = oci_error($s);
+		if (!in_array($m['code'], array(   // ignore expected errors
+			    942 // table or view does not exist
+			,  2289 // sequence does not exist
+			,  4080 // trigger does not exist
+                        , 38802 // edition does not exist
+                ))) {
+			echo $stmt . PHP_EOL . $m['message'] . PHP_EOL;
+		}
+	}
+}
+
+// Run Test
+
+function f1($c)
+{
+    $s = oci_parse($c, 'select id, c1 from lob_043_tab order by id');
+    oci_execute($s);
+    $r = array();
+    while (($row = oci_fetch_array($s, OCI_RETURN_NULLS+OCI_ASSOC+OCI_RETURN_LOBS)) !== false) {
+        $r[] = $row['C1'];
+    }
+    echo "f1 ended\n";
+    return $r;
+}
+
+function f2($c)
+{
+    $s = oci_parse($c, 'select id, c1 from lob_043_tab order by id');
+    oci_execute($s);
+    $r = array();
+    while (($row = oci_fetch_array($s, OCI_RETURN_NULLS+OCI_ASSOC)) !== false) {
+        $r[] = $row['C1'];
+    }
+    echo "f2 ended\n";
+    return $r;
+}
+
+echo "Test 1\n";
+$r = f1($c);
+/*
+  foreach ($r as $v) {
+  echo $v, "\n";
+  }
+*/
+
+echo "Test 2\n";
+$r = f2($c);
+/*
+  foreach ($r as $v) {
+  echo $v->load(), "\n";
+  }
+*/
+
+// Clean up
+
+$stmtarray = array(
+	"drop table lob_043_tab"
+);
+
+foreach ($stmtarray as $stmt) {
+	$s = oci_parse($c, $stmt);
+	oci_execute($s);
+}
+
+oci_close($c);
+
+?>
+===DONE===
+<?php exit(0); ?>
+--EXPECTF--
+Test 1
+f1 ended
+Test 2
+f2 ended
+===DONE===


Property changes on: php/php-src/trunk/ext/oci8/tests/lob_043.phpt
___________________________________________________________________
Added: svn:keywords
   + Id Rev Revision
Added: svn:eol-style
   + native
-- 
PHP CVS Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to