Autor Zpráva
nethor
Profil
Zdravím,

V PHP mám serii časově náročnějších operací:

1. Stažení zipu (server -> server)
2. Rozbalení
3. Extract některých dat
4. Vytvoření obrázku
5. Extract dalších dat

Tuto serii spouštím požadavkem přes Ajax,
je napsaná tak, aby při předčasném ukončení a následném zavolání pokračovala od posleního zpracovaného kroku.

U některých dat trvá serie cca 5 s, ale u některých třeba >30 s a uživatel netuší, co se děje.
Chtěl bych ho informovat o aktuální činnosti.

Jediné řešení, které mě napadá, je vyžádat Ajaxem postupně každý krok zvlášť, ale to protahuje už tak dlouhý čas klient-server komunikací.

Máte nějaký nápad, jak na to lépe?

A související doplňující otázka:
Dokončí se běh PHP scriptu, když v jeho průběhu uživatel např. odklikne jinam nebo třeba chodí prohlížeč?
Keeehi
Profil
Nevím, jak je to moc čisté řešení, ale já to nedávno řešil dvěma ajaxy. Jeden ajax odesílá data a tím inicializuje dlouho_bezici_script.php Ten provádí práci a informaci co zrovna dělá ukládá třeba do databáze. Ihned po odeslání prvního AJAXu posílám druhý, který volá status.php Tento script nedělá nic jiného, než že načte data z databáze a vypíše je. Když mi data z druhého AJAXu dorazí, zobrazím je uživateli a naplánuji další dotaz na status.php za třeba 2 vteřiny. Tím s maximálně dvouvteřinovým zpožděním zjistím, jak je na tom dlouho_bezici_script.php Když mi dorazí odpověď z prvního AJAXu, znamená to, že dlouho_bezici_script.php zkončil a zruším naplánování druhého AJAXu.

Odpověď na doplňující otázku: záleží na nastavení a kódu
viz php.net/manual/en/function.ignore-user-abort.php
blaaablaaa
Profil
Nebylo by lepsi jen data odeslat, na serveru prijmout, zaradit do fronty a vratit nejaky identifikator? Frontu na serveru prubezne odbavovat a uzivatel se mezitim muze treba ajaxem dotazovat, zda jeho ukol uz byl zpracovan.
Keeehi
Profil
blaaablaaa:
To je samozřejmě taky možné, ze strany javascript velmi podobné řešení. Co to může přinést je to, že pokud ta fronta bude prioritní, můžou mít úlohy které přijdou později přednost.
Na druhou stranu může být problém s tím, že potřebuješ nějaký na pozadí běžící script který frontu zpracovává, což může u hostingu být problém.
nethor
Profil
Keeehi:
Dospěl jsem k podobnému řešení. Na dvou Ajaxech není nic špatného a myslím, že to lépe řešit nejde.
Status jsem nakonec řešil přes $_SESSION, což je asi snazší a o něco rychlejší, než Db.
Díky za názor a odkaz.

blaaablaaa:
Problém je v tom, jak jedním odesláním hned vrátit výsledek a zároveň spustit frontu. - to jsem nerozlousknul.
Když použijeme 2 odeslání, je to principielně stejné řešení, jako uvedl Keeehi. 1. pro suštění fronty, 2. pro Ping (Status)

Takhle nějak to funguje:
class  Status { 
    public $Name     ;    
    function __construct($Name)    {
        if(is_array($Name)) $Name = implode("|", $Name);
        $this->Name = trim($Name)    ;
        if(!isset($_SESSION["Status"])) $_SESSION["Status"] = array();
    } 
    function Set($Value){
        $_SESSION["Status"][$this->Name] = $Value    ;    
    }    
    function Get($Clear = false){
        $Return = $_SESSION["Status"][$this->Name]    ;
        if($Clear) $_SESSION["Status"][$this->Name] = null    ;
        return $Return    ;    
    }     
} 

// -----------------  Script

$Status = new Status("identifikator")    ;

if($_POST["Status"]) {    // dotaz na status 
    echo $Status->Get()  ;
    die();
}

// ------------- Fronta

$Status->Set("Stahuji soubor.")    ;

// .. Download

$Status->Set("Rozbaluji.")    ;

// .. Unzip

$Status->Set("Extract dat.")    ;

// .. Extract 
// ....

$Status->Set("Hotovo.")    ;

echo "Konec fronty"  ;
Keeehi
Profil
nethor:
Status jsem nakonec řešil přes $_SESSION, což je asi snazší a o něco rychlejší, než Db.
To už je jedno jaké úložiště se zvolí. Já to ukládal do html souboru se statickým jménem. Tudíž kdybych to pustil v druhém okně, byl by problém. Což u mě nevadilo, protože šlo o proof of concept aplikaci.

Chválím za ten identifikátor v session, jinak by jsi ten problém s více instancemi měl taky.

Problém je v tom, jak jedním odesláním hned vrátit výsledek a zároveň spustit frontu.
Dá se to udělat, že posleš prohlížeči výsledek, řekneš, že jsi skončil ale přesto pracuje dál. Dělalo se to nějak takto (nalezeno na internetu)
ob_start();

/*
 * Generate your output here
 */ 

