Jump to content

How to delete an object?


NotionCommotion

Recommended Posts

I thought I was deleting (unset) an object, but when adding a destructor, found it was never being fired.  I then used debug_zval_dump() and found that my refcount is 7, and it is my understanding that it must be 3 (more on why I think this later).

I searched where I might have left a copy, but couldn't find it.  Any recommendations how to do so?  I looked for ways to find all references to an object

After manually searching and not finding exactly what I was looking for, I came across this script.

    public function addGatewayClient(GatewayClient $client):TimerInterface
    {
        return $this->loop->addPeriodicTimer($this->period, function() use($client) {
            // bla bla bla
        });
    }


To verify whether a reference was being created, I wrote the below script.

<?php
ini_set('display_errors', 1);

class MyClass
{
    public function __destruct()
    {
        echo('__destruct'.PHP_EOL);
    }    
}

function getZval($v):string {
        ob_start();
        debug_zval_dump($v);
        $rs = ob_get_clean();
        return str_replace(PHP_EOL, '', $rs);
}

$obj = new MyClass;
echo('create object: '.getZval($obj).PHP_EOL);
$objC1 = $obj;
echo('save first reference: '.getZval($obj).PHP_EOL);
$objC2 = $obj;
echo('save second reference: '.getZval($obj).PHP_EOL);

// This is the line to comment out
$func = function() use($obj) {};

echo('set function: '.getZval($obj).PHP_EOL);

unset($objC1);
echo('delete first reference: '.getZval($obj).PHP_EOL);

unset($objC2);
echo('delete second reference: '.getZval($obj).PHP_EOL);

unset($obj);

echo('Done'.PHP_EOL);

If I comment out \\$func = function() use($obj) {};, I see the refcount start at 3, go up to 5 and then back down to 3, then __destruct() being called, and then the script completion "Done".  But then when I don't comment the closure function, I see it go up to 6, go down to 4, the script completion "Done", and then __destruct() being called.

Guess I can just set the object to be deleted to NULL, but I feel there should be a better way.  Any recommendations?

On a side note, debug_zval_dump() is more difficult in the real world if there is recursion, and my getZval() function results in a fatal error by exhausting too much memory.  I also tried just calling debug_zval_dump(), but it definitely wasn't a good call.

Link to comment
Share on other sites

50 minutes ago, requinix said:

You cannot delete an object. Stop using it and PHP will delete it automatically.

In your example code, $func is using $obj.

Ah, I need to unset $func!  And in my real example, unset the TimerInterface returned by addGatewayClient().  Let me give that a try.  Thanks

PS.  Ended up installing Xdebug.  It helps with the recursion and the documentation claims it is "more accurate" because it doesn't pass a variable but doesn't seem to provide more information.

Edited by NotionCommotion
Link to comment
Share on other sites

2 minutes ago, NotionCommotion said:

PS.  Ended up installing Xdebug.  It helps with the recursion and the documentation claims it is "more accurate" because it doesn't pass a variable but doesn't seem to provide more information.

debug_zval_dump shows you refcounts, but the very act of passing a value into the function increases the refcount. The solution was call-time byref variables, but that was removed a few PHP versions ago. So the refcount isn't as accurate anymore and debug_zval_dump isn't really useful now.
Xdebug provides something else.

Link to comment
Share on other sites

Okay, thanks.

For my test script, unsetting $func allows the garbage collector to delete the object in question and thus its destructor is called, but for my real example the destructor isn't being called and therefore some object or function must still be using it.  That being said, figuring out where it is still being used is not so easy.  I thought that either debug_zval_dump  or xdebug_debug_zval might provide a clue, but it doesn't appear to do so.  Any other strategies to identify what is still using it?

Link to comment
Share on other sites

No idea. Xdebug might have a tool that can help, but I would probably just go through the code and trace where the variables are being stored. Or at least being sent.

But like I said in the other thread, don't use destructors as a shutdown mechanism. They're about dealing with memory and resources that were used by the instance being destroyed.

Link to comment
Share on other sites

1 hour ago, requinix said:

But like I said in the other thread, don't use destructors as a shutdown mechanism. They're about dealing with memory and resources that were used by the instance being destroyed.

