php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #75522 Inconsistent behavior with PHP's own destroy and free handles behavior
Submitted: 2017-11-14 09:27 UTC Modified: 2021-06-02 13:20 UTC
Votes:1
Avg. Score:3.0 ± 0.0
Reproduced:0 of 0 (0.0%)
From: dinumarina at gmail dot com Assigned: cmb (profile)
Status: Wont fix Package: Weakref (PECL)
PHP Version: 5.6.32 OS: CentOS 7
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2017-11-14 09:27 UTC] dinumarina at gmail dot com
Description:
------------
PHP's shutdown mechanism may call the destruct handler but not the free handler, in case of circular-referenced objects (see below at Expected result).

At runtime, it is possible to create an external reference to an object in its destructor. This is not documented in PHP, but alas, it happens; we use this edge  behavior to implement a second-pass garbage collector. I provide below a test based on this functionality, as it's simpler to trace, but it should exhibit the same behavior as the PHP shutdown handling.

The behavior in this case is this:
1) __destruct is called
2) __destruct sets an external reference to $this. Its refcount is increased
3) __destruct ends, but object is not freed
4) when the reference is freed (refcount decreased), object is freed but __destruct is not called again

Test script:
---------------
<?php
	class A {
		function __construct($id){
			$this->someProp=str_repeat('.',1024*1024);
			$this->id=$id;
		}
		function __destruct(){
			echo "Destroyed\n";
			$GLOBALS['static'][$this->id]=$this;
		}
	}
	
	echo "Current memory: ".floor(memory_get_usage()/1024/1024)."M \n";
	echo "Creating instances\n";
	
	$map=new WeakMap();
	$static=array();
	for($i=0;$i<10;$i++){
		$static[$i]=new A($i);
		$map[$static[$i]]=true;
	}
	
	echo "Current memory: ".floor(memory_get_usage()/1024/1024)."M \n";
	echo "1) Map count: ".count($map)."\n";
	echo "Destroying\n";
	
	for($i=0;$i<10;$i++){
		unset($static[$i]);
	}

	echo "Current memory: ".floor(memory_get_usage()/1024/1024)."M \n";
	echo "2) Map count: ".count($map)."\n";
	echo "Freeing\n";
	
	unset($static);
	
	echo "Current memory: ".floor(memory_get_usage()/1024/1024)."M \n";
	echo "3) Map count: ".count($map)."\n";
	

Expected result:
----------------
I would expect the 2) Map count to be still 10, which in other terms is to say WeakMap to respond to the object's free handler, not the destruct handler. I think this would also be consistent to PHP's final destruction of objects (it keeps the references valid for as long as possible, even if the objects were destroyed).

Reference: 
https://stackoverflow.com/questions/14096588/in-which-order-are-objects-destructed-in-php 
(see chosen answer)

While not required by any documentation, this behavior would be consistent with PHP's own destroy semantics. While I have not made a test case, I assume the current behavior makes a WeakMap's keys unavailable in the shutdown process even though PHP still has valid references to the same objects (as in the shutdown process, it does not free circular referenced objects, it merely calls their destructor handle).

If it's not the case, at least it might help someone if it were documented as diverging.

Actual result:
--------------
WeakMap references disappear on the destroy handler.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-06-02 13:20 UTC] cmb@php.net
-Status: Open +Status: Wont fix -Assigned To: +Assigned To: cmb
 [2021-06-02 13:20 UTC] cmb@php.net
Since PECL/Weakref is no longer maintained (it is inherently
incompatible with PHP 7.3+), I'm closing this.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Mar 19 02:01:28 2024 UTC