Session refactoring ideas

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

Here are my ideas for refactoring Nette's session implementation. I start with problems of current implementation,
then summarize all current requirements on new imlementation and also add some new ones. Then, I propose new interfaces and briefly discuss their usage.
These interfaces are then shown in details with some notes. Finally, I show outcomes of this implementation and also present some new extensions, which will be now possible to implement
and which weren't possible in old implementation.

Problems with current session:

  • difficult mocking
  • serializes requests – more requests cannot run in paralell
  • almost no possibility for different storages
Paralell access

PHP's implementation of session opens session file when we call sesssion_start() and locks it, so until we finish working with session and close it, another request cannot be processed.
It could be good to implement session storage, where paralell access is possible, e.g. by locking individual namespaces and not whole session object.

All methods which are required:

Here is brief list of all features/methods in current implementation, which should be supported in new one.

get+set cookie_params
start
write_close
destroy
get+set name (session_name)
regenerateId
get+set id
configure - Nette\Session::configure
init storage - _SESSION['__NF']
send cookies - SESS_ID + Browser Key
pre-clean - before session is started, remove all expired data
clean
get+set+has+remove namespace
get namespace iterator
get+set parameters - like Counter, BrowserKey, ...
set+get+has+remove values from namespaces

Additional requirements:

  • easy change of storage backend (classic, mysql, memcache, …)
  • concurrent access – allow multiple simultaneous requests to work with different namespaces
  • allow storage with locking – acquire lock for namespace
  • storage can or can not rely on PHP's session implementation, e.g. it can set dummy methods for session_set_handler and load/write data when loading individual namespaces
  • removing expired values lazily, i.e. not in all namespaces at once, but when namespace is loaded

Proposed interfaces:

  • ISessionStorage – given session_id, handles requests for namespaces and/or parameters
  • ISessionNamespace – manipulation of variables within namespace, can support locking; has data + its metadata + parameters (misc metadata not dependent on data)
  • ISession – configures session, dispatches calls and events and retrieves namespaces
ISessionStorage:
  • constructor($sessionId) – receives session id (but I think constructor shouldn't be in interfaces, any ideas?)
  • basic features
    • open/start() – loads session data and initializes structures (if needed)
    • close() – write data to disk (if needed)
    • destroy() – removes all session data
    • clean() – clean structures (like remove empty namespaces)
    • gc() – remove all old existing sessions (not just mine)
  • global parameter handling
    • {get+set+has+remove}Parameter(…)
  • namespace handling
    • {get+set+has+remove}Namespace(…)
    • getAllNamespaces()
  • misc
    • changeId($newId) – after regenerateId has been called, it moves data to another sessionId
ISessionNamespace:
  • constructor($storage, $name) – loads data from storage
  • destructor – flushes data to storage
  • flush()
  • reload() – reloads data from storage
  • manipulation with variables, like it is now
  • set+get Expiration
  • {get+set+has+remove}Parameter – namespace's metadata
ISession:
  • start() – populaes BrowserKey + Counter + …
  • close()
  • destroy()
  • configure()
  • sendCookie()
  • {get+set}CookieParams
  • regenerateId
  • {get+set}{Id+Name}
  • {get+set+has+remove}Namespace
  • gc() – removes expired values

Outcomes

  • simple implementation of different storages by just implementing ISessionStorage
  • mocking storage with e.g. just static variables
  • mocking Session not to send any cookies, but allowing all storages to be used
  • namespaces can be overriden and thus support locking (if it is supported by storage)

Future work

ILockableSessionStorage: (pesimistic locking, will not be part of Nette)

  • get($namespace, bool $lock = false)
  • set($namespace, $data, $metadata, bool $unlock = false)

Loclable namespace will then loads data with lock and in __destruct() call set() and unlock it

IVersionableSessionStorage: (optimistic locking, will not be part of Nette)

  • get($namespace) → data, meta, version
  • set($namespace, $data, $medatada, $prevVersion) – can throw exception when data has been modified

Not solved problems

  • how to pass sessionId to SessionStorage?
  • should cookie methods be in Session or rather in HttpRequest/HttpResponse? (as proposed by Ondra Mirtes)

Please discuss and provide your ideas.

Editoval juzna.cz (5. 3. 2011 0:06)

Patrik Votoček
Člen | 2221
+
0
-

Není tohle už náhodou pořešené? https://github.com/…tte/pull/206

Btw to už budeme i na českém fóru spíkovat englicky (Tak to jsem pěkně v ***)? Chápu že s tím přepínáním máš problém… :-)

juzna.cz
Člen | 248
+
0
-

V tom pull requestu je IMHO par dobrych veci, ale jinak to toho moc neresi. Chce se to poradne zamyslet a pak az neco implementovat. Proto prosim o pripominky, uz me zadne zadrhely nenapadaji, tak to zkusim naimplementovat jak pisu.

PS: bylo to moc textu aby se mi to povedlo napsat cesky, sorry :/ Ale snad nema nikdo problem to precist.

David Grudl
Nette Core | 8129
+
0
-

Neřeší naprostou většinu požadavků funkce session_set_save_handler?

