Page 1 of 2
[BUGFIXED] How to destroy Swift_Message object? bug?
Posted: Wed Jun 20, 2007 8:46 am
by eskaj2
Swift_Message object has reference to itself in $parentRefs.
In Message.php file in constructor there are this:
foreach (array_keys($this->parentRefs) as $key)
{
$this->parentRefs[$key] = $this;
}
and php can't destroy Swift_Message objects. I think this is a bug.
Posted: Wed Jun 20, 2007 8:55 am
by eskaj2
It is in 3.2.6 ver
Posted: Wed Jun 20, 2007 12:47 pm
by Chris Corbyn
The reference is vital since the references stored in that array are links to the objects which are containers for sub-parts... by default, that would be itself, but when you start adding attachments it all changes around. This is actually the most complex part of Swift mailer and the one bit that I seriously pulled hairs out for nearly a week trying to get it working with PHP4
Can you show me a test case example? Are you calling unset() or assigning null to it? Either way, it won't be a Swift bug if you can't unset(), it will be a PHP bug, but I can see if there is a way around it. I can't think of a situation where this would affect you though... can you elaborate please?

Posted: Wed Jun 20, 2007 12:59 pm
by Weirdan
d11wtq wrote: I can't think of a situation where this would affect you though... can you elaborate please?

It leads to a memory leak if I understand correctly.
Posted: Wed Jun 20, 2007 1:27 pm
by eskaj2
d11wtq wrote:The reference is vital since the references stored in that array are links to the objects which are containers for sub-parts... by default, that would be itself, but when you start adding attachments it all changes around. This is actually the most complex part of Swift mailer and the one bit that I seriously pulled hairs out for nearly a week trying to get it working with PHP4
Can you show me a test case example? Are you calling unset() or assigning null to it? Either way, it won't be a Swift bug if you can't unset(), it will be a PHP bug, but I can see if there is a way around it. I can't think of a situation where this would affect you though... can you elaborate please?

Sorry i cant write in english very well. Unset() does not help because there is another reference in parentRefs array. Here is how I solved this problem :
in contructor:
foreach (array_keys($this->parentRefs) as $key)
{
//$this->parentRefs[$key] = $this;
$this->parentRefs[$key] = null;
}
than in every place where parentRefs occurs in Message.php I put something like that:
-------------
if(!$this->parentRefs["alternative"])
$id = $this->addChild($child, $id, $sign);
else
$id = $this->parentRefs["alternative"]->addChild($child, $id, $sign);
-------------
and like that:
-------------
if($new == $this)
$this->parentRefs[$old_branch] = null;
else
$this->parentRefs[$old_branch] = $new;
-------------------
Now it works fine and i don't have problem with out of memmory error.
Posted: Wed Jun 20, 2007 1:32 pm
by Chris Corbyn
Do the unit tests still pass like this? The unit tests cover many different things which can easily be overooked. If they do pass, and you can provide a patch then I'll patch it in with credit to you
Thanks very much
//Thinking out loud... __destruct() can be used to clean up in PHP5... not so easy in PHP4 though.
Posted: Wed Jun 20, 2007 1:59 pm
by AKA Panama Jack
d11wtq wrote:Either way, it won't be a Swift bug if you can't unset(), it will be a PHP bug, but I can see if there is a way around it. I can't think of a situation where this would affect you though... can you elaborate please?

It's not a PHP bug but how all versions of PHP handle Objects. Yes, even the latest versions of PHP 5.
You can UNSET an Object with PHP 4/5 but it will not remove the Object from memory. Neither PHP 4 or PHP 5 have a way to completely destroy an object and free up the memory it was using. When you use the UNSET function on an object you just clear the variable pointers to the object. The object will continue to STAY in memory. If you have a loop that continues to create an object and then unset it when finished you are NOT saving any memory. You have basically created something similar to a memory leak but unlike a memory leak the memory will be come free when the PHP script finishes executing. In PHP 4/5 you should never loop create an object unless you have fixed limits on the number of times the object can be created to maintain a certain memory threshold. If you don't then users of the program might run into memory issues. Especially on a shared server. If at all possible you should reuse an object over and over again in a loop instead of creating a new one. This will also make the overall operation faster. One of the slowest things under PHP 4/5 is creating new objects because it has to ask the operating system to find and allocate memory for the new object and then reparse the object code.
They have said that PHP 6 should be able to destroy an object and free up the memory while the program is still executing but don't look for anything like this to happen for PHP4/5.
Posted: Wed Jun 20, 2007 2:03 pm
by AKA Panama Jack
d11wtq wrote://Thinking out loud... __destruct() can be used to clean up in PHP5... not so easy in PHP4 though.
__destruct() will NOT free up any memory used by the object. __destruct() is basically a shutdown function to allow the program to execute specific things when the PHP 5 script finishes executing. Yes, it can be called at anytime but when PHP 5 finishes executing a program it will call the __destruct() function for every instance of an object that was created by the program.
Posted: Wed Jun 20, 2007 2:17 pm
by Chris Corbyn
AKA Panama Jack wrote:d11wtq wrote://Thinking out loud... __destruct() can be used to clean up in PHP5... not so easy in PHP4 though.
__destruct() will NOT free up any memory used by the object. __destruct() is basically a shutdown function to allow the program to execute specific things when the PHP 5 script finishes executing. Yes, it can be called at anytime but when PHP 5 finishes executing a program it will call the __destruct() function for every instance of an object that was created by the program.
Last I knew about it, __destruct() was not *ony* called at the end of script execution, it's also called duing object destruction (it's a destructor like in other OO languages). Therefore, within the __destruct() method I could feasibly unset() any remaining references to itself when unset() has been called on the object.
Code: Select all
class Foo
{
protected $me;
public function __construct() {
$this->me = $this; //Memory leak?
}
public function __destruct() {
echo "Destructor called";
$this->me = null;
}
}
$o = new Foo();
var_dump($foo);
unset($foo); //Destructor called
echo "Script ended here";
I can address this issue either way

