Problém s Tracy a vlastním SessionHandlerem

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Jan Folwarczny
Člen | 10
+
0
-

Ahoj,

mám problém s Tracy a vlastním session handlerem. Při zapnutém debug modu a při požadavcích končících přesměrováním, ukládá Tracy do session předchozí debug panely. Tohle uložení mi způsobí to, že se předchozí změna dat v session neprojeví (neuloží). Používám vlastní session handler, který ukládá data do MySQL. Ten funguje v produkčním prostředí v pořádku, problém je jen při zapnutém debug modu v Tracy.

Zjistil jsem, že problém nastane u tohoto řádku: https://github.com/…racy/Bar.php#L91

Příklad:
Mám zboží v nákupním košíku, který ukládá data do session v poli jako [id_produktu => počet_kusů]. Provede se subrequest, který v session změní u daného id počet kusů a provede se redirect (v presenteru $this->redirect('this');). Při zapnutém debug modu ještě Tracy na konci scriptu (pomocí register_shutdown_function) provede zápis do sessions podle toho zdrojáku výše. No a právě to způsobí, že se změna počtu kusů neprojeví-neuloží.

Už pár dní nad tím bádám a pořád jsem nenalezl, kde by mohl být problém a proč se to tak vůbec děje. Session nikde ručně v aplikaci neuzávírám. Ta je postavena na Nette 2.2, v configu je pro Nette\Http\Session nastaveno předání toho vlastního handleru, který je taky jako služba, stejně tak jako DibiConnection, který využívá. Nevíte někdo, kde by mohl být problém? Díky!

Vlastní SessionHandler:

<?php

namespace App\Model\Common;


final class DatabaseSessionHandler implements \SessionHandlerInterface
{

    const LOCK_TIMEOUT = 10;


    /**
     * @var \DibiConnection
     */
    private $database;
    private $lockName = '';



    public function __construct(\DibiConnection $database)
    {
        $this->database = $database;
    }



    public function open($save_path, $session_id)
    {
        $this->lockName = 'session_' . session_id();

        while (!$this->database->query("SELECT IS_FREE_LOCK(%s) AS lo", $this->lockName)->fetch()->lo) {
            usleep(100000);
        }

        $this->database->query("SELECT GET_LOCK(%s, %i)", $this->lockName, self::LOCK_TIMEOUT);
        return true;

    }



    public function close()
    {
        $this->database->query("SELECT RELEASE_LOCK(%s)", $this->lockName);
        return true;
    }



    public function destroy($session_id)
    {
        $this->database->query('DELETE FROM [session] WHERE [id] = %s', $session_id);
        return true;
    }



    public function gc($maxlifetime)
    {
        $this->database->query('DELETE FROM [session] WHERE [time] < %i', (time() - $maxlifetime));
        return true;
    }



    public function read($session_id)
    {
        $result = $this->database->query('SELECT data FROM [session] WHERE [id] = %s', $session_id);
        if ($row = $result->fetch())
            return $row->data;
        return "";

    }



    public function write($session_id, $session_data)
    {
        $this->database->query("REPLACE INTO [session] ([id], [time], [data]) VALUES (?, ?, ?)", $session_id, time(), $session_data);
        return true;
    }



}

Editoval Folwik (29. 5. 2014 15:10)

Jan Tvrdík
Nette guru | 2595
+
0
-

To by chtělo projít debuggerem. Jako workaround můžeš na konci běhu aplikace (= před voláním shutdown funkcí) ručně session zavřít (což způsobí úspěšný zápis do databáze a uvolnění zámku).

chloris
Člen | 23
+
0
-

Jen takový dotaz…

Ten while v metodě open:

	while (!$this->database->query("SELECT IS_FREE_LOCK(%s) AS lo", $this->lockName)->fetch()->lo) {
	usleep(100000);
	}

je tam vlastně vůbec k něčemu dobrý? Přece GET_LOCK sám o sobě se snaží získat zámek a čeká po dobu self::LOCK_TIMEOUT sekund, pokud to zamknul již nějaký proces před ním, ne? Navíc stejně mezi dotazem na IS_FREE_LOCK a dotazem na GET_LOCK může teoreticky přijít jiný proces a zámek si urvat pro sebe, takže to stejně ve výsledku skončí na GET_LOCK s tím timeoutem, nebo se mýlím?

Martin Janeček
Člen | 4
+
+2
-

Ahoj,
teď jsem řešil stejný problém. Pokud by se ještě někdo dogooglil sem, tak u mě to byl problém s limitem úložiště. Tracy si do session ukládalo víc dat než se vešlo v MySQL do sloupce typu text – stačilo změnit typ na longtext.