Merge pull request #443 from v-kaywon/fix_leak
Fix leak in connection and statement resource in SQLSRV driver
This commit is contained in:
commit
a97a2f5880
|
@ -668,7 +668,11 @@ PHP_FUNCTION( sqlsrv_close )
|
|||
if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) {
|
||||
LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r ));
|
||||
}
|
||||
|
||||
|
||||
// when conn_r is first parsed in zend_parse_parameters, conn_r becomes a zval that points to a zend_resource with a refcount of 2
|
||||
// need to DELREF here so the refcount becomes 1 and conn_r can be appropriate destroyed by the garbage collector when it goes out of scope
|
||||
// zend_list_close only destroy the resource pointed to by Z_RES_P( conn_r ), not the zend_resource itself
|
||||
Z_TRY_DELREF_P(conn_r);
|
||||
ZVAL_NULL( conn_r );
|
||||
|
||||
RETURN_TRUE;
|
||||
|
|
|
@ -1376,6 +1376,10 @@ PHP_FUNCTION( sqlsrv_free_stmt )
|
|||
LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle);
|
||||
}
|
||||
|
||||
// when stmt_r is first parsed in zend_parse_parameters, stmt_r becomes a zval that points to a zend_resource with a refcount of 2
|
||||
// need to DELREF here so the refcount becomes 1 and stmt_r can be appropriate destroyed by the garbage collector when it goes out of scope
|
||||
// zend_list_close only destroy the resource pointed to by Z_RES_P( stmt_r ), not the zend_resource itself
|
||||
Z_TRY_DELREF_P(stmt_r);
|
||||
ZVAL_NULL( stmt_r );
|
||||
|
||||
RETURN_TRUE;
|
||||
|
|
File diff suppressed because one or more lines are too long
101
test/pdo_sqlsrv/MsData_UTF8.inc
Normal file
101
test/pdo_sqlsrv/MsData_UTF8.inc
Normal file
File diff suppressed because one or more lines are too long
243
test/pdo_sqlsrv/PDO81_MemoryCheck.phpt
Normal file
243
test/pdo_sqlsrv/PDO81_MemoryCheck.phpt
Normal file
|
@ -0,0 +1,243 @@
|
|||
--TEST--
|
||||
Memory Leakage Test
|
||||
--DESCRIPTION--
|
||||
Checks for memory leaks using memory_get_usage(). memory_get_usage() only tracks the memory that is allocated using
|
||||
emalloc (which only allocate memory in the memory space allocated for the PHP process).
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
include 'MsCommon.inc';
|
||||
|
||||
function MemCheck($noPasses, $noRows1, $noRows2, $startStep, $endStep, $leakThreshold)
|
||||
{
|
||||
include 'MsSetup.inc';
|
||||
|
||||
$testName = "Memory Leakage Check";
|
||||
|
||||
StartTest($testName);
|
||||
|
||||
Setup();
|
||||
|
||||
Trace("Execution setup: $noPasses passes over a table with $noRows1 => ".($noRows1 + $noRows2)." rows.\n");
|
||||
$conn1 = Connect();
|
||||
|
||||
CreateTable($conn1, $tableName);
|
||||
$noRowsInserted = InsertRows($conn1, $tableName, $noRows1);
|
||||
|
||||
// Calibration
|
||||
$phpLeak = RunTest($noPasses, 0, $tableName, $conn1, false, 0);
|
||||
Trace("\n0. Calibration\t - PHP memory leak: $phpLeak bytes\n");
|
||||
|
||||
// Preliminary Execution
|
||||
Trace("\nPreliminary Execution:\n");
|
||||
$drvLeak = 0;
|
||||
for ($j = 0; $j < 2; $j++)
|
||||
{
|
||||
$leak = ExecTest(1, $noRows1, $startStep, $endStep, $tableName, $conn1, (($j % 2) != 0), $phpLeak);
|
||||
if ($leak > $drvLeak)
|
||||
{
|
||||
$drvLeak = $leak;
|
||||
}
|
||||
}
|
||||
$totalLeak = 0;
|
||||
|
||||
// Execution
|
||||
$noRows = $noRows1;
|
||||
$prepared = false;
|
||||
Trace("\nActual Execution:\n");
|
||||
for ($j = 0; $j < 4; $j++)
|
||||
{
|
||||
switch ($j)
|
||||
{
|
||||
case 0:
|
||||
$prepared = false;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$prepared = false;
|
||||
InsertRows($conn1, $tableName, $noRows2);
|
||||
$noRows += $noRows2;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$prepared = true;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$prepared = true;
|
||||
InsertRows($conn1, $tableName, $noRows2);
|
||||
$noRows += $noRows2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
$leak = ExecTest($noPasses, $noRows, $startStep, $endStep, $tableName, $conn1, $prepared, $phpLeak) - $drvLeak;
|
||||
if ($leak > $totalLeak)
|
||||
{
|
||||
$totalLeak = $leak;
|
||||
}
|
||||
}
|
||||
|
||||
$conn1 = null;
|
||||
|
||||
$conn2 = Connect();
|
||||
DropTable($conn2, $tableName);
|
||||
$conn2 = null;
|
||||
|
||||
if ($totalLeak > 0)
|
||||
{
|
||||
$expectedLeak = min($drvLeak, $leakThreshold) * $noPasses;
|
||||
Trace("Driver memory leak: $totalLeak bytes (max expected: $expectedLeak)\n");
|
||||
if ($totalLeak > $expectedLeak)
|
||||
{
|
||||
die("Memory leaks detected: $totalLeak bytes\n");
|
||||
}
|
||||
}
|
||||
|
||||
EndTest($testName);
|
||||
}
|
||||
|
||||
function ExecTest($noPasses, $noRows, $startStep, $endStep, $tableName, $conn, $prepared, $phpLeak)
|
||||
{
|
||||
$leak = 0;
|
||||
|
||||
// Execution
|
||||
if ($prepared)
|
||||
{
|
||||
Trace("\nPrepared Query Mode\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace("\nDirect Query Mode\n");
|
||||
}
|
||||
for ($i = $startStep; $i <= $endStep; $i++)
|
||||
{
|
||||
switch ($i)
|
||||
{
|
||||
case 0: // Calibration
|
||||
Trace("$i. Calibration\t - ");
|
||||
break;
|
||||
|
||||
case 1: // connection only
|
||||
Trace("$i. Connection\t - ");
|
||||
break;
|
||||
|
||||
case 2: // query
|
||||
Trace("$i. Query\t - ");
|
||||
break;
|
||||
|
||||
case 3: // fetch
|
||||
Trace("$i. Fetch\t - ");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$memLeak = RunTest($noPasses, $noRows, $tableName, $conn, $prepared, $i) - $phpLeak;
|
||||
Trace("Driver memory leak: $memLeak bytes\n");
|
||||
if ($memLeak > $leak)
|
||||
{
|
||||
$leak = $memLeak;
|
||||
}
|
||||
}
|
||||
|
||||
return ($leak);
|
||||
}
|
||||
|
||||
function RunTest($noPasses, $noRows, $tableName, $conn, $prepared, $mode)
|
||||
{
|
||||
$leak = 0;
|
||||
for ($k = 1; $k <= $noPasses; $k++)
|
||||
{
|
||||
$tsql = "SELECT * FROM [$tableName]";
|
||||
$memStart = 0;
|
||||
$memEnd = 0;
|
||||
$conn2 = null;
|
||||
$stmt = null;
|
||||
$row = null;
|
||||
$rowCount = 0;
|
||||
$fldCount = 0;
|
||||
|
||||
$memStart = memory_get_usage();
|
||||
switch ($mode)
|
||||
{
|
||||
case 0: // calibration
|
||||
break;
|
||||
|
||||
case 1: // connection
|
||||
$conn2 = GetConnection();
|
||||
unset($conn2);
|
||||
break;
|
||||
|
||||
case 2: // query
|
||||
$stmt = ExecuteQueryEx($conn, $tsql, ($prepared ? false : true));
|
||||
$fldCount = $stmt->columnCount();
|
||||
$stmt->closeCursor();
|
||||
unset($stmt);
|
||||
break;
|
||||
|
||||
case 3: // fetch
|
||||
$stmt = ExecuteQueryEx($conn, $tsql, ($prepared ? false : true));
|
||||
$fldCount = $stmt->columnCount();
|
||||
while ($row = $stmt->fetch())
|
||||
{
|
||||
unset($row);
|
||||
$rowCount++;
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
unset($stmt);
|
||||
if ($rowCount != $noRows)
|
||||
{
|
||||
die("$rowCount rows retrieved instead of $noRows\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
$memEnd = memory_get_usage();
|
||||
if ($memEnd > $memStart)
|
||||
{
|
||||
$leak += ($memEnd - $memStart);
|
||||
}
|
||||
|
||||
}
|
||||
return ($leak);
|
||||
}
|
||||
|
||||
function GetConnection()
|
||||
{
|
||||
include 'MsSetup.inc';
|
||||
$conn = PDOConnect('PDO', $server, $uid, $pwd, true);
|
||||
return ($conn);
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Repro
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
function Repro()
|
||||
{
|
||||
try
|
||||
{
|
||||
MemCheck(20, 10, 15, 1, 3, 0);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
echo $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Repro();
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Test "Memory Leakage Check" completed successfully.
|
393
test/sqlsrv/TC81_MemoryCheck.phpt
Normal file
393
test/sqlsrv/TC81_MemoryCheck.phpt
Normal file
|
@ -0,0 +1,393 @@
|
|||
--TEST--
|
||||
Memory Leakage Test
|
||||
--DESCRIPTION--
|
||||
Checks for memory leaks using memory_get_usage(). memory_get_usage() only tracks the memory that is allocated using
|
||||
emalloc (which only allocate memory in the memory space allocated for the PHP process).
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
include 'MsCommon.inc';
|
||||
|
||||
function MemCheck($noPasses, $noRows1, $noRows2, $startStep, $endStep)
|
||||
{
|
||||
include 'MsSetup.inc';
|
||||
|
||||
$testName = "Memory Leakage Check";
|
||||
|
||||
StartTest($testName);
|
||||
|
||||
Setup();
|
||||
|
||||
Trace("Execution setup: $noPasses passes over a table with $noRows1 => ".($noRows1 + $noRows2)." rows.\n");
|
||||
|
||||
// The data added into the table has some UTF-8 characters in it.
|
||||
// The fetch functions in the switch block below fail if we don't
|
||||
// set the encoding to UTF-8. We can set the UTF-8 option elsewhere
|
||||
// (in the options for sqlsrv_fetch for example) but it is easier
|
||||
// to simply call ConnectUTF8().
|
||||
$conn1 = ConnectUTF8();
|
||||
SetUTF8Data(true);
|
||||
|
||||
CreateTable($conn1, $tableName);
|
||||
$noRowsInserted = InsertRows($conn1, $tableName, $noRows1);
|
||||
|
||||
|
||||
// Calibration
|
||||
// when fetching DateTime in the test, the DateTime PHP extension is used, and memory is allocated when this
|
||||
// is extension is first used. Thus create a new DateTime and release it in the calibration step so it won't
|
||||
// appear to be a leak in the testing step.
|
||||
$date = new DateTime();
|
||||
unset($date);
|
||||
$phpLeak = RunTest($noPasses, 0, $tableName, $conn1, false, true, 0);
|
||||
Trace("\n0. Calibration\t - PHP memory leak: $phpLeak bytes\n");
|
||||
|
||||
// Preliminary Execution
|
||||
Trace("\nPreliminary Execution:\n");
|
||||
$drvLeak = ExecTest(1, $noRows1, $startStep, $endStep, $tableName, $conn1, false, true, $phpLeak);
|
||||
$totalLeak = 0;
|
||||
|
||||
// Connection & Query
|
||||
$start = Max($startStep, 1);
|
||||
$end = Min($endStep, 3);
|
||||
Trace("\nConnection & Direct Query Execution:\n");
|
||||
$leak = ExecTest($noPasses, $noRows1, $start, $end, $tableName, $conn1, false, true, $phpLeak) - $drvLeak;
|
||||
if ($leak > $totalLeak)
|
||||
{
|
||||
$totalLeak = $leak;
|
||||
}
|
||||
|
||||
Trace("\nPrepared Query Execution:\n");
|
||||
$start = Max($startStep, 2);
|
||||
$leak = ExecTest($noPasses, $noRows1, $start, $end, $tableName, $conn1, true, true, $phpLeak) - $drvLeak;
|
||||
if ($leak > $totalLeak)
|
||||
{
|
||||
$totalLeak = $leak;
|
||||
}
|
||||
|
||||
// Execution
|
||||
$noRows = $noRows1;
|
||||
$start = Max($startStep, 4);
|
||||
$end = Min($endStep, 7);
|
||||
$prepared = false;
|
||||
$release = false;
|
||||
for ($j = 0; $j < 8; $j++)
|
||||
{
|
||||
switch ($j)
|
||||
{
|
||||
case 0:
|
||||
$prepared = false;
|
||||
$release = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$prepared = true;
|
||||
$release = true;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$prepared = false;
|
||||
$release = false;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$prepared = true;
|
||||
$release = false;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
InsertRows($conn1, $tableName, $noRows2);
|
||||
$noRows = $noRows1 + $noRows2;
|
||||
$prepared = false;
|
||||
$release = false;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$prepared = true;
|
||||
$release = false;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$prepared = false;
|
||||
$release = true;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
$prepared = true;
|
||||
$release = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
if ($prepared)
|
||||
{
|
||||
Trace("\nPrepared Query");
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace("\nDirect Query");
|
||||
}
|
||||
if ($release)
|
||||
{
|
||||
Trace(" with statement release:\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace(" without statement release:\n");
|
||||
}
|
||||
$leak = ExecTest($noPasses, $noRows, $start, $end, $tableName, $conn1, $prepared, $release, $phpLeak) - $drvLeak;
|
||||
if ($leak > $totalLeak)
|
||||
{
|
||||
$totalLeak = $leak;
|
||||
}
|
||||
}
|
||||
|
||||
sqlsrv_close($conn1);
|
||||
|
||||
$conn2 = Connect();
|
||||
DropTable($conn2, $tableName);
|
||||
sqlsrv_close($conn2);
|
||||
SetUTF8Data(false);
|
||||
|
||||
if ($totalLeak > 0)
|
||||
{
|
||||
die("Memory leaks detected: $totalLeak bytes\n");
|
||||
}
|
||||
|
||||
EndTest($testName);
|
||||
}
|
||||
|
||||
function GetConnection()
|
||||
{
|
||||
include 'MsSetup.inc';
|
||||
$conn = sqlsrv_connect($server, $connectionOptions);
|
||||
return ($conn);
|
||||
}
|
||||
|
||||
function ExecQuery($conn, $tableName, $prepared)
|
||||
{
|
||||
$selectQuery = "SELECT * FROM [$tableName]";
|
||||
$stmt = null;
|
||||
|
||||
if ($prepared)
|
||||
{
|
||||
$stmt = sqlsrv_prepare($conn, $selectQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
$stmt = sqlsrv_query($conn, $selectQuery);
|
||||
}
|
||||
if ($stmt === false)
|
||||
{
|
||||
FatalError("Query execution failed: $selectQuery");
|
||||
}
|
||||
if ($prepared)
|
||||
{
|
||||
if (!sqlsrv_execute($stmt))
|
||||
{
|
||||
FatalError("Query execution failed: $selectQuery");
|
||||
}
|
||||
}
|
||||
|
||||
return ($stmt);
|
||||
}
|
||||
|
||||
function ExecTest($noPasses, $noRows, $startStep, $endStep, $tableName, $conn, $prepared, $release, $phpLeak)
|
||||
{
|
||||
$leak = 0;
|
||||
|
||||
// Execution
|
||||
for ($i = $startStep; $i <= $endStep; $i++)
|
||||
{
|
||||
switch ($i)
|
||||
{
|
||||
case 1: // connection only
|
||||
Trace("$i. Connection\t - ");
|
||||
break;
|
||||
|
||||
case 2: // query with no release
|
||||
Trace("$i. Query\t - ");
|
||||
break;
|
||||
|
||||
case 3: // query with release
|
||||
Trace("$i. Query Freed\t - ");
|
||||
break;
|
||||
|
||||
case 4: // fetch
|
||||
Trace("$i. Simple Fetch\t - ");
|
||||
break;
|
||||
|
||||
case 5: // fetch fields
|
||||
Trace("$i. Fetch Fields\t - ");
|
||||
break;
|
||||
|
||||
case 6: // fetch array
|
||||
Trace("$i. Fetch Array\t - ");
|
||||
break;
|
||||
|
||||
case 7: // fetch object
|
||||
Trace("$i. Fetch Object\t - ");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$memLeak = RunTest($noPasses, $noRows, $tableName, $conn, $prepared, $release, $i) - $phpLeak;
|
||||
Trace("Driver memory leak: $memLeak bytes\n");
|
||||
if ($memLeak > 0)
|
||||
{
|
||||
if ($leak <= 0)
|
||||
{
|
||||
$leak = $memLeak;
|
||||
echo intval($leak) . " leaking\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ($leak);
|
||||
}
|
||||
|
||||
function RunTest($noPasses, $noRows, $tableName, $conn, $prepared, $release, $mode)
|
||||
{
|
||||
$memStart = memory_get_usage();
|
||||
for ($k = 1; $k <= $noPasses; $k++)
|
||||
{
|
||||
$conn2 = null;
|
||||
$stmt = null;
|
||||
$fld = null;
|
||||
$rowCount = 0;
|
||||
$numFields = 0;
|
||||
$i = 0;
|
||||
|
||||
switch ($mode)
|
||||
{
|
||||
case 0: // calibration
|
||||
break;
|
||||
|
||||
case 1: // no release
|
||||
$conn2 = GetConnection();
|
||||
sqlsrv_close($conn2);
|
||||
break;
|
||||
|
||||
case 2: // query with no release
|
||||
$stmt = ExecQuery($conn, $tableName, $prepared);
|
||||
break;
|
||||
|
||||
case 3: // query with release
|
||||
$stmt = ExecQuery($conn, $tableName, $prepared);
|
||||
sqlsrv_free_stmt($stmt);
|
||||
break;
|
||||
|
||||
case 4: // fetch
|
||||
$stmt = ExecQuery($conn, $tableName, $prepared);
|
||||
while (sqlsrv_fetch($stmt))
|
||||
{
|
||||
$rowCount++;
|
||||
}
|
||||
if ($release)
|
||||
{
|
||||
sqlsrv_free_stmt($stmt);
|
||||
}
|
||||
if ($rowCount != $noRows)
|
||||
{
|
||||
die("$rowCount rows retrieved instead of $noRows\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // fetch fields
|
||||
$stmt = ExecQuery($conn, $tableName, $prepared);
|
||||
$numFields = sqlsrv_num_fields($stmt);
|
||||
while (sqlsrv_fetch($stmt))
|
||||
{
|
||||
$rowCount++;
|
||||
for ($i = 0; $i < $numFields; $i++)
|
||||
{
|
||||
$fld = sqlsrv_get_field($stmt, $i);
|
||||
if ($fld === false)
|
||||
{
|
||||
die("Field $i of row $rowCount is missing");
|
||||
}
|
||||
unset($fld);
|
||||
}
|
||||
}
|
||||
if ($release)
|
||||
{
|
||||
sqlsrv_free_stmt($stmt);
|
||||
}
|
||||
if ($rowCount != $noRows)
|
||||
{
|
||||
die("$rowCount rows retrieved instead of $noRows\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 6: // fetch array
|
||||
$stmt = ExecQuery($conn, $tableName, $prepared);
|
||||
While (sqlsrv_fetch_array($stmt))
|
||||
{
|
||||
$rowCount++;
|
||||
}
|
||||
if ($release)
|
||||
{
|
||||
sqlsrv_free_stmt($stmt);
|
||||
}
|
||||
if ($rowCount != $noRows)
|
||||
{
|
||||
die("$rowCount rows retrieved instead of $noRows\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case 7: // fetch object
|
||||
$stmt = ExecQuery($conn, $tableName, $prepared);
|
||||
While (sqlsrv_fetch_object($stmt))
|
||||
{
|
||||
$rowCount++;
|
||||
}
|
||||
if ($release)
|
||||
{
|
||||
sqlsrv_free_stmt($stmt);
|
||||
}
|
||||
if ($rowCount != $noRows)
|
||||
{
|
||||
die("$rowCount rows retrieved instead of $noRows\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
// need unset to trigger the destruction of a zval with refcount of 0
|
||||
unset($conn2);
|
||||
unset($stmt);
|
||||
}
|
||||
$memEnd = memory_get_usage();
|
||||
Trace( intval($memEnd) . " - " . intval($memStart) . "\n" );
|
||||
return ($memEnd - $memStart);
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Repro
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
function Repro()
|
||||
{
|
||||
try
|
||||
{
|
||||
MemCheck(20, 10, 15, 1, 7);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
echo $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Repro();
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Test "Memory Leakage Check" completed successfully.
|
Loading…
Reference in a new issue