[BUGFIXED] How to destroy Swift_Message object? bug?

Swift Mailer is a fantastic library for sending email with php. Discuss this library or ask any questions about it here.

Moderators: Chris Corbyn, General Moderators

eskaj2
Forum Newbie
Posts: 3
Joined: Wed Jun 20, 2007 8:36 am

[BUGFIXED] How to destroy Swift_Message object? bug?

Post 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.
Last edited by eskaj2 on Wed Jun 20, 2007 9:52 am, edited 1 time in total.
eskaj2
Forum Newbie
Posts: 3
Joined: Wed Jun 20, 2007 8:36 am

Post by eskaj2 »

It is in 3.2.6 ver
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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? :D
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

d11wtq wrote: I can't think of a situation where this would affect you though... can you elaborate please? :D
It leads to a memory leak if I understand correctly.
eskaj2
Forum Newbie
Posts: 3
Joined: Wed Jun 20, 2007 8:36 am

Post 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? :D
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.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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.
User avatar
AKA Panama Jack
Forum Regular
Posts: 878
Joined: Mon Nov 14, 2005 4:21 pm

Post 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? :D
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.
User avatar
AKA Panama Jack
Forum Regular
Posts: 878
Joined: Mon Nov 14, 2005 4:21 pm

Post 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.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post 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;
    }
}
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

I eat my hat :oops:

After fixing the undefined variable $foo issue :P
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 :oops:

//goes off to compare with other languages
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post 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.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post 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.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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 :D
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post 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.
Post Reply