Description:
------------
Using a FilterIterator on a DirectoryIterator does not close the handle of the directory being iterated.
If I do not use a FilterIterator and process the files manually from the DirectoryIterator, the handle is released.
To see this in action ...
Place a bunch of CSV files in the C:\TEMP directory and create a C:\TEMP\JUNK directory.
The code below will loop until it has 1000 CSV files.
Using System Internals Process Explorer you can see that the number of handles used by php.exe goes up and up and up.
I have several programs which loop over a directory repeatedly. I have stopped using the FilterIterator and use just a DirectoryIterator and then manually filter the filenames.
Reproduce code:
---------------
<?php
class FileTypeList extends FilterIterator
{
protected
$s_Type;
function __construct($s_Path, $s_Type)
{
$this->s_Type = $s_Type;
parent::__construct(new DirectoryIterator($s_Path));
}
function accept()
{
$b_Result = (strcasecmp($this->s_Type, pathinfo($this->current(), PATHINFO_EXTENSION)) !== 0);
return $b_Result;
}
}
$am_Files = array();
while(count($am_Files) < 1000)
{
echo "Looking\n";
foreach(new FileTypeList('C:\\TEMP', 'CSV') as $o_FILE)
{
if ($o_FILE->isFile())
{
$s_FileName = str_replace('/', DIRECTORY_SEPARATOR, $o_FILE->getPathname()); // Directory separator required for windows filenames.
$am_Files[] = array
(
'Modified' => filemtime($s_FileName),
'Name' => $s_FileName,
);
rename($s_FileName, 'C:\\TEMP\\JUNK\\' . basename($s_FileName));
echo "Found $s_FileName\n";
}
}
}
print_r($am_Files);
?>
Expected result:
----------------
File handles to open and close as required.
Actual result:
--------------
File handles are opened and stay open. System resources get used up.

Patches

Pull Requests

History

Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.php.net/manual/ and the instructions on how to report
a bug at http://bugs.php.net/how-to-report.php
First you must explicitly destruct objects holding resources to free the resources.
Second instead of "'Modified' => filemtime($s_FileName),
" you should be using "$o_File->getATime()" or getMTime() or getCTime().
Sidenote there is no reason to use \ instead / unless you were generating batch files. If you are using \ because of any PHP function then you should open a bug report for that.

[2006-01-16 09:39 UTC] rquadling at gmail dot com

>>"First you must explicitly destruct objects holding resources to free the resources."
I understand the need to free the resources, but none of the examples of implementing a filter on a directoryiterator have any destruction code and the DirectoryIterator does not have a __destruct() method to call, nor does the FilterIterator.
I tried to understand what you meant and the only way I can see of doing this is to create the object and pass that to the foreach() construct.
e.g.
<?php
$o_DIR = new FileTypeList('C:\\TEMP', 'CSV');
foreach($o_DIR as $o_FILE)
{
}
$o_DIR->__destruct();
?>
Fatal error: Call to undefined method DirectoryIterator::__destruct() in C:\t2.php
Replaced the $o_DIR->__destruct(); with unset($o_DIR); but that did not make any difference.
I recently passed my ZCE, so hopefully I should get SOME leaway here. Maybe I'm missing something in the SPL documentation. But as the only examples with __destruct in them are in dbaarray.inc and dbareader.inc I hope I can be forgiven for assuming that the destruction of the file handle would be handled automatically when the object was killed off.
If I add a __destruct method to my FileTypeList, it is called automatically on the exit of the foreach ...
<?php
class FileTypeList extends FilterIterator
{
protected
$s_Type;
function __construct($s_Path, $s_Type)
{
$this->s_Type = $s_Type;
parent::__construct(new DirectoryIterator($s_Path));
}
function __destruct()
{
echo "Destruction called\n";
}
function accept()
{
$b_Result = (strcasecmp($this->s_Type, pathinfo($this->current(),
PATHINFO_EXTENSION)) !== 0);
return $b_Result;
}
}
$am_Files = array();
while(count($am_Files) < 1000)
{
echo "Looking\n";
foreach(new FileTypeList('C:\\TEMP', 'CSV') as $o_FILE)
{
if ($o_FILE->isFile())
{
$am_Files[] = array
(
'Modified' => $o_FILE->getMTime(),
'Name' => str_replace('/', DIRECTORY_SEPARATOR, $o_FILE->getPathname()),
);
rename($s_FileName, 'C:\\TEMP\\JUNK\\' . basename($s_FileName));
echo "Found $s_FileName\n";
}
}
}
print_r($am_Files);
?>
produces output of ...
Looking
Destruction called
Looking
Destruction called
Looking
Destruction called
Looking
Destruction called
Looking
Destruction called
...
But as the parent has no destructor method to call and the directoryiterator has no destruct method to call, implementing one in my class is pointless.
I was expecting to see something like this in spl_directory.c ...
/* {{{ spl_filesystem_dir_close */
/* close a directory resource */
static void spl_filesystem_dir_close(....)
I can see how they are opened but not how they are closed and because they are not closed, they are not destroyed.
BUT I am probably wrong and I can't see it.