file_get_contents obteniendo resultados incorrectos


10

Actualizar

Resolví el problema y publiqué una respuesta. Sin embargo, mi solución no es 100% ideal. Preferiría eliminar solo symlinkel cachecon clearstatcache(true, $target)o clearstatcache(true, $link)pero eso no funciona.

También preferiría evitar el almacenamiento en caché de enlaces simbólicos en primer lugar o eliminar el enlace simbólico del caché inmediatamente después de generarlo. Lamentablemente, no tuve suerte con eso. Por alguna razón, clearstatcache(true)después de crear un enlace simbólico no funciona, todavía se almacena en caché.

Con mucho gusto otorgaré la recompensa a cualquiera que pueda mejorar mi respuesta y resolver esos problemas.

Editar

Intenté optimizar mi código generando un archivo cada vez que clearstatcachese ejecuta, por lo que solo necesito borrar el caché una vez para cada enlace simbólico. Por alguna razón, esto no funciona. clearstatcachenecesita ser llamado cada vez que a symlinkestá incluido en el camino, pero ¿por qué? Debe haber una manera de optimizar la solución que tengo.


Estoy usando PHP 7.3.5con nginx/1.16.0. A veces file_get_contentsdevuelve el valor incorrecto cuando se usa a symlink. El problema es que después de eliminar y recrear un enlace simbólico, su valor anterior permanece en la memoria caché. A veces se devuelve el valor correcto, a veces el valor anterior. Parece aleatorio.

Intenté borrar el caché o evitar el almacenamiento en caché con:

function symlink1($target, $link)
{
    realpath_cache_size(0);
    symlink($target, $link);
    //clearstatcache(true);
}

Realmente no quiero deshabilitar el almacenamiento en caché, pero todavía necesito una precisión del 100% con file_get_contents.

Editar

No puedo publicar mi código fuente, ya que es demasiado largo y complejo, así que he creado un ejemplo mínimo y reproducible (index.php) que recrea el problema:

<h1>Symlink Problem</h1>
<?php
    $dir = getcwd();
    if (isset($_POST['clear-all']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        foreach ($nos as $no)
        {
            unlink($dir.'/nos/'.$no.'/id.txt');
            rmdir($dir.'/nos/'.$no);
        }
        foreach (array_values(array_diff(scandir($dir.'/ids'), array('..', '.'))) as $id)
            unlink($dir.'/ids/'.$id);
    }
    if (!is_dir($dir.'/nos'))
        mkdir($dir.'/nos');
    if (!is_dir($dir.'/ids'))
        mkdir($dir.'/ids');
    if (isset($_POST['submit']) && !empty($_POST['id']) && ctype_digit($_POST['insert-after']) && ctype_alnum($_POST['id']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        $total = count($nos);
        if ($total <= 100)
        {
            for ($i = $total; $i >= $_POST['insert-after']; $i--)
            {
                $id = file_get_contents($dir.'/nos/'.$i.'/id.txt');
                unlink($dir.'/ids/'.$id);
                symlink($dir.'/nos/'.($i + 1), $dir.'/ids/'.$id);
                rename($dir.'/nos/'.$i, $dir.'/nos/'.($i + 1));
            }
            echo '<br>';
            mkdir($dir.'/nos/'.$_POST['insert-after']);
            file_put_contents($dir.'/nos/'.$_POST['insert-after'].'/id.txt', $_POST['id']);
            symlink($dir.'/nos/'.$_POST['insert-after'], $dir.'/ids/'.$_POST['id']);
        }
    }
    $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
    $total = count($nos) + 1;
    echo '<h2>Ids from nos directory</h2>';
    foreach ($nos as $no)
    {
        echo ($no + 1).':'.file_get_contents("$dir/nos/$no/id.txt").'<br>';
    }
    echo '<h2>Ids from using symlinks</h2>';
    $ids = array_values(array_diff(scandir($dir.'/ids'), array('..', '.')));
    if (count($ids) > 0)
    {
        $success = true;
        foreach ($ids as $id)
        {
            $id1 = file_get_contents("$dir/ids/$id/id.txt");
            echo $id.':'.$id1.'<br>';
            if ($id !== $id1)
                $success = false;
        }
        if ($success)
            echo '<b><font color="blue">Success!</font></b><br>';
        else
            echo '<b><font color="red">Failure!</font></b><br>';
    }
?>
<br>
<h2>Insert ID after</h2>
<form method="post" action="/">
    <select name="insert-after">
        <?php
            for ($i = 0; $i < $total; $i++)
                echo '<option value="'.$i.'">'.$i.'</option>';
        ?>
    </select>
    <input type="text" placeholder="ID" name="id"><br>
    <input type="submit" name="submit" value="Insert"><br>
</form>
<h2>Clear all</h2>
<form method="post" action="/">
    <input type="submit" name="clear-all" value="Clear All"><br>
</form>
<script>
    if (window.history.replaceState)
    {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

Parecía muy probable que fuera un problema con la Nginxconfiguración. No tener estas líneas puede causar el problema:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

Aquí está mi Nginxconfiguración (puede ver que he incluido las líneas anteriores):

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.websemantica.co.uk;
    root "/path/to/site/root";
    index index.php;

    location / {
        try_files $uri $uri/ $uri.php$is_args$query_string;
    }

    location ~* \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_param   QUERY_STRING            $query_string;
        fastcgi_param   REQUEST_METHOD          $request_method;
        fastcgi_param   CONTENT_TYPE            $content_type;
        fastcgi_param   CONTENT_LENGTH          $content_length;

        fastcgi_param   SCRIPT_FILENAME         $realpath_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME             $fastcgi_script_name;
        fastcgi_param   PATH_INFO               $fastcgi_path_info;
        fastcgi_param   PATH_TRANSLATED         $realpath_root$fastcgi_path_info;
        fastcgi_param   REQUEST_URI             $request_uri;
        fastcgi_param   DOCUMENT_URI            $document_uri;
        fastcgi_param   DOCUMENT_ROOT           $realpath_root;
        fastcgi_param   SERVER_PROTOCOL         $server_protocol;

        fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
        fastcgi_param   SERVER_SOFTWARE         nginx/$nginx_version;

        fastcgi_param   REMOTE_ADDR             $remote_addr;
        fastcgi_param   REMOTE_PORT             $remote_port;
        fastcgi_param   SERVER_ADDR             $server_addr;
        fastcgi_param   SERVER_PORT             $server_port;
        fastcgi_param   SERVER_NAME             $server_name;

        fastcgi_param   HTTPS                   $https;

        # PHP only, required if PHP was built with --enable-force-cgi-redirect
        fastcgi_param   REDIRECT_STATUS         200;

        fastcgi_index index.php;
        fastcgi_read_timeout 3000;
    }

    if ($request_uri ~ (?i)^/([^?]*)\.php($|\?)) {
        return 301 /$1$is_args$args;
    }
    rewrite ^/index$ / permanent;
    rewrite ^/(.*)/$ /$1 permanent;
}

Actualmente tengo el ejemplo anterior en vivo en https://www.websemantica.co.uk .

Intente agregar algunos valores en el formulario. Debería mostrarse Success!en azul cada vez. A veces se muestra Failure!en rojo. Se puede tomar un buen número de actualizaciones de página para cambiar de Success!a Failure!o viceversa. Eventualmente, se mostrará Success!cada vez, por lo tanto, debe haber algún tipo de problema de almacenamiento en caché.


Estaba buscando el mismo caso y encontré comentarios muy útiles en la realpathpágina de funciones . Tal vez podría ayudarte.
marv255

@ marv255 Traté de usar realpathcon file_get_conentsy sin suerte. Todavía a veces se carga desde el caché.
Dan Bray

2
Me refiero no solo a realpathalgo comoclearstatcache(true); file_get_conents(realpath($fileName));
marv255

Pruebe linux.die.net/man/8/updatedb ejecute el comando entre llamadas consecutivas. Aunque no estoy seguro de cómo resolver el problema en php si este es el caso.
Jannes Botis el

Respuestas:


3

Depende demasiado del nivel del sistema operativo. Entonces, ¿qué tal tratar de pensar fuera de la caja? ¿Qué tal intentar leer la ubicación real del archivo readlinky usar esa ruta de ubicación real?

$realPath = shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);

No creo que sea suficiente (de fábrica), después de todo, readlink también depende de las llamadas a nivel del sistema operativo y se ve afectado por el caché.
Bahram Ardalan

