file_get_contents uzyskiwanie błędnych wyników

10

Aktualizacja

Rozwiązałem problem i opublikowałem odpowiedź. Moje rozwiązanie nie jest jednak w 100% idealne. Chętniej tylko usunąć symlinkz cacheze clearstatcache(true, $target)albo clearstatcache(true, $link)ale to nie działa.

Wolałbym też przede wszystkim zapobiec buforowaniu dowiązań symbolicznych lub usunąć dowiązanie symboliczne z bufora natychmiast po jego wygenerowaniu. Niestety nie miałem z tym szczęścia. Z jakiegoś powodu clearstatcache(true)po utworzeniu dowiązania symbolicznego nie działa, nadal jest buforowane.

Z przyjemnością przyznam nagrodę każdemu, kto może poprawić moją odpowiedź i rozwiązać te problemy.

Edytować

Próbowałem zoptymalizować kod, generując plik przy każdym clearstatcacheuruchomieniu, więc muszę wyczyścić pamięć podręczną tylko raz dla każdego łącza symbolicznego. Z jakiegoś powodu to nie działa. clearstatcachemusi być wywoływana za każdym razem, gdy a symlinkwchodzi na ścieżkę, ale dlaczego? Musi istnieć sposób na optymalizację rozwiązania, które mam.


Używam PHP 7.3.5z nginx/1.16.0. Czasami file_get_contentszwraca niewłaściwą wartość podczas używania symlink. Problem polega na usunięciu i ponownym utworzeniu dowiązania symbolicznego, jego stara wartość pozostaje w pamięci podręcznej. Czasami zwracana jest poprawna wartość, czasem stara wartość. Wydaje się losowy.

Próbowałem wyczyścić pamięć podręczną lub zapobiec buforowaniu za pomocą:

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

Tak naprawdę nie chcę wyłączać buforowania, ale nadal potrzebuję 100% dokładności z file_get_contents.

Edytować

Nie mogę opublikować mojego kodu źródłowego, ponieważ jest on zbyt długi i skomplikowany, dlatego stworzyłem minimalny, powtarzalny przykład (index.php), który odtwarza problem:

<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>

Wydawało się bardzo prawdopodobne, że będzie to problem z Nginxkonfiguracją. Brak tych linii może powodować problem:

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

Oto moja Nginxkonfiguracja (widać, że zawarłem powyższe wiersze):

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;
}

Obecnie mam powyższy przykład na żywo na https://www.websemantica.co.uk .

Spróbuj dodać kilka wartości w formularzu. Powinien być wyświetlany Success!na niebiesko za każdym razem. Czasami pokazy są Failure!na czerwono. Może minąć sporo odświeża stronę, aby zmienić Success!się Failure!lub vice versa. W końcu będzie się wyświetlać za Success!każdym razem, dlatego musi istnieć jakiś problem z buforowaniem.

Dan Bray
źródło
Rozglądałem się wokół tej samej sprawy i znalazłem bardzo przydatny komentarz na realpathstronie funkcji . Może to może ci pomóc.
marv255
@ marv255 Próbowałem przy użyciu realpathz file_get_conentsi bez powodzenia. Nadal czasami ładuje się z pamięci podręcznej.
Dan Bray,
2
Mam na myśli nie tylko realpath, ale coś w styluclearstatcache(true); file_get_conents(realpath($fileName));
marv255,
Spróbuj linux.die.net/man/8/updatedb uruchom komendę między kolejnymi wywołaniami. Chociaż nie jestem pewien, jak rozwiązać problem w php, jeśli tak jest.
Jannes Botis

Odpowiedzi:

3

To zbyt wiele zależy od poziomu systemu operacyjnego. A może spróbujesz wymyślić pudełko. A może spróbujesz odczytać rzeczywistą lokalizację pliku readlinki użyć tej prawdziwej ścieżki lokalizacji?

$realPath = shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);
Vo Kim Nguyen
źródło
Nie sądzę, żeby to wystarczyło (po wyjęciu z pudełka), w końcu readlink zależy również od wywołań na poziomie systemu operacyjnego i zależy od pamięci podręcznej.
Bahram Ardalan
3

Jest to pożądane zachowanie PHP, które możesz zobaczyć tutaj, ponieważ PHP używa realpath_cachedo przechowywania ścieżek plików ze względu na ulepszenia wydajności dzięki czemu może zmniejszyć operacje dysku.

Aby tego uniknąć, możesz spróbować wyczyścić realpath_cacheprzed użyciemget_file_contents funkcji

Możesz spróbować czegoś takiego:


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

Możesz przeczytać więcej o clearstatcache w dokumentacji PHP.

Touqeer Shafi
źródło
2

Istnieją dwie skrzynki.

Najpierw pamięć podręczna systemu operacyjnego, a następnie pamięć podręczna PHP.

W większości przypadków clearstatcache(true)wcześniejfile_get_contents(...) działa.

Ale czasami trzeba również wyczyścić pamięć podręczną systemu operacyjnego. W przypadku Linuksa mogę wymyślić dwa miejsca do wyczyszczenia. PageCache (1) i dentries / i-węzły (2).

To usuwa zarówno:

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

Uwaga: Jest to przydatne do rozwiązywania problemów, ale nie do częstych wywołań produkcyjnych, ponieważ usuwa pamięć podręczną całego systemu operacyjnego i kosztuje system kilka chwil ponownego zapełniania pamięci podręcznej.

