php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Doc Bug #78933 gc_collect_cycles() may need a second pass when destructors are used
Submitted: 2019-12-08 21:55 UTC Modified: 2019-12-09 16:11 UTC
From: requinix@php.net Assigned:
Status: Open Package: *General Issues
PHP Version: 7.4.0 OS: Any (Windows?)
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.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: requinix@php.net
New email:
PHP Version: OS:

 

 [2019-12-08 21:55 UTC] requinix@php.net
Description:
------------
Discovered during bug #78930. Works in PHP 7.3.

With two classes forming a cyclic reference, one of them doesn't seem to be GCed properly if the other has a destructor - even if empty. It works without the destructor.

The only way I've been able to reproduce *and detect* the problem has been to use ext/pdo_sqlite to open a file, then test that the file is closed using unlink(). As far as I know, this is only possible to do on Windows since Linux is supposed to allow deleting files with open handles.

But this doesn't quite feel like a PDO, SQLite, or Windows issue. I assume the GC recognizes when there is a destructor that needs to be invoked, and that affects how it cleans up.

Test script:
---------------
<?php

class Test {

	public $base;
	public $container;

	public function __construct(Container $c) {
		$this->container = $c;
		$this->base = new PDO("sqlite:base.sqlite", "", "");
	}

}

class Container {

	public $test;

	public function __construct() {
		$this->test = new Test($this);
	}

	// commented: no errors, file deleted
	// uncommented: "resource temporarily unavailable", file not deleted
	function __destruct() { }
}

new Container();

gc_collect_cycles();
unlink("base.sqlite");



Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-12-08 22:05 UTC] nikic@php.net
-Status: Open +Status: Feedback
 [2019-12-08 22:05 UTC] nikic@php.net
Classes with destructors may require two GC runs to be fully collected. Does it work if you repeat gc_collect_cycles() twice?
 [2019-12-08 22:12 UTC] requinix@php.net
Yes it does.

Could it ever need more than two? If so, that would suggest using it like
  while (gc_collect_cycles());
Or perhaps a new optional "bool $repeat = true" argument to do that automatically?
 [2019-12-09 07:56 UTC] nikic@php.net
Assuming the destructor doesn't do anything to make the object non-GCable, I believe that two runs should be sufficient.

> Or perhaps a new optional "bool $repeat = true" argument to do that automatically?

In reference to the original issue with PDO SQLite: No, I don't think this makes sense. Generally, manual GC runs should not be necessary to influence program behavior.

I think this is an indication that PDO needs a way to explicitly close a connection that does not require destroying the object. I believe this was rejected in the past "because you can just write unset($pdo)", but in the presence of circular references this just isn't reliable.
 [2019-12-09 13:50 UTC] requinix@php.net
-Summary: Presence of a destructor affects garbage collection +Summary: gc_collect_cycles() may need a second pass when destructors are used -Status: Feedback +Status: Open -Type: Bug +Type: Documentation Problem
 [2019-12-09 16:11 UTC] cmb@php.net
> As far as I know, this is only possible to do on Windows since
> Linux is supposed to allow deleting files with open handles.

FWIW, as of PHP 7.3.0, files opened by the engine can also be
deleted on Windows if they have open handles.  That doesn't apply
to SQLite3 databases, though.
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Fri Jan 24 02:01:25 2020 UTC