Updating the DB when the server stops is a different need.  When a client connects, I add it to a SplObjectStorage object, and if it disconnects, I remove it from that object and and need to perform some other tasks as well.  I suppose I could just explicitly perform those other tasks the same time I remove it from the list, but it seemed like using the destructor was a more elegant solution.   Maybe not?  Regardless, now that I know the destructor isn't being executed, I know there is some other reference to the client and I am not releasing the memory and still need to fix that.  While I am surprised that there isn't some function/etc to find existing references to an existing object, at least now I know there isn't any common ones and will stop looking.  Thanks for your help.

Link to comment
Share on other sites

42 minutes ago, NotionCommotion said:

but it seemed like using the destructor was a more elegant solution.   Maybe not?

Not really.

Think about how much time you're spending now into trying to figure out why your destructor is not being run vs just re-factoring the code to do:

$storage->detach($client);
$client->cleanup();

My view on the matter is one should for the most part limit destructors to things that are good to do, but don't necessarily need to be done with specific timing/ugency.  I rarely ever use a destructor in most of my code.   When I do, it's usually for just cleaning up resources (file handles, curl handles, image handles, etc).

  • Like 1
Link to comment
Share on other sites

2 hours ago, kicken said:

Not really.

Think about how much time you're spending now into trying to figure out why your destructor is not being run vs just re-factoring the code to do:


$storage->detach($client);
$client->cleanup();

My view on the matter is one should for the most part limit destructors to things that are good to do, but don't necessarily need to be done with specific timing/ugency.  I rarely ever use a destructor in most of my code.   When I do, it's usually for just cleaning up resources (file handles, curl handles, image handles, etc).

Thanks kicken,  I see your point and have no compelling need to code this in the destructor for aesthetics reasons alone.  I do, however, think it is important to allow PHP's garbage collector to do its job for a server application, and while I will likely abandon the notion of performing this work in the destructor, am glad I attempted so if for no other reason to learn I wasn't releasing the no longer needed client objects.

Turned out my issues were caused by two objects having references back to one another.  As overkill, I have set both Client::stream and Stream::client to null before detaching and now the destructor fires, and expect I really only need to set one of them to null.

 

function setClient(StreamParser $stream, AbstractClient $client){
	$stream->setClient($client);
	$storage[$stream] = $client;
}

// on connection
$initialClient = new UnregisteredClient($stream);
setClient($stream, $initialClient);

// on data -> register 
$actualClient = new SpecificClient($stream);
setClient($stream, $actualClient);

// on close
$storage->detach($stream);

 

Link to comment
Share on other sites

2 minutes ago, NotionCommotion said:

Turned out my issues were caused by two objects having references back to one another.  As overkill, I have set both Client::stream and Stream::client to null before detaching and now the destructor fires, and expect I really only need to set one of them to null.

If those two objects weren't being used anywhere else, and really were eligible to be cleaned up if not for the fact that they referenced each other, then gc_collect_cycles() is an option.
However it's a relatively expensive operation (it goes through the symbol tables looking for circular references that could be cleaned up) so it's the sort of thing you should avoid needing to use - avoiding in ways such as altering the code a tiny bit to break the circle when you're done with the object.

Link to comment
Share on other sites

1 hour ago, requinix said:

If those two objects weren't being used anywhere else, and really were eligible to be cleaned up if not for the fact that they referenced each other, then gc_collect_cycles() is an option.
However it's a relatively expensive operation (it goes through the symbol tables looking for circular references that could be cleaned up) so it's the sort of thing you should avoid needing to use - avoiding in ways such as altering the code a tiny bit to break the circle when you're done with the object.

Thanks requinix,  While I haven't done any research and do not claim that my opinion is sensible, I agree with you as I feel either PHP is being used for a simple website and there is no need to use gc_collect_cycles(), or else either PHP is being used to execute some very memory intensive single page requirements or some continuously run server in which one should fully understand what is going on and tweak the code so that it is not required.

However, it would sure be nice to tell PHP to almost do what gc_collect_cycles() does and go through the symbol tables looking for circular references for a given object as a troubleshooting tool.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.