It's something I'd never seen a problem with before.
Posted: Wed Jun 20, 2007 2:19 pm
by Weirdan
APJ, what you're saying is equivalent to 'PHP does not have GC' and that's plain wrong. Consider the following snippet:
Code: Select all
<?php
echo phpversion() ."\n";
$i = 0;
while ($i < 1000000000) {
if (($i++ % 100) == 0) {
echo "\ni: $i, memory: " . memory_get_usage() . "\n";
}
$q = new obj;
unset($q);
}
class obj
{
var $var = array("a", "s", "d");
}
It won't leak memory (observed both with memory_get_usage() and top). But if you added a circular reference, it would:
Code: Select all
class obj
{
var $var = array("a", "s", "d");
function obj() {
$this->q =& $this;
}
}
Posted: Wed Jun 20, 2007 2:21 pm
by Chris Corbyn
I eat my hat
After fixing the undefined variable $foo issue
object(Foo)#1 (1) { ["me:protected"]=> object(Foo)#1 (1) { ["me:protected"]=> *RECURSION* } } Script ended hereDestructor called
My understanding of what a destructor should do is clearly flawed
//goes off to compare with other languages
Posted: Wed Jun 20, 2007 2:26 pm
by Weirdan
Therefore, within the __destruct() method I could feasibly unset() any remaining references to itself when unset() has been called on the object.
The catch is that __destruct won't get called until all the references to the object are unset.
$o = new Foo();
var_dump($foo);
unset($foo); //Destructor called
Really? What version of PHP you are using? And try that code in a loop (add your destructor to my 'obj' class for example.
Posted: Wed Jun 20, 2007 2:28 pm
by Weirdan
My understanding of what a destructor should do is clearly flawed
No, it's not. It's a php problem that arises
only when you use circular references.
Posted: Wed Jun 20, 2007 2:35 pm
by Chris Corbyn
Weirdan wrote:
My understanding of what a destructor should do is clearly flawed
No, it's not. It's a php problem that arises
only when you use circular references.
Yep

I also just checked how Java (also a GC language) handles destructors:
Generally, you won't need to - the most common usage of destructors in C++ is to free memory - and java will do that for you automatically via "garbage collection" (GC).
That said, if you have some other special resource to free or if you're curious as to when the GC runs, implement a finalize() method (protected void finalize()) which will be run when the GC is about to delete the object.
It's important to bear in mind that the finalizer is run when the GC frees your object, not when the object first becomes garbage (i.e. unreferenced) - depending on memory conditions inside the heap on your specific platform and the particulars of the runtime you use, it could be a very long time before the finalizer is called during a GC cycle.
So I guess the reason I saw the destructor called at the end of the script is not a logical one in the sense of straight-forward execution... PHP obviously takes the unset() call, removes the vaiable but does NOT free up memory until the GC decides it's the right time to do so (end of script in this case). Very interesting.... I'll experiment with this since I'm curious

Posted: Wed Jun 20, 2007 2:43 pm
by Weirdan
PHP obviously takes the unset() call, removes the vaiable but does NOT free up memory until the GC decides it's the right time to do so (end of script in this case).
Not exactly. In this case the object
can't be destroyed because after you had unset $foo (or $o

) there's still one reference pointing to that object: $this->_me. Your script could be running for hours, aggressively allocating memory etc.. and that destructor still won't get called. And the reason it's actually called during the shutdown sequence is that PHP destroys every object (that managed to stay alive to that time) when the php engine is shutting down.