Skip to content

Commit 0615aa8

Browse files
committed
Fix temporary-LOB leak and add tests (Senthil)
1 parent 0787cd6 commit 0615aa8

File tree

6 files changed

+248
-6
lines changed

6 files changed

+248
-6
lines changed

ext/pdo_oci/oci_statement.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@
5353

5454
static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob);
5555

56+
#define OCI_TEMPLOB_CLOSE(envhp, svchp, errhp, lob) \
57+
do \
58+
{ \
59+
boolean isTempLOB; \
60+
OCILobIsTemporary(envhp, errhp, lob, &isTempLOB); \
61+
if (isTempLOB) \
62+
OCILobFreeTemporary(svchp, errhp, lob); \
63+
} while(0)
64+
5665
static int oci_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */
5766
{
5867
pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data;
@@ -99,6 +108,8 @@ static int oci_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */
99108
switch (S->cols[i].dtype) {
100109
case SQLT_BLOB:
101110
case SQLT_CLOB:
111+
OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err,
112+
(OCILobLocator *) S->cols[i].data);
102113
OCIDescriptorFree(S->cols[i].data, OCI_DTYPE_LOB);
103114
break;
104115
default:
@@ -293,7 +304,13 @@ static int oci_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *pa
293304

294305
case PDO_PARAM_EVT_FREE:
295306
P = param->driver_data;
296-
if (P) {
307+
if (P && P->thing) {
308+
OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing);
309+
OCIDescriptorFree(P->thing, OCI_DTYPE_LOB);
310+
P->thing = NULL;
311+
efree(P);
312+
}
313+
else if (P) {
297314
efree(P);
298315
}
299316
break;
@@ -381,7 +398,6 @@ static int oci_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *pa
381398
if (stm) {
382399
OCILobOpen(S->H->svc, S->err, (OCILobLocator*)P->thing, OCI_LOB_READWRITE);
383400
php_stream_to_zval(stm, parameter);
384-
P->thing = NULL;
385401
}
386402
} else {
387403
/* we're a LOB being used for insert; transfer the data now */
@@ -430,6 +446,7 @@ static int oci_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *pa
430446
OCILobClose(S->H->svc, S->err, (OCILobLocator*)P->thing);
431447
}
432448
}
449+
OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing);
433450
OCIDescriptorFree(P->thing, OCI_DTYPE_LOB);
434451
P->thing = NULL;
435452
}

ext/pdo_oci/tests/bug46274.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ var_dump($res->fetch());
5656
$db->exec("DROP TABLE test_one_blob");
5757

