Problém s Tracy a vlastním SessionHandlerem
- Jan Folwarczny
- Člen | 10
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
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
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
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.