3

Este es el comportamiento deseado de PHP, puede ver esto aquí porque PHP utiliza realpath_cachepara almacenar las rutas de los archivos debido a las mejoras de rendimiento para que pueda reducir las operaciones de disco.

Para evitar este comportamiento, tal vez pueda intentar borrar realpath_cacheantes de usar la get_file_contentsfunción

Puedes probar algo como esto:


clearstatcache();
$data = file_get_contents("Your File");

Puede leer más para clearstatcache en PHP doc.


2

Hay dos cachés.

Primero el caché del sistema operativo y luego el caché de PHP.

En la mayoría de los casos clearstatcache(true)antes file_get_contents(...)hace el trabajo.

Pero a veces también necesita borrar el caché del sistema operativo. En el caso de Linux, allí puedo pensar en dos lugares para borrar. PageCache (1) y dentries / inodes (2).

Esto aclara ambos:

shell_exec('echo 3 > /proc/sys/vm/drop_caches')

Nota: Esto es bueno para la resolución de problemas, pero no para las llamadas frecuentes en producción, ya que borra todo el caché del sistema operativo y le cuesta al sistema unos momentos de reabastecimiento de caché.


Esto no funciona, a veces todavía carga el valor almacenado en caché y necesito una solución que sea buena para llamadas frecuentes en producción.
Dan Bray

2
@DanBray, ¿podrías registrar cosas para descubrir más sobre la naturaleza de algunas veces ?
Bahram Ardalan

1
@DanBray, ¿y cómo detectas la apariencia del antiguo valor? ¿Podría ser que su prueba devuelva el valor anterior debido a otras condiciones de prueba mientras el valor allí realmente ha cambiado?
Bahram Ardalan

2

"El problema es después de eliminar y recrear un enlace simbólico"

¿Cómo eliminas el enlace simbólico? Eliminar un archivo (o un enlace simbólico) debería borrar automáticamente el caché.

De lo contrario, podría ver lo que sucede si hace:

// This has "race condition" written all around it
unlink($link);
touch($link);
unlink($link); // Remove the empty file
symlink($target, $link);

Si esto no resuelve el problema, ¿podría ser un problema con nginx como en este problema? ?

Intente registrar todas las operaciones en un archivo de registro para ver qué sucede realmente .

o tal vez...

... ¿podrías prescindir de los enlaces simbólicos por completo ? Por ejemplo, almacene en una base de datos, memcache, archivo SQLite o incluso un archivo JSON el mapeo entre "nombre de archivo" y "objetivo de enlace simbólico real". Utilizando, por ejemplo, redis u otros almacenes de claves, puede asociar el "nombre de archivo" con el objetivo del enlace simbólico real y omitir por completo la resolución del sistema operativo.

Dependiendo del caso de uso, esto podría resultar incluso más rápido que usar enlaces simbólicos.


No pude ver cómo esto puede relacionarse con nginx ya que parece que no hay una cosa http entre el proceso php y el sistema de archivos local. ¿Ser el proceso padre hace que nginx sea de alguna manera relevante?
Bahram Ardalan

@BahramArdalan el hecho es que no sabemos cómo se diagnosticó el problema o cuáles son los enlaces simbólicos o cómo se usan. Por lo tanto, es concebible que la falta de coincidencia de contenido se haya detectado aguas abajo de nginx, y que en realidad no esté relacionada con PHP. Un SCCCE sería de gran ayuda.
LSerni

Si. Tenemos que profundizar un poco en ese "cómo".
Bahram Ardalan

1

Hubo dos problemas que causaron el problema.

Primer problema

Ya publiqué como y edito en la pregunta. Es un problema con la configuración de Nginx.

Estas líneas:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;

necesario reemplazado por:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

Segundo problema

El segundo problema era que necesitaba llamar clearstatcacheantes de llamar file_get_contents. Solo quiero llamar clearstatcachecuando sea absolutamente necesario, así que escribí una función que solo borra el caché cuando el directorio incluye un symlink.

function file_get_contents1($dir)
{
    $realPath = realpath($dir);
    if ($realPath === false)
        return '';
    if ($dir !== $realPath)
    {
        clearstatcache(true);
    }
    return file_get_contents($dir);
}

1