Bahram Ardalan
źródło
To nie działa, wciąż czasami ładuje wartość z pamięci podręcznej i potrzebuję rozwiązania, które jest dobre dla częstych wywołań produkcyjnych.
Dan Bray
2
@ DanBray, czy możesz logować rzeczy, aby czasem dowiedzieć się więcej o naturze ?
Bahram Ardalan
1
@DanBray, a jak rozpoznać wygląd starej wartości? Czy to możliwe, że Twój test zwraca starą wartość z powodu innych warunków testowych, podczas gdy tamta wartość naprawdę się zmieniła?
Bahram Ardalan
2

„Problem polega na usunięciu i ponownym utworzeniu dowiązania symbolicznego”

Jak usunąć dowiązanie symboliczne? Usunięcie pliku (lub dowiązania symbolicznego) powinno automatycznie wyczyścić pamięć podręczną.

W przeciwnym razie możesz zobaczyć, co się stanie, jeśli:

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

Jeśli to nie rozwiąże problemu, może to być problem z nginx, jak w tym numerze ?

Spróbuj zarejestrować wszystkie operacje w pliku dziennika, aby zobaczyć, co się faktycznie dzieje.

albo może...

... czy mógłbyś zrobić całkowicie bez dowiązań symbolicznych ? Na przykład przechowuj w bazie danych, pamięci podręcznej, pliku SQLite, a nawet pliku JSON mapowanie między „nazwą pliku” a „rzeczywistym celem dowiązania symbolicznego”. Używając np. Redis lub innych magazynów kluczy, możesz powiązać „nazwę pliku” z rzeczywistym celem dowiązania symbolicznego i całkowicie ominąć rozdzielczość systemu operacyjnego.

W zależności od przypadku użycia może to nawet okazać się szybsze niż użycie dowiązań symbolicznych.

LSerni
źródło
Nie widziałem, jak to może się odnosić do nginx, ponieważ wydaje się, że nie ma nic http między procesem php a lokalnym systemem plików. Czy bycie procesem nadrzędnym sprawia, że ​​Nginx jest w jakiś sposób istotny?
Bahram Ardalan
@BahramArdalan faktem jest, że nie wiemy, w jaki sposób zdiagnozowano problem ani jakie są dowiązania symboliczne ani jak są one używane. Można więc sobie wyobrazić, że niedopasowanie treści zostało wykryte za nginx i może być niezwiązane z PHP. SCCCE byłby bardzo pomocny.
LSerni,
Tak. Musimy trochę zagłębić się w to „jak”.
Bahram Ardalan
1

Przyczyną problemu były dwa problemy.

Pierwsza sprawa

Już opublikowałem jako i edytuję pytanie. Jest to problem z konfiguracją Nginx.

Te linie:

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

potrzebne zastąpione przez:

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

Drugi numer

Drugi problem polegał na tym, że musiałem zadzwonić, clearstatcachezanim zadzwonię file_get_contents. Chcę dzwonić tylko clearstatcachewtedy, gdy jest to absolutnie konieczne, dlatego napisałem funkcję, która czyści pamięć podręczną tylko wtedy, gdy katalog zawiera symlink.

function file_get_contents1($dir)
{
    $realPath = realpath($dir);
    if ($realPath === false)
        return '';
    if ($dir !== $realPath)
    {
        clearstatcache(true);
    }
    return file_get_contents($dir);
}
Dan Bray
źródło
1

Pierwszą odpowiedź zostawiam, ponieważ wciąż jest poprawna. Poprawiam odpowiedź @DanBray poprzez implementację clearstatcache (true, $ filename).

Przyczyną problemu były dwa problemy.

Pierwsza sprawa

Już opublikowałem jako i edytuję pytanie. Jest to problem z konfiguracją Nginx.

Te linie:

fastcgi_param SCRIPT_FILENAME $ katalog_główny dokumentu $ fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $ katalog_główny dokumentu;

potrzebne zastąpione przez:

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

Drugi numer

Drugi problem polegał na tym, że musiałem wywołać funkcję clearstatcache przed wywołaniem file_get_contents. Chcę wywoływać clearstatcache tylko wtedy, gdy jest to absolutnie konieczne, dlatego napisałem funkcję, która czyści pamięć podręczną tylko wtedy, gdy katalog zawiera dowiązanie symboliczne.

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);
}

Przetestowałem powyższy kod na moim serwerze internetowym i zadziałał.

JTS
źródło
1
Niestety nie działa to na moim serwerze internetowym.
Dan Bray
Cóż, wrócę do komody. @DanBray
JTS
1
Dziękuję bardzo, ale niestety czas wygranej jest bardzo krótki. Jeśli jednak pomyślisz o rozwiązaniu, z którego jestem w 100% zadowolony, przyznam dodatkową nagrodę. Jest także file_get_contents1częścią frameworku, który stworzyłem, więc jest często używany, dlatego optymalizacja jest ważna.
Dan Bray
$dir_go=readdir("$realPath")zwraca null.
Dan Bray
Może to wymagać zmiany na While($dir_go!==null)@DanBray
JTS
0

Spróbuj umieścić kod w elemencie, który jest stale odświeżany za pomocą Jquery, a także wymuszając ponowną walidację i czyszczenie statycznego catch. Ten kod został zmodyfikowany w stosunku do oryginalnej odpowiedzi @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 );
 ?>
JTS
źródło