Hello together,
today I noticed that the order in which stream_flush/stream_close and
the destructor of a (userspace implemented) stream wrapper are called is
weird:
When a stream is closed with fclose()
, the call order is as expected:
stream_flush, stream_close, finally the destructor.
But if the stream is closed automatically on program exit, the order
seems wrong: stream_flush and stream_close are called AFTER the
destructor was called (and so the object should be destroyed!)
The documentation (http://php.net/manual/en/streamwrapper.destruct.php)
for the destructor contains the ominous sentence "Called when closing
the stream wrapper, right before streamWrapper::stream_flush()." which
is clearly only half of the truth.
Example: https://3v4l.org/CMItP
Is this some kind of "desired" behavior? Should I open a bug?
Greets,
Dennis
P.s.:
In addition to the call order, in auto-close case autoloading with
registered autoloaders does not work inside stream_flush and
stream_close, it seems registered autoloaders (maybe all objects) have
been dispatched when the calls happen, see: https://3v4l.org/GB7Mm
(I also appended both example files as I don't know how long snippets at
3v4l.org persist)
Hello again,
Am 20.03.2018 um 23:13 schrieb Dennis Birkholz:
(I also appended both example files as I don't know how long snippets at
3v4l.org persist)
the list does not allow attachments, so inline. If this does not work,
3v4l must suffice.
Greets,
Dennis
<?php
\spl_autoload_register(function($class) {
echo "Autoloader for '$class' called\n";
class_alias(Test::class, $class);
return true;
});
class Test
{
static function blubb()
{
echo static::class, "\n";
}
}
class MyStreamWrapper
{
static $counter = 1;
private $id;
public function __construct()
{
$this->id = self::$counter++;
}
public function stream_open($path, $mode, $options, &$opened_path)
{
echo "({$this->id}) stream_open()\n";
return true;
}
public function stream_write($data)
{
echo "({$this->id}) stream_write()\n";
return \strlen($data);
}
public function stream_flush()
{
echo "({$this->id}) stream_flush()\n";
if ($this->id === 1) {
Lalala::blubb();
} else {
Foobar::blubb();
}
return true;
}
public function stream_close()
{
echo "({$this->id}) stream_close()\n";
return true;
}
public function __destruct()
{
echo "({$this->id}) __destruct()\n";
}
}
\stream_wrapper_register('test', MyStreamWrapper::class);
if (($handle = \fopen('test:///foobar', 'r+')) === false) {
throw new \RuntimeException("Failed to open file");
}
if (\fwrite($handle, 'test') === false) {
throw new \RuntimeException("Failed to write to file");
}
if (\fclose($handle) === false) {
throw new \RuntimeException("Failed to close file");
}
if (($handle2 = \fopen('test:///blubb', 'r+')) === false) {
throw new \RuntimeException("Failed to open file");
}
if (\fwrite($handle2, 'test') === false) {
throw new \RuntimeException("Failed to write to file");
}
P.s.:
In addition to the call order, in auto-close case autoloading with
registered autoloaders does not work inside stream_flush and
stream_close, it seems registered autoloaders (maybe all objects) have
been dispatched when the calls happen, see: https://3v4l.org/GB7Mm
The behaviour with the stream wrapper certainly seems wrong to me, since
there is clearly still a reference to the object after its destructor is
called.
The autoloader not being available seems reasonable, though - during
shutdown, the engine has to start unpicking various global state in
order to cleanly release any references, and there is no order to do
this that would guarantee no code breaking. You demonstrate a stream
wrapper relying on an autoloader, but we could equally have an
autoloader that relied on a stream wrapper. If both needed to be
available for the other, the engine could never cleanly free any
resources they held, so it has to start somewhere. In general, anything
that might run on shutdown, such as a destructor, should be very
conservative in its assumptions about the engine's state.
Regards,
--
Rowan Collins
[IMSoP]
There is a related bug report: https://bugs.php.net/bug.php?id=75931
It also point out that the construcor method has been ignored.
Best regards,
CHU Zhaowei
Hello,
Am 22.03.2018 um 06:59 schrieb CHU Zhaowei:
There is a related bug report: https://bugs.php.net/bug.php?id=75931
Thank you for pointing out this bug report. It is about stream filters
(extending the php_user_filter class) and not about stream wrappers
which seem to have similar issues.
It also point out that the construcor method has been ignored.
That does not apply to stream wrappers, the constructor is called just fine.
Greets
Dennis