Estoy dejando mi primera respuesta, ya que sigue siendo una respuesta válida. Estoy mejorando la respuesta de @DanBray mediante la implementación de clearstatcache (verdadero, $ nombre de archivo).

Hubo dos problemas que causaron el problema.

Primer problema

Ya publiqué como y edito en la pregunta. Es un problema con la configuración de Nginx.

Estas líneas:

fastcgi_param SCRIPT_FILENAME $ document_root $ fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $ document_root;

necesario reemplazado por:

fastcgi_param SCRIPT_FILENAME $ realpath_root $ fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $ realpath_root;

Segundo problema

El segundo problema era que necesitaba llamar a clearstatcache antes de llamar a file_get_contents. Solo quiero llamar a clearstatcache cuando sea absolutamente necesario, por lo que escribí una función que solo borra el caché cuando el directorio incluye un enlace simbólico.

function file_get_contents1234_hard_drives($dir_go_1){
    $realPath = realpath($dir_go_1);
        $myDirectory=opendir(dirname($realPath));        
        while($entryName=readdir($myDirectory)) {
          $dirArray[]=$entryName;
        }

        /* Finds extensions of files used for my site theelectronichandbook.tech
        function findexts ($filename) {
          $filename=strtolower($filename);
          $exts=split("[/\\.]", $filename);
          $n=count($exts)-1;
          $exts=$exts[$n];
          return $exts;
        }*/

        // Closes directory
        closedir($myDirectory);

        // Counts elements in array
        $indexCount=count($dirArray);
        for($ArPos=1;$ArPos<=$indexCount;$ArPos++){
            /*used for my site theelectronichandbook.tech
            if($_SERVER['QUERY_STRING']=="hidden"){
                $H="";
                $af="./";
                $atext="Hide";
            }else{
                $H=".";
                $af="./?hidden";
                $at="Show";
            }*/
            if(strpos($dirArray[$ArPos], "Symlink") !== false){
                clearstatcache(true,$dir_go_1);
            }
        }
    return file_get_contents($dir_go_1);
}

Probé el código anterior con mi servidor web y funcionó.


1
Desafortunadamente, no funciona para mí en mi servidor web.
Dan Bray

Bueno, volveré a la mesa de dibujo. @DanBray
JTS

1
Muchas gracias, pero desafortunadamente, hay muy poco tiempo antes de que expire el período de recompensa. Sin embargo, si piensa en una solución con la que estoy 100% satisfecho, le otorgaré una recompensa adicional. Además, file_get_contents1es parte del marco que he creado, por lo que se usa mucho, lo que hace que la optimización sea importante.
Dan Bray

$dir_go=readdir("$realPath")devuelve nulo
Dan Bray

Puede que sea necesario cambiar a While($dir_go!==null)@DanBray
JTS

0

Intente colocar el código dentro de un elemento que se actualiza continuamente utilizando Jquery, así como forzar la revalidación y borrar la captura estática. Este código ha sido modificado de la respuesta original @naveed .

form.php:

 <meta http-equiv="Cache-Control" content="no-store, must-revalidate" />
 <meta http-equiv="Expires" content="0"/>
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 <script> 
 jQuery(document).ready(function(){
    jQuery('.ajaxform').submit( function() {
        $.ajax({
            url     : $(this).attr('action'),
            type    : $(this).attr('method'),
            dataType: 'json',
            data    : $(this).serialize(),
            success : function( data ) {
                        // loop to set the result(value)
                        // in required div(key)
                        for(var id in data) {
                            jQuery('#' + id).html( data[id] );
                        }
                      }
        });
        return false;
    });
});
var timer, delay = 30;
timer = setInterval(function(){
    $.ajax({
      type    : 'POST',
      url     : 'profile.php',
      dataType: 'json',
      data    : $('.ajaxform').serialize(),
      success : function(data){
                  for(var id in data) {
                    jQuery('#' + id).html( data[id] );
                  }
                }
    }); }, delay);
 </script>
 <form action='profile.php' method='post' class='ajaxform'></form>
 <div id='result'></div>

profile.php:

 <?php
       // All form data is in $_POST
       // Now perform actions on form data here and create an result array something like this
       clearstatcache();
       $arr = array( 'result' => file_get_contents("./myfile.text") );
       echo json_encode( $arr );
 ?>
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.