php / php-src

The PHP Interpreter
https://www.php.net
Other
37.93k stars 7.72k forks source link

stream_get_contents/fread timeouts #15521

Closed 6562680 closed 6 days ago

6562680 commented 3 weeks ago

Description

Actually i have not so much experience with resources or sockets, but tomorrow i tried to create videostream using PHP and ffmpeg tool.

There's mode that allows to pass data with PIPE to input and receive data with PIPE from the output.

Guess, it works great on Unix systems, but still works problematic with Windows.

Short example:


function _sigstop(?bool $sigstop)
{
  static $val;

  if (null !== $sigstop) {
    $val = (bool) $sigstop;
  }

  $val = $val ?? false;

  return $val;
}

$width = 640;
$height = 480;

$ffmpegPath = implode(DIRECTORY_SEPARATOR, [ "c:", 'Program Files', 'ffmpeg', 'bin', 'ffmpeg.exe' ]);

$videoOutputPath = implode(DIRECTORY_SEPARATOR, [ __DIR__, 'video.mp4' ]);

$cmd = [
    '"' . $ffmpegPath. '"',
    //
    // "-y", // overwrite existing file, if file, you know...
    //
    // > tried this one with pipes, nothing changed
    // "-flush_packets 1",
    //
    // > input
    //
    "-framerate 10",
    "-i -", // input `from PIPE`
    //
    // output
    //
    "-b:v 2500k", // output bitrate
    "-c:v libx264", // output codec
    "-pix_fmt yuvj420p", // output color palette
    //
    // > i succeeded to create file...
    // '"' . $videoOutputPath . '"', // output `to FILE`
    //
    // > but failed with pipes...
    "-f rawvideo -", // output format and mark `to PIPE`
];

$cmd = implode(' ', $cmd);
$cmdQuoted = "{$cmd}";

$pipesSpec = [
  0 => ['pipe', 'r'],
  1 => ['pipe', 'w'],
  2 => ['pipe', 'w'],
];

$ph = proc_open($cmdQuoted, $pipesSpec, $pipes);

$limit = 30;
while (true
  && ($limit-- > 0)
  && ! _sigstop();
) {
  // create image.... (resource) $imageHandler
  $imageHandler = imagecreatetruecolor($width, $height);
  $bgColor = imagecolorallocate(imageHandler, rand(0, 255), rand(0, 255), rand(0, 255));
  imagefill(imageHandler, 0, 0, $bgColor);
  $circleColor = imagecolorallocate(imageHandler, rand(0, 255), rand(0, 255), rand(0, 255));
  imagefilledellipse(imageHandler, rand(0, $width), rand(0, $height), 100, 100, $circleColor);

  ob_start();
  imagejpeg($imageHandler);
  $imageContent = ob_get_clean();
  imageclose($imageHandler);

  fwrite($pipes[0], $imageContent); // > using input pipe to pass data to the process

  // > i've tried fread(), stream_select(), stream_set_blocking() and stream_set_timeout() too
  $stdout = stream_get_contents($pipes[1]);
  fclose($pipes[1]);

  $stderr = stream_get_contents($pipes[2]);
  fclose($pipes[2]);
}

fclose($pipes[0]); // close input pipe on process end

// there is 2 cases, you have set of images and you can sent all images, then close the pipe (code will be different, but will work)

// my case is sending images non-stop, like creating unlimited video stream, thats why closing reading pipe outside the loop.

proc_close($ph);

The problem is in that line never reach first byte (infinite reading loop is happened) because reading pipe never closed (maybe ffmpeg buffers the output, but flush_packets argument should do the job, instead it wont work):

$stdout = stream_get_contents($pipes[1]);

But once i closed that pipe - i cannot organize unlimited stream - only one bulk of data can be passed (we cant reopen closed pipe known way)

It seems a very old and known bug, but maybe after that years some solution was found by php devs team?

ps. somewhere on php.net comments i found the comment where man told that "windows already (i mean - today) has possibility to work with non-blocking pipes, but old PHP core code still dont support it, so people used additional process with TCP/IP socket and proxy pipe through it... I didnt check that socket workaround, but "already has possibility" looks promising.

ps2. of course, production will work on Unix systems... But actually would be great to write application on windows, test it, and then push it to production, instead of using few OS on home machine (of course, possible with VM or maybe Docker, but... please!)

6562680 commented 3 weeks ago

An example with "file" output: https://github.com/user-attachments/assets/fe0004f3-62a5-449c-bf3a-7142d07c7c0b

cmb69 commented 3 weeks ago

Try

$pipesSpec = [
    0 => ['socket'],
    1 => ['socket'],
    2 => ['socket']
];

instead of

$pipesSpec = [
  0 => ['pipe', 'r'],
  1 => ['pipe', 'w'],
  2 => ['pipe', 'w'],
];

That may work or not. See https://www.php.net/manual/en/migration80.new-features.php#migration80.new-features.standard for a bit more info (well, actually not much more).

cmb69 commented 3 weeks ago

ps. somewhere on php.net comments i found the comment where man told that "windows already (i mean - today) has possibility to work with non-blocking pipes, but old PHP core code still dont support it, so people used additional process with TCP/IP socket and proxy pipe through it... I didnt check that socket workaround, but "already has possibility" looks promising.

See #5777/#5864.

github-actions[bot] commented 6 days ago

No feedback was provided. The issue is being suspended because we assume that you are no longer experiencing the problem. If this is not the case and you are able to provide the information that was requested earlier, please do so. Thank you.