hrach
Člen | 1834
+
0
-

Řeší. Navíc mnohem efektivněji.

edit: tzn. –1 pro imlementaci neceho takoveho.

Editoval hrach (7. 3. 2011 16:30)

juzna.cz
Člen | 248
+
0
-

Vetsinu jo, ale neresi to treba ten paralelni pristup do session. Je nutne vsechny pozadavky se session serializovat. (coz treba v moji app je docela problem)

Editoval juzna.cz (7. 3. 2011 23:43)

David Grudl
Nette Core | 8129
+
0
-

Proč by ne, pokud tvé úložiště nezamyká soubory, budou fungovat paralelně.

juzna.cz
Člen | 248
+
0
-

Pak ale zas budou mizet data, protoze session si na zacatku nacte vsecka data do pameti a na konci je pak zase ulozi.

Predstav si paralelni beh 2 requestu pouzivajicich session:

A...............end
      B..end

A nacte data, zacne neco generovat a ukladat si to do session. Behem toho uzivatel klikne na B, coz neco ulozi do session, skonci a zapise data. Po chvili skonci A, serializuje celou promennou $_SESSION a ulozi. A ejhle, data z requestu B jsou najednou fuc.

Nebo jde toto nejak vyresit i s klasickou PHP session?

Filip Procházka
Moderator | 4668
+
0
-

V klasické tomu zabraňuje, že nemůžou zapisovat dva requesty zároveň. Ve vlastním handleru si přece můžeš ušetřit zamykání tabulek (MySQLDibiStorage) nebo popř. flockem (FileStorage)

juzna.cz
Člen | 248
+
0
-

Asi se porad nechapeme :( Co ja vim, tak muzu bud zamykat celou session, a nebo mit paralelni pristup. Pri prvnim reseni se requesty serializuji, pri druhem se ztraci data. Zadnou jinou moznost nevidim.

Editoval juzna.cz (8. 3. 2011 15:52)

David Grudl
Nette Core | 8129
+
0
-

juzna.cz napsal(a):

Pak ale zas budou mizet data, protoze session si na zacatku nacte vsecka data do pameti a na konci je pak zase ulozi.

Já vím, ale když to tak moc chceš ;-)

paranoiq
Člen | 392
+
0
-

@juzna: serializace nebude tolik vadit, pokud session otevřeš, přečteš, zapíšeš a uzavřeš hned na začátku požadavku. pokud vykonávání požadavku trvá dlouho, nemusí být session otevřená po celou dobu. takhle to řeší třeba Adminer

juzna.cz
Člen | 248
+
0
-

David Grudl: „Mily zakazniku, chcete aby ten system lagoval neco aby se ztracely data? Zvolte alespon jednu moznost“ ;-)

Jde session otevrit a zavrit vicekrat behem jednoho requestu? A nebo u techto pozadavku ty data hold budu cpat uplne nekam jinam, nez do session, kdyz se vam ty moje napady tak nelibi.

David Grudl
Nette Core | 8129
+
0
-

Jde session otevrit a zavrit vicekrat behem jednoho requestu?

Jde.

JJWorren
Člen | 6
+
0
-

Ahoj hele koukám že to tu už řešíte pokouším se naimplementovat ukládání session do DB a měl bych dotaz kde a jak nette říct že pro všechny sessions má používat můj DatabaseSessionStorage(implementuje ISessionStorage) logicky předpokládám že v bootstrapu něco jako toto

<?php
NEnvironment::getSession()->setStorage(new DatabaseSessionStorage(Model::tb_session_db()));
?>
MartyIX
Člen | 217
+
0
-

Vyřešil někdo nakonec tu paralelizaci? Pracuji se SESSIONs málo ve svém kódu (skoro jen přihlašování).

Zkusil jsem:

if ($user->isLoggedIn()) {
	$identity = $user->getIdentity();
        // Do some work
}

// The method $user->isLoggedIn() starts sessions and concurrent requests are blocked
// until session is closed.
// @link https://forum.nette.org/cs/6375-zamykani-session-blokovani-soucasnych-requestu
$this->context->session->close();

ale toto mi nezafungovalo, následující zápisy do $_SESSION se neuložily.

Něco přehlížím?
______________
Nette 2.0.10

Jan Tvrdík
Nette guru | 2595
+
0
-

následující zápisy do $_SESSION se neuložily.

Tak to je snad jasné, že po volání $this->context->session->close() se už nic do session zapsat nedá. Nebo ne?

MartyIX
Člen | 217
+
0
-

Jan Tvrdík napsal(a):

následující zápisy do $_SESSION se neuložily.

Tak to je snad jasné, že po volání $this->context->session->close() se už nic do session zapsat nedá. Nebo ne?

No, není. Protože:

  1. Už David výše psal, že sessions jdou během běhu PHP skriptu vícekrát otevřít+zavřít.
  2. Nette otevírá sessions vždy při zápisu, viz: https://api.nette.org/…ion.php.html#…
Jan Tvrdík
Nette guru | 2595
+
0
-

@MartyIX: Už chápu, jak jsi to myslel. Zkus session nastartovat znovu explicitně voláním start()