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: 2021-06-09 12:54 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
 [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

Pull Requests

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.
 [2021-06-09 12:54 UTC] nikic@php.net
For PHP 8.1, I have added an automatic GC re-run for destructed objects in https://github.com/php/php-src/commit/b58d74547f7700526b2d7e632032ed808abab442.
 [2021-06-10 19:47 UTC] m at m dot cz
this and related tickets should be closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 17:01:32 2024 UTC