Reused object will not call `__destruct`?

Passerby

I was trying to make a "pool" like structure in a CLI program, which includes lots of "borrowing" and "recycling". And while testing things, I encountered something quite unexpected:

<?php
class FOO
{
    public static $pool=[];
    public static function get()
    {
        if(empty(self::$pool))
        {
            self::$pool[]=new self(mt_rand(1000,9999));
        }
        return array_shift(self::$pool);
    }
    protected static function recycle(FOO $foo)
    {
        echo "Recycling {$foo->num}\n";
        self::$pool[]=$foo;
    }

    public $num;
    protected function __construct(int $num)
    {
        $this->num=$num;
    }
    public function __destruct()
    {
        static::recycle($this);
    }
}
function Bar()
{
    $foo=FOO::get();
    echo "Got {$foo->num}\n";
}
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
print_r(FOO::$pool);
echo "End.\n";

The output is:

Bar
Got 2911
Recycling 2911
Bar
Got 2911
Bar
Got 1038
Recycling 1038
Bar
Got 1038
Array
(
)
End.

If I limit the Bar() call to 3 times instead of 4, I got the following output:

Bar
Got 7278
Recycling 7278
Bar
Got 7278
Bar
Got 6703
Recycling 6703
Array
(
    [0] => FOO Object
        (
            [num] => 6703
        )

)
End.

Here you can see, when an object is "reused" (see 2911 and 1038 in example 1, 7278 in example 2), its __destruct() will not be called; instead, it will simply disappear.

I'm confused. Why would this happen? Why wouldn't FOO->__destruct got called every time? And is it possible to make instances of FOO auto recycle itself?

I'm testing this in PHP-7.2, behavior observed both in Windows and WSL-Ubuntu.

Dharman

As far as I know destructors will not be called twice for the same object. It is generally a bad practice to reuse objects from the destructor. An object whose destructor was called should be destroyed, not reused. In your test script it doesn't cause any serious issues, but in real life, such usage could lead to unexpected behaviours if the developer is not careful.

At first when I read your question, I was worried that you have created a memory leak, but that is not the case. The destructor is called as soon as you leave the scope of Bar() only if it hasn't been called yet on this object. However, because you save the reference and increase the ref_count of this object inside of the __destruct() the garbage collector cannot collect the object yet. It must wait until the next time when you decrease the ref_count of this object.

The process can be explained as follows (simplified):

  1. You call Bar() function which creates an instance of FOO. An object is created and assigned to $foo. (ref_count = 1)
  2. Upon the end of Bar() the only reference to this object is lost (ref_count = 0), which means PHP starts the process of destroying the object.
    2.1. The destructor is called. Inside of the destructor you increase the ref_count to 1.
    2.2. The next step would be for the GC to collect the object, but the ref_count is not zero. This could mean a cycle or like in your example, a new reference has been created in the destructor. GC must wait until there are no uncyclical references to collect the object.
  3. You call Bar() again. You shift the same object from static array, which means that the reference inside of FOO::$pool is gone, but you immediately assign it to $foo. (ref_count = 1)
  4. Upon the end of Bar() the only reference to this object is lost (ref_count = 0), which means GC can finally collect this object. The destructor has already been called, so there is no other action to be taken here.

You didn't recycle the object, you merely prolonged its existance in the memory. You could access it for a little longer, but for PHP that object was already in the process of being destroyed.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

destruct object with interface

Destruct an object type into an "or" type

__destruct() and __call() create endless loop

Is Java lambda anonymous object reused?

Javascript's equivalent of destruct in object model

Destruct a string from split to named object

Javascript - How to destruct an object and clone a property?

Does C++ destruct object in given compound?

how to destruct part of properties from an object

Destruct javascript object not setting a default value

How can I destruct this object and mapping in reactjs?

Are elements of @_ reused for new subroutine call or new @_ is created?

Object lifetime, in which situation is reused the storage?

Saving ggmap object to a file which can be reused?

End of object lifetime if its storage is partially reused

How to use Rest pattern to destruct only desired part of an object?

Can you destruct an object and have nullish coalescing for potential undefined constants?

Zend: How to correctly destruct a custom object in PHP 7?

How to safely destruct class with smart pointer to incomplete object type?

shared_ptr does not destruct the object at the end of the program

Is it possible to destruct a large object only once in Javascript's function?

Where to destruct Object within Array from React State

How do I destruct a dynamically allocated array of dynamic object?

How to destruct array of numbers in an object and combine to one in javascript?

Export an object to be reused in another JavaScript file without the default operator

lxml object identifiers appear to be reused while objects are alive

Why getting "Destructuring expressions can only have identifier references" when trying to destruct an object?

How do I correctly destruct a derived object that was constructed using placement new

What's the best way to store a huge Map object populated at runtime to be reused by another tool?

TOP Ranking

HotTag

Archive