WN

WN (https://www.wn.se/forum/index.php)
-   Serversidans teknologier (https://www.wn.se/forum/forumdisplay.php?f=4)
-   -   PHP skript dör utan varning eller felmeddelande (https://www.wn.se/forum/showthread.php?t=24878)

blixtsystems 2007-11-07 15:06

Jag håller på att parsar handhistoriefiler för poker och efter mycket slit så har jag fått ihop ett system som fungerar bra, men med ett mycket besvärligt undantag.
Då text filerna blir stora så dör skriptet innan det är klart.

Filerna är inte jättestora och problemet visar sig innan de nått 100k.
Systemet är en state machine där jag använder file_get_contents för att läsa in hand för hand (en 20-30 rader åt gången) för att sedan köra en loop med olika "states" som har diverse reguljära uttryck för att extrahera informationen.
Informationen sparas sedan i en array och då en hand är färdig parsad sparas den i en MySQL databas.
Sedan rensar jag arrayerna och börjar på nästa hand.

Enligt memory_get_usage så använder skriptet drygt 5 Mb då det dör.
Jag har testat att höja memory limit till 100M, men det gör ingen skillnad.
Skriptet jobbar bara i några sekunder innan det dör och timeout är satt till 120s.

PHP ger inte ger mig inte någonting att gå på vad det är som får skriptet att dö, varken varningar eller felmeddelanden.
I firefox kör skriptet en 4 sekunder och sedan dyker det upp en "connection reset" sida. I IE dyker det upp en "page not available" sida efter någon sekund.

Det bästa hade väl varit att läsa linje för linje, men det blir lite knepigt att ändra i detta stadium med det system jag använder.
Och man tycker att en 20-30 linjer åt gången inte borde vara några problem alls.

Är det några resursinställningar i PHP eller apache som jag kan fixa problemet?
Koden jag använder för att läsa in en hand från textfilen är:
Kod:

file_get_contents($filename, FALSE, NULL, $offset, $end)
Kan det finnas ett bättre alternativ för att läsa in hand för hand?

Björklund 2007-11-07 15:17

Vad säger php-error-loggen?

blixtsystems 2007-11-07 15:45

Ingenting i varken PHP eller apache loggar, och jag kör med E_ALL.

Jonas 2007-11-07 15:50

Hur körs det? Som Apache modul, CGI eller som CLI script?

CLI är mer eller mindre känt att vara en instabil variant.

Lägg in breaks i scriptet?
Anpassa scriptet så att den inte gör om arbetet när det kraschar utan fortsätter där det slutade. Kan vara att scriptet får något oväntat svar?

Hur hämtar du indata? Från en lokal fil? Från en annan server?

Lite mer info så sätt kan behövas.

blixtsystems 2007-11-07 16:00

Lokalt så kör jag det med en oförändrad xampp installation på XP och det är väl som apache modul.
Jag får samma problem på mitt webhotell som kör med linux och som CGI.

Jag har ingen aning om hur jag skall lyckas få skriptet att börja om där det dog.
PHP har ju slutat exekvera det och jag kan inte komma på någon metod för att känna av det och starta om processen.

Indata är från en lokal fil.

blixtsystems 2007-11-07 16:06

Jag missade....angående breaks så kör ju koden som sagt hand för hand och normalt har jag Charles debugging proxy på och då så försvinner inte sidan utan slutar bara ladda vid en viss punkt.
Så jag kan följa exakt var det dör och det har inte att göra med kod eller input vid den punkten.
Tar jag bort några rader i början av filen så parsar den några rader till, så det förefaller helt klart vara någon slags resursbegränsing.

Jonas 2007-11-07 16:11

Stäng av max_execution_time, höj minnes användning, Finns en hel del man kan ändra i php.ini, gör ändringarna där istället för i scriptet och starta om apache.

Eka även ut data i webläsaren...

blixtsystems 2007-11-07 16:26

Har testat följande i php.ini

pcre.backtrack_limit = 1000000
pcre.recursion_limit = 1000000
ignore_user_abort = On
max_execution_time = 120
max_input_time = -1
memory_limit = -1

Ingen av ändringarna har någon inverkan på hur lång skriptet kommer innan det dör.
pcre.recursion_limit verkar krasha apache vid den extrema inställningen men då den inte gör det har den ingen effekt på hur långt skriptet kör.

blixtsystems 2007-11-07 18:18

Jag märkte nu att om jag inte skriver ut all data utan bara ett id för varje hand så dör skriptet ungefär dubbelt så tidigt.

Det tyder kanske på att det inte är en resurs som tar slut då jag inte ser varför den skulle använda mer resurser för att man tar bort en massa echo's?

Jag har dock fortfarande inte en aning vad det är som dödar skriptet :(

WizKid 2007-11-07 19:44

Har du testat köra skriptet utan att köra det igenom Apache?

blixtsystems 2007-11-07 19:58

Jag testade nu att köra i CLI...är det det du menar?

Resultat...det händer ingenting.
Tar en knapp sekund och sedan är jag tillbaka till prompten.
Jag hittar inte heller några kommandon som verkar som de skulle ge mig mer information.

orreborre 2007-11-07 21:10

Jag tycker det låter som om php är tilldelat för lite minne för uppgiften.
Priva att ändra detta i php.ini och se om det tar längre tid innan det "dör".

blixtsystems 2007-11-07 21:13

Den enda minnesinställningen jag känner till är memory_limit i php.ini, och den gör som sagt ingen skillnad.

SimonP 2007-11-07 22:40

-Testa att minimiera kommandona i din huvudloop, t.ex. ta bort mysql anropen och se om det fungerar.

-Prova att använda fopen() och fgets() (eller fread()) istället för file_get_contents(), eftersom file_get_contents() använder memory-map teknik för att läsa in filen kan det vara värt att testa en variant utan memory mapping.

mbomelin 2007-11-08 07:07

släng in

error_reporting(E_ALL);
ini_set('display_errors', 'On');

längst upp i php-filen. Ibland får man tvinga php att felrapportera.

blixtsystems 2007-11-08 10:39

Tack för tipsen.

Jag har testat tidigare att deaktivera databasdelen samt att inte lagra datan i en array, men samma problem.

fgets är inte så enkelt att ändra till då systemet skall läsa hand för hand och inte linje för linje vilket kanske hade varit bäst....men jag skulle gärna undvika att skriva om såpass mycket.
Jag har testat att läsa in hela filen med fopen och med cURL och sedan använda substr för att plocka ut relevant hand. Skriptet dör på precis samma ställe men minnesanvändningen är högre.

Jag har E_ALL och E_STRICT.
Faktum är att om jag låter skriptet köra tills det dör så dyker det inte ens upp i access.log

Magnus_A 2007-11-08 12:27

Det är två problem. Dels fungerar inte ditt script när du ökar på mängden indata. Dels får du inte bra loggar där du tycker att du ser allt.
Det finns en funktion som heter memory_get_peak_usage. Den kanske kan hjälpa dig att få grepp om minnesförbrukningen.

blixtsystems 2007-11-08 12:48

Jag ser absolut ingenting i loggar eller output vilket sannerligen är ett problem i detta fall....någon slags indikation på varför skriptet dör vorde mycket hjälpsamt, om inte annat för att jag då skulle haft något att googla med för att få mer insikt om problemet.

Jag har testat memory_get_peak_usage och det ligger på ungefär samma som memory_get_usage, d.v.s. drygt 5Mb.

SimonP 2007-11-08 13:06

Testa att läsa betydligt fler än 1 hand/gång, problemet verkar inte vara att minnet tar slut, utan snarare att nått anrop krashar efter att det itererats för många gånger, som t.ex file_get_contents.

Posta källkoden till din huvudloop så blir det lättare att försöka hitta felet.

blixtsystems 2007-11-08 13:50

Det är inte en huvudloop direkt utan tre klasser som interagerar, annars hade jag redan postat den, men det blir en hel del kod som kan vara relevant.

HHParser.php är huvudklassen som kollar vad det är för format på text filen (vilket pokernätverk den kommer ifrån).
Den relevanta koden där är:
Kod:

        function getFormat(){
 // parser rules to get HH format
 $this->Parser->FSM('/<general>/',$this, 'FormatFound', 'iPoker', 'STOP','FMT');
 $this->Parser->FSM('/PokerStars Game/',$this,'FormatFound', 'Poker Stars', 'STOP','FMT');       
 $this->Parser->FSM('/\*\* Game ID/',$this,'FormatFound', 'MicroGaming', 'STOP','FMT');
 $this->Parser->FSM('/Full Tilt Poker Game/',$this,'FormatFound', 'Full Tilt', 'STOP','FMT');       
 $this->Parser->ParseFile(FILE,"FMT", 0, 100);
        }
        function FormatFound($str){
 $this->location=$str;
 $this->addSessionToDB();
 switch($str){
        case "Poker Stars":
  require("classes/sites/ps.php");
  $this->Data->setMode("PS");
  $this->Site=new PS($this);
  break;
        case "MicroGaming":
  require("classes/sites/mg.php");
  $this->Data->setMode("MG");
  $this->Site=new MG($this);
  break;       
        case "Full Tilt":
  require("classes/sites/ft.php");
  $this->Data->setMode("FT");
  $this->Site=new FT($this);
  break;       
 }
        }

parserclass.inc.php innehåller en state machine:
Kod:

<?
/*
Finite State Machine Parser class.
FSM-based text parser framework.
*/

//Parse() result codes
define("FSMSTOP_OK",0); //FSM reached end of text or file.
define("FSMSTOP_UNHANDLED",1);        //FSM stopped because some state is unhandled.
define("FSMSTOP_STOP",2);        //FSM stopped by a handler.

class FSMParser {
        var $FSMArr=array();
        var $STATE;
        /*
 FSM programming
        */
        function FSM($expect,$caller,$execute,$arg,$defaultstate,$state=NULL, $start=NULL, $end=NULL, $greed=FALSE, $recurse=NULL){
 $this->FSMArr[]=array("ex"=>$expect,"ca"=>$caller,"do"=>$execute,"ar"=>$arg,"ds"=>$defaultstate,"cs"=>$state, "st"=>$start, "en"=>$end, "gr"=>$greed, "re"=>$recurse);
        }
        /*
 Main loop method.
        */
        function Parse($text,$state){
 $PTR=0;
 $LEN=strlen($text);
 $RET=FSMSTOP_OK;
 $this->STATE=$state;
 $EN=$LEN;
 $nextState;
 $lastPTR;
 $a=$this->FSMArr;
 $this->FSMArr=array();
 $tok_off=NULL;
 $tok_key=NULL;
 $tok_len=NULL;
 while($PTR<$LEN){
        if(isset($a)){
  foreach($a as $tkey=>$line){ 
          if( (!$line["cs"]) or ($line["cs"]==$this->STATE) ) {
  if($line["st"] != NULL){
          $PTR=$line["st"];
  }
  if($line["en"] != NULL){
          $EN=$line["en"]-$PTR;
  }
  //echo "<br/>STATE: ".$line["cs"]." REGEXP: ".$line["ex"]." RANGE:".$PTR."-".$EN;
  //echo "<br/>".substr($text,$PTR,$EN)."<br/>";
  if(preg_match($line["ex"],substr($text,$PTR,$EN),$matches,PREG_OFFSET_CAPTURE)) {
          if( (is_null($tok_off)) or ($matches[0][1]<$tok_off) or ( ($matches[0][1]==$tok_off) and (strlen($matches[0][0])>$tok_len) ) ) {       
    $tok_off=$matches[0][1];
    $tok_len=strlen($matches[0][0]);
    $tok_key=$tkey;
    $STRING=$matches[0][0];
          }
  }
  if($line["gr"]==TRUE || $line["re"] == TRUE){
          $nextState=$line["ds"];
  }
       
          }
  }
  if(is_null($tok_key)){
          if(!is_null($nextState)){
  $this->STATE=$nextState;
  $nextState=NULL;
  continue;
          } else {
  $RET=FSMSTOP_UNHANDLED;
  break;
          }
  }       
  $PTR+=$tok_off+$tok_len;
  $lastPRT=$PTR;
  $arg=$a[$tok_key]["ar"] != "string" ? $a[$tok_key]["ar"] : $STRING;
  $arr=array($a[$tok_key]["ca"], $a[$tok_key]["do"]);
  $result=call_user_func($arr, $arg);
  $nextState=$result;
  if($a[$tok_key]["re"] != TRUE){
          $this->STATE=$a[$tok_key]["ds"];
  } else {
          $this->STATE=$a[$tok_key]["cs"];
          $nextState=NULL;
  }
  $tok_off=NULL;
  $tok_len=NULL;
  $tok_key=NULL;
        }
 }
 return $RET;
        }
        /*
 Main loop method - file wrapper.
        */
        function ParseFile($filename,$state, $offset=0, $end=NULL){
 ini_set('default_socket_timeout',  120);
 return $this->Parse(file_get_contents($filename, FALSE, NULL, $offset, $end),$state);
        }
}
?>

ft.php innehåller parse regler för Full Tilt filer och anropar först FSM funktionen i parserclass ett antal gånger för att bygga en array med states, och sedan kör den:
Kod:

$this->C->Parser->FSM('/(Seat[\r\n'.$this->playerNameChars.':.$,]+\))+/',$this, 'TablePlayers', 'string', 'HANDFORCED','TABLEPLAYERS', NULL, $streets[0]);
//
// 18 rader med olika states för att gå igenom all information
//
$this->C->Parser->FSM('/\r?\n/',$this, 'HandEnd', 'string', 'STOP','HANDEND', $endIndex);
$this->C->Parser->ParseFile(FILE,"TABLEPLAYERS", $this->offset, $endIndex);

Det få parserclass att läsa in handen och köra reexp uttrycken för att extrahera informationen.

Då alla states är genomgångna så börjar ft.php om från början och bearbetar nästa hand på samma vis.

Tyvärr är det som du ser inte alltför enkelt att ändra så man läser mer eller mindre än en hand åt gången.
Jag kan förvisso läsa in hela filen i ParseFile funktionen i parserclass, men jag måste ändå plocka ut reveant del någonstans.
Resultatet då jag har testat att läsa in hela filen och använda substr för att plocka ur nödvändig del är att den kör lika långt men använder mer minne.

SimonP 2007-11-08 20:39

Du måste lägga in fler felkontroller på fler ställen, t.ex:
var kollar du returnvärdet i Parse() funktionen?
returnvärdet på file_get_contents() ? Ifall returnvärdet blir FALSE kommer Parse() raden: $LEN=strlen($text) bli helt fel, som ett exempel...

ini_set() behövs bara sättas en gång, som det är nu sätts det varje gång ft.php kallar ParseFile()..

Lägg in flera echo så du får koll på värdena under resans gång.

blixtsystems 2007-11-09 12:49

Tack för tipsen Simon.

Visst, felhanteringen är lite bristfällig för tillfället.
Det är lite bökigare att implementera då skriptet anropas från en flash fil och jag kan inte ha någon output annat än från PHP filen som instansierar klasserna, så t.ex. die() eller exit() går tyvärr bort.
Jag debuggar i webbläsaren med echo just nu och kommer väl lägga till lite variabler jag kan följa för att ge detaljerad information till flash klienten om eventuella fel då systemet är live.

Returvärdet på Parse() är egentligen inte till mycket nytta.
Jag anger alltid ett state att gå vidare till eller ett stopp och jag har inte ens behövt köra echo på det för att kolla då det inte varit några frågetecken där.

Att filen finns kollar jag innan parsern körs, men det skadar kanske inte att dubbelkolla.

ini_set slängde jag bara in lite slarvigt där för ett hastigt test, men det fyller ingen funktion och skall väck.

Jag har kört en hel del echo's men plockade bort dem innan jag postade koden.

Hursomhelst verkar det som det inte är något direkt fel med koden som orsakar problemet. Uppenbarligen är den ineffektiv eller problematisk på något sätt eftersom jag har detta problemet, men den exekverar utan problem, skapligt snabbt och utan alltför mycket minnesanvändning då jag testat med massor med olika input filer.
Det är bara att den dör efter ett visst antal händer :(

En sak jag märkt gör att den klarar några händer till är om jag kör flush() efter varje hand.
Egentligen skall inte skriptet generera någon output, och några händer extra gör ingen större skillnad i praktiken, men det kanske kan vara en ledtråd vad det är som spökar?

SimonP 2007-11-09 13:20

Testa lägg in typ: usleep(100000) ibörjan i ParseFile() funktionen, för att slöa ner det en en tiondelssekund och se om det blir nån skillnad.
Eftersom file_get_contents både öppnar och stänger filen kanske inte OS:et hinner stänga den innan nästa anrop.

Jag vet att man kan få mkt konstiga fel i PHP ifall man gör snabba itererande filoperationer på samma fil.

blixtsystems 2007-11-09 14:05

Jag har testat att köra sleep mellan varje hand och nu har jag även ändrat så att jag läser in hela filen en gång och sparar den i en variabel och sedan plockar ut relevant bit med substr istället.
Som vanligt kör skriptet precis lika långt.

Jag har även funderat på om man kunde splitta upp filen i flera delar och ominstansiera klasserna för varje del, men jag kommer inte på något sätt att smidigt lösa det.
Att köra unset() på en klass funkar ju inte...finns det någon funktion för att ta bort en klass?


Alla tider är GMT +2. Klockan är nu 16:33.

Programvara från: vBulletin® Version 3.8.2
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Svensk översättning av: Anders Pettersson