php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71980 Decorated/Nested Generator is Uncloseable in Finally
Submitted: 2016-04-06 19:30 UTC Modified: 2016-04-07 06:13 UTC
Votes:1
Avg. Score:3.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:0 (0.0%)
From: php at fleshgrinder dot com Assigned:
Status: Closed Package: Scripting Engine problem
PHP Version: Irrelevant OS:
Private report: No CVE-ID:
 [2016-04-06 19:30 UTC] php at fleshgrinder dot com
Description:
------------
The finally block of a Generator that is decorated with or nested in another Generator is never called. This behavior is of course true for multiple decoration or nesting levels.

Using a finally block for clean-up in case a Generator is aborted is the official and only way of doing so. It is also recommended in the initial RFC:

https://wiki.php.net/rfc/generators#closing_a_generator

Please see attached test script as well as expected and actual result.

Test script:
---------------
#!/usr/bin/env php
<?php

function generator() {
	try {
		while (true) {
			yield 'Hello';
		}
	}
	finally {
		echo "Generator Clean-up\n";
	}
}

function generator_decoration() {
	try {
		foreach (generator() as $word) {
			yield $word . " World!\n";
		}
	}
	finally {
		echo "Decorator Clean-up\n";
	}
}

foreach (generator_decoration() as $sentence) {
	echo $sentence . "Break\n";
	break;
}

echo "Shutdown\n";


Expected result:
----------------
Hello World!
Break
Decorator Clean-up
Generator Clean-up
Shutdown

Actual result:
--------------
Hello World!
Break
Decorator Clean-up
Shutdown
Generator Clean-up

Workaround:
----------------

In case a frustrated user is finding this bug report. There is a possible workaround that works in all PHP versions that support Generators via a signal that is being sent to the Generator. Please have a look at the following example that makes use of it.

#!/usr/bin/env php
<?php

const GENERATOR_BREAK = 'PHP_GENERATOR_BREAK';

function generator() {
	try {
		while (true) {
			if ((yield 'Hello') === GENERATOR_BREAK) {
				break;
			}
		}
	}
	finally {
		echo "Generator Clean-up\n";
	}
}

function generator_decoration() {
	$generator = generator();
	try {
		foreach ($generator as $word) {
			yield $word . " World!\n";
		}
	}
	finally {
		echo "Decorator Clean-up\n";
		$generator->send(GENERATOR_BREAK);
	}
}

foreach (generator_decoration() as $sentence) {
	echo $sentence . "Break\n";
	break;
}

echo "Shutdown\n";


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-04-07 06:13 UTC] nikic@php.net
-Status: Open +Status: Verified
 [2016-04-07 06:13 UTC] nikic@php.net
We're leaking the inner generator here. We need to perform partial unfinished execution cleanup before jumping to the finally block.
 [2016-04-07 10:30 UTC] nikic@php.net
Automatic comment on behalf of nikic
Revision: http://git.php.net/?p=php-src.git;a=commit;h=aad4ecebf83568f41f5b27b274f988710ce53646
Log: Fixed bug #71980
 [2016-04-07 10:30 UTC] nikic@php.net
-Status: Verified +Status: Closed
 [2016-04-07 17:11 UTC] php at fleshgrinder dot com
Awesome, thanks for this very fast fix.
 [2016-07-20 11:32 UTC] davey@php.net
Automatic comment on behalf of nikic
Revision: http://git.php.net/?p=php-src.git;a=commit;h=aad4ecebf83568f41f5b27b274f988710ce53646
Log: Fixed bug #71980
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Tue Feb 21 14:01:44 2017 UTC