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: -
Votes:1
Avg. Score:3.0 ± 0.0
Reproduced:0 of 0 (0.0%)
From: dinumarina at gmail dot com Assigned:
Status: Open Package: Weakref (PECL)
PHP Version: 5.6.32 OS: CentOS 7
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: dinumarina at gmail dot com
New email:
PHP Version: OS:

 

 [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

 
PHP Copyright © 2001-2018 The PHP Group
All rights reserved.
Last updated: Mon Dec 10 04:01:24 2018 UTC