php / php/php-src

Streams blocking read and EOF handling for pipes

Aperta
#10,171 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Category: Streams Feature
Lingua principale
C
Stelle
40.4k
Fork
8.1k
Merge medio
2g 13h
PR unite (30g)
96

Descrizione

Description

The plain wrapper is usually used for files but it is also used for pipes created by proc_open. Currently EOF in plain wrapper is set only after an empty read (0 bytes returned). There is a special case for files, memory and temp streams to do another read if less than requested bytes is returned which sets EOF if no more data is present. To have a consistent behavior it might be convenient to do the same for pipes to identify whether EOF is reached. This is exactly what glibc fread does after opening pipe descriptor using fdopen. It might make sense to apply the same logic for PHP fread. However in some cases returning 0 bytes does not mean that there won't be more data coming in the pipe especially when proc opening interactive program or a program with delays in the processing. So it might actually make more sense treat pipes as sockets and for blocking read apply timeouts after which the eof is set and return what's in the buffer until then.

In any case something needs to be done about the current inconsistency between single and multiple reads reading the same amount of bytes. To explain that, it's best to give an example of such behavior. Lets consider following script called pipe_eof_test.php:

<?php
$descriptorspec=array(
	0 => STDIN,
	1 => array("pipe", "w"),
	2 => STDERR
);
$p = proc_open(['echo', '-n', '01234567890123456789'], $descriptorspec, $fd);

$res = '';
$size = $argv[1];
while (strlen($s = fread($fd[1], $size)) == $size) {
	$res .= $s;
}
$res .= $s;

fprintf(STDERR, "Result: %s, EOF: %d\n", $res, feof($fd[1]));

It basically reads from a pipe containing 20 bytes (echo...). The result differs depending on the size of the requested bytes. So the EOF is 0 if $size is over 20 but it is 1 if it is below 20 which can be seen when running script

$ php pipe_eof_test.php 32
Result: 01234567890123456789, EOF: 0
$ php pipe_eof_test.php 16
Result: 01234567890123456789, EOF: 1

The reason is that all available bytes are read internally to 8k buffer on the first read. In the first case, we use only a single read as less bytes are requested. It means there is no 0 read and EOF is 0. In the second case, we need a second read because all 16 bytes are returned in first read and then only 4 bytes are left in the buffer. Because 16 bytes are requested in the 2nd read, another read is attempted to fill more bytes in which results in 0 bytes read and setting EOF.

If we used socket logic the same logic as for files we would get EOF 1 in both cases because in the first case, it would still attempt to read more bytes to fill the buffer so we would get 0 bytes. The problem is if the such program is interactive and there are more bytes coming in the pipe later. In such case, the program hangs which is actually what happens currently when reading less bytes as the 2nd read blocks. Also the blocking is not limited by timeout which is also not convenient.

It seems though that using the same logic as for socket is more convenient as we would get EOF 0 in both cases. Only if we did another read, it would apply timeout and set EOF only after the timeout.

This should be treated as a feature request because it is a subtle BC break and changing the implementation to use the same logic for pipes as for sockets might be slightly bigger and more involved.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia con pipe_eof_test.php e riproduci le differenze nei risultati di fread e feof usando proc_open con diverse dimensioni richieste. Esamina il comportamento descritto della pipe per le letture bloccanti, l’input ritardato e i timeout, quindi stabilisci il comportamento EOF previsto e aggiungi una copertura di test che mostri che le letture singole e multiple si comportano in modo coerente senza bloccare i processi interattivi.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
c, php
Ambito
backend
Tipo di issue
Funzionalità
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
25/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.