5858
?>
59-
--XFAIL--
60-
Corrupts memory
6159
--EXPECTF--
6260
array(2) {
6361
["blob1"]=>

ext/pdo_oci/tests/bug46274_2.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ fclose($row[0]);
6060
$db->exec("DROP TABLE test_one_blob");
6161

6262
?>
63-
--XFAIL--
64-
Corrupts memory
6563
--EXPECTF--
6664
array(2) {
6765
["blob1"]=>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
--TEST--
2+
PDO OCI: Inserts 10K with 1 number and 2 LOB columns (stress test)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_oci')) die('skip not loaded');
6+
if (getenv('SKIP_SLOW_TESTS')) die('skip slow tests excluded by request');
7+
require(dirname(__FILE__).'/../../pdo/tests/pdo_test.inc');
8+
PDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
13+
require(dirname(__FILE__) . '/../../pdo/tests/pdo_test.inc');
14+
15+
$db = PDOTest::factory();
16+
17+
$query = "begin execute immediate 'drop table pdo_oci_stream_2'; exception when others then if sqlcode <> -942 then raise; end if; end;";
18+
$stmt = $db->prepare($query);
19+
$stmt->execute();
20+
21+
$query = "create table pdo_oci_stream_2 (id number, data1 blob, data2 blob)";
22+
$stmt = $db->prepare($query);
23+
$stmt->execute();
24+
25+
function do_insert($db, $id, $data1, $data2)
26+
{
27+
$db->beginTransaction();
28+
$stmt = $db->prepare("insert into pdo_oci_stream_2 (id, data1, data2) values (:id, empty_blob(), empty_blob()) returning data1, data2 into :blob1, :blob2");
29+
$stmt->bindParam(':id', $id);
30+
$stmt->bindParam(':blob1', $blob1, PDO::PARAM_LOB);
31+
$stmt->bindParam(':blob2', $blob2, PDO::PARAM_LOB);
32+
$blob1 = null;
33+
$blob2 = null;
34+
$stmt->execute();
35+
36+
fwrite($blob1, $data1);
37+
fclose($blob1);
38+
fwrite($blob2, $data2);
39+
fclose($blob2);
40+
$db->commit();
41+
}
42+
43+
$a1 = str_repeat('a', 4086);
44+
$a2 = str_repeat('b', 4087);
45+
$a3 = str_repeat('c', 4088);
46+
$a4 = str_repeat('d', 4089);
47+
$a5 = str_repeat('e', 4090);
48+
$a6 = str_repeat('f', 4091);
49+
$a7 = str_repeat('g', 4092);
50+
$a8 = str_repeat('h', 4093);
51+
$a9 = str_repeat('i', 4094);
52+
$a10 = str_repeat('j', 4095);
53+
54+
printf("Inserting 10000 Records ... ");
55+
for($i=0; $i<1000; $i++) {
56+
do_insert($db, 1, $a1, $a10);
57+
do_insert($db, 1, $a2, $a9);
58+
do_insert($db, 1, $a3, $a8);
59+
do_insert($db, 1, $a4, $a7);
60+
do_insert($db, 1, $a5, $a6);
61+
do_insert($db, 1, $a6, $a5);
62+
do_insert($db, 1, $a7, $a4);
63+
do_insert($db, 1, $a8, $a3);
64+
do_insert($db, 1, $a9, $a2);
65+
do_insert($db, 1, $a10, $a1);
66+
}
67+
printf("Done\n");
68+
69+
/* Cleanup is done in pdo_oci_stream_2b.phpt */
70+
//$db->exec("drop table pdo_oci_stream_2");
71+
72+
?>
73+
--EXPECT--
74+
Inserting 10000 Records ... Done
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
--TEST--
2+
PDO OCI: Fetches 10K records from a table that contains 1 number and 2 LOB columns (stress test)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_oci')) die('skip not loaded');
6+
if (getenv('SKIP_SLOW_TESTS')) die('skip slow tests excluded by request');
7+
require(dirname(__FILE__).'/../../pdo/tests/pdo_test.inc');
8+
PDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
13+
// !! Note: uses data inserted in pdo_oci_stream_2a.phpt !!
14+
15+
require('ext/pdo/tests/pdo_test.inc');
16+
$db = PDOTest::test_factory('ext/pdo_oci/tests/common.phpt');
17+
18+
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Let's use streams
19+
20+
// Since each column only has one lob descriptor, the last row is
21+
// shown twice because the lob descriptor for each column is reused in
22+
// the stream
23+
24+
$i = 0;
25+
$j = 9;
26+
$a_val = ord('a');
27+
foreach($db->query("select data1 as d4_1, data2 as d4_2 from pdo_oci_stream_2 order by id") as $row) {
28+
$a = $row['d4_1'];
29+
$a1 = $row['d4_2'];
30+
31+
$str1 = stream_get_contents($a);
32+
$str2 = stream_get_contents($a1);
33+
34+
$str1len = strlen($str1);
35+
$str2len = strlen($str2);
36+
37+
$b = ord($str1[0]);
38+
$b1 = ord($str2[0]);
39+
40+
if (($b != ($a_val + $i)) && ($str1len != (4086 + $i)) &&
41+
($b1 != ($a_val + $j)) && ($str2len != (4086 + $j))) {
42+
printf("There is a bug!\n");
43+
printf("Col1:\n");
44+
printf("a_val = %d\n", $a_val);
45+
printf("b = %d\n", $b);
46+
printf("i = %d\n", $i);
47+
printf("str1len = %d\n", $str1len);
48+
49+
printf("Col2:\n");
50+
printf("a_val = %d\n", $a_val);
51+
printf("b1 = %d\n", $b1);
52+
printf("j = %d\n", $j);
53+
printf("str2len = %d\n", $str1len);
54+
55+
}
56+
$i++;
57+
if ($i>9)
58+
$i = 0;
59+
$j--;
60+
if ($j<0)
61+
$j = 9;
62+
}
63+
echo "Fetch operation done!\n";
64+
65+
/* Cleanup */
66+
$db->exec("drop table pdo_oci_stream_2");
67+
68+
?>
69+
--EXPECTF--
70+
Fetch operation done!
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
--TEST--
2+
PDO OCI: Test to verify all implicitly created temporary LOB are cleaned up
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_oci')) die('skip not loaded');
6+
require(dirname(__FILE__).'/../../pdo/tests/pdo_test.inc');
7+
PDOTest::skip();
8+
?>
9+
--FILE--
10+
<?PHP
11+
12+
require('ext/pdo/tests/pdo_test.inc');
13+
$db = PDOTest::test_factory('ext/pdo_oci/tests/common.phpt');
14+
15+
$clobquery1 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
16+
$clobquery2 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
17+
$clobquery3 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
18+
$clobquery4 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
19+
$clobquery5 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
20+
$clobquery6 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
21+
$clobquery7 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
22+
$clobquery8 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
23+
$clobquery9 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
24+
$clobquery10 = "select TO_CLOB('Hello World') CLOB_DATA from dual";
25+
26+
$stmt= $db->prepare($clobquery1);
27+
$stmt->execute();
28+
$row = $stmt->fetch();
29+
$stmt= $db->prepare($clobquery2);
30+
$stmt->execute();
31+
$row = $stmt->fetch();
32+
$stmt= $db->prepare($clobquery3);
33+
$stmt->execute();
34+
$row = $stmt->fetch();
35+
$stmt= $db->prepare($clobquery4);
36+
$stmt->execute();
37+
$row = $stmt->fetch();
38+
$stmt= $db->prepare($clobquery5);
39+
$stmt->execute();
40+
$row = $stmt->fetch();
41+
$stmt= $db->prepare($clobquery6);
42+
$stmt->execute();
43+
$row = $stmt->fetch();
44+
$stmt= $db->prepare($clobquery7);
45+
$stmt->execute();
46+
$row = $stmt->fetch();
47+
$stmt= $db->prepare($clobquery8);
48+
$stmt->execute();
49+
$row = $stmt->fetch();
50+
$stmt= $db->prepare($clobquery9);
51+
$stmt->execute();
52+
$row = $stmt->fetch();
53+
$stmt= $db->prepare($clobquery10);
54+
$stmt->execute();
55+
$row = $stmt->fetch();
56+
57+
$query1 = "SELECT SYS_CONTEXT('USERENV', 'SID') SID FROM DUAL";
58+
59+
$stmt1 = $db->prepare($query1);
60+
$stmt1->execute();
61+
62+
$row1 = $stmt1->fetch();
63+
$sid_value = $row1[0];
64+
65+
$query2 = "SELECT (CACHE_LOBS+NOCACHE_LOBS+ABSTRACT_LOBS) FROM V\$TEMPORARY_LOBS WHERE SID = :SID_VALUE";
66+
67+
$stmt2 = $db->prepare($query2);
68+
$stmt2->bindParam(':SID_VALUE', $sid_value);
69+
$stmt2->execute();
70+
71+
$row2 = $stmt2->fetch();
72+
/* 1 temporary LOB still exists in V$TEMPORARY_LOBS since the destructor of $stmt is not yet called by PHP */
73+
if ($row2[0] > 1)
74+
{
75+
echo "TEMP_LOB is not yet cleared!" . $row2[0] . "\n";
76+
}
77+
else
78+
{
79+
echo "Success! All the temporary LOB in previously closed statements are properly cleaned.\n";
80+
}
81+
82+
?>
83+
--EXPECTF--
84+
Success! All the temporary LOB in previously closed statements are properly cleaned.
85+

0 commit comments

Comments
 (0)