ignore_user_abort(true);
$content = ob_get_contents();
ob_end_clean();
$len = strlen($content);
header('Connection: close');
header("Content-Length: $len");
echo $content;
flush();

/*
 * Continue with script
 */

Taky by se to nejspíše dalo vytvořit pomocí vláken nebo spouštění nových procesů. Což na hostingu zase může být problém.
nethor
Profil
No tak mi to stejně nefunguje. (na localu ani na hostingu)
Z nějakého důvodu PHP čeká na dokončení dlouhého scriptu a neodpovídá na dotaz na status , dokud dlouhý script nedoběhne.
Zkoušel jsem i ob_start(); flush(); (dobrý nápad! :) , ale nepomohlo.
Vypadá to, že je je pro jednu session vyhrazený jen jeden proces.
Keeehi
Profil
nethor:
Nevím to přesně, ale je to dost možné, dávalo by to smysl. Session se serializuje do souboru. Je dost možné, že PHP na začátku ten soubor načte a uzamkne. Deserializuje obsah do $_SESSION. Na konci scriptu zase serializuje obsah $_SESSION, nahradí tím obsah souboru a odemkne ho.

Pokud je ale opravdu problém v session, pak ale přece nemůže fungovat ani [#5].

Můžeš si vytvořit "vlastní implementaci session" bez zamykání a se serializací p každém zápisu. V tomto případě by to bylo v pořádku, protože zapisuje jen jeden proces tudíž paralelní přístup k sdílenému prostředku nebude dělat ošklivé věci. Je ale třeba mít zvláštní soubor pro každý proces a ne jeden, který sdílí více procesů se stejným PHPSESSID

Nebo použiješ tu databázi, ta paralelní přístup zvládá. Pokud můžeš upravovat prostředí serveru, můžeš zkusit třeba redis - velmi rychlou a jednoduchou databázi v paměti počítače. Myslím, že na tohe by byla idání.
Davex
Profil
Keeehi:
Je dost možné, že PHP na začátku ten soubor načte a uzamkne.
Ano je to tak.

Pro zavření souboru se session se dá použít funkce
session_write_close();
Keeehi
Profil
Možná by stálo otestovat zda by fungovalo toto:
session_start();
$_SESSION["foo"] = "bar";
session_write_close();

sleep(5);

session_start();
$_SESSION["foo"] = "baz";
session_write_close();
nethor
Profil
Keeehi:
když jsem sem vkládal 5 něměl jsem to ještě odladěné a vyzkoušené, byl to spíš náčrt myšlenky.
V praxi to nefunguje.

Je to, jak píšeš, tohle chování má na svědomí session_start(); . Když je současně v souborech LongScript.php i Status.php , čeká se na dokončení LongScript.php.
(Pozn.: Když je session_start(); v LongScript.php, nede ani běžně udělat refresh stránky (F5), dokud script nedoběhne. To moc nechápu. )

Musím použít jiné úložiště, pak to snad bude chodit.
Pro alternativní ukládání dat jsem si napsal třídu SerialStore, ta by měla jít použít.
Dík.
nethor
Profil
Tak už to šlape.
Jen nevím, jestli tím neudělám binec v ostatních $_SESSION (přihlášení, nastavení, atd ...) , netuším, co to může provést se souběžně běžícím scriptem v jiném okně.
(Edit: session_start(); spouštím na začátku všech scriptů v tomto projektu.)

Když tak prosím kritiku.

class  Status { 
    public $Name     ;    
    function __construct($Name)    {    
        if(is_array($Name)) $Name = implode("|", $Name);
        $this->Name = trim($Name)    ;
        if(!isset($_SESSION["Status"])) $_SESSION["Status"] = array();
    } 

    function Set($Value){
        session_start()    ;
        $_SESSION["Status"][$this->Name] = $Value    ;        
        session_write_close();
    }    
        
    function Get($Clear = false){
        session_start()    ;
        $Return = $_SESSION["Status"][$this->Name]    ;
        if($Clear) $this->Clear()    ;
        session_write_close();
        return $Return    ;    
    }     
    
    function Clear(){
        session_start()    ;
        $_SESSION["Status"][$this->Name] = null    ;
        session_write_close();
    }    
} 



// -----------------  Script
 
session_write_close();    
 
$Status = new Status("identifikator")    ;
 
if($_POST["Status"]) {    // dotaz na status 
    echo $Status->Get()  ;
    die();
}
 
// ------------- Fronta
 
$Status->Set("Stahuji soubor.")    ;
 
// .. Download
 
$Status->Set("Rozbaluji.")    ;
 
// .. Unzip
 
$Status->Set("Extract dat.")    ;
 
// .. Extract 
// ....
 
$Status->Set("Hotovo.")    ;

session_start()    ;
 
echo "Konec fronty"  ;

.. také přemýšlím, jestli správně nepatří session_write_close(); a session_start(); spíš do třídy, ale ve skutečném scriptu používám více instancí Status(), tak nevím.
Kubo2
Profil
nethor:
Možno by stálo za to si vyskúšať otvoriť socket pomocou stream_socket_server(), ku ktorému by sa následne dalo pripojiť z JavaScriptu cez WebSockets a takto priebežne počúvať stav dokončenia skriptu.

Vaše odpověď


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: