Session a Application v ASP .NET vs. threadová bezpečnosť
HTTP protokol, ktorý sa využíva na komunikáciu internetového prehliadača a webového servra je bezstavový a preto znamenal od počiatku problém pre vývojárov. Všetky požiadavky na stránku voči webovému servru sú od seba nezávislé a nevedia vzájomne zdieľať žiaden stav a informácie. Od čias zažitia protokolu sa však vynašli rôzne spôsoby ako tento problém preklenúť.
Ak hovoríme o stave, tak s ním úzko súvisí problém paralelného resp. konkuretného prístupu. Dva paralelné dotazy, či už od rôznych alebo dokonca jedného používateľa možu paralelne pristupovať a mať ambíciu meniť "stav" uchovávaný na servry. Aj keď sa pod problém konkurentného prístupu má veľa implikácií, my sa sústredíme primárne na oblasť zdielania dát prostredníctvom operačnej pamäte v rámci ASP .NET frameworku. Máme k dispozícii často vyžívané rozhrania Session a Application, ktoré sú dedičstvom staršieho ASP frameworku, ale ktoré nie sú neznáme aj z iných platforiem. Veľmi zjednodušene, kým Session rieši problém zdieľania stavu v kontexte jedného používateľa, Application je určená na zdieľanie stavu na úrovni celej aplikácie, teda zjednodušene, medzi jednotlivými používateľmi.
Opať veľmi zjednodušene si na úvod povedzme, že každému dotazu na konkrétnu aspx stránku v ASP .NET frameworku zodpovedá pridelnie samostatného vlákna(threadu) z threadového poolu v rámci procesu, v ktorom beží daná webová aplikácia. V jeho kotexte sa vytvorí nová inštancia triedy Page zodpovedajúca požadovanej aspx stránke. Postupne sa na ňej vyvolajú metódy žívotného cyklu stránky, až kým nevznikne výsledné HTML, ktoré sa odošle ako odpoveď na požiadavku prehliadača späť. V rámci životného cyklu stránky, podľa potreby, kód stánky môže pristupovať tak ku informáciam zdieľaným prostredníctvom Application ako i Session. Tu vnizká problém. Čo v prípade, čo sa stane v prípade, že sa stav snažia modifikovať dve spracovávané inštancie stránky paralelne? Predstavme si, len čisto pre ilustráciu, že v application incrementujeme nejaký counter, ktorý hovorí o tom, koľko ľudí hitlo od štartu aplikácie nejakú stránku. Do Loadu stránky vložíme kód.
(kôly názornosti)
int counter= (int) Application["counter"];
Application["counter"]= ++counter;
(na tomto malo kúsku kódu sa dá ilustrovať veľa zaujímavých veci, napríklad sa udeje boxing a unboxing value typu)
OK, kde je problém?
Používateľ A resp. konkrétna inštancia stránky "vyberie" hodnotu z Application dictionary. Povedzme že je aktuálna hodnota bude 10. Používateľ B resp. paralelne spracovávaná inštancia tej istej stránky "vyberie" hodnotu z Application dictionary. Jej aktuálna hodnota bude 10. Inštancia A incrementuje counter a priradí spať hodnotu spať do Application. Jej hodnota je 11. To isté robí ale aj inštancia B, takže výsledkom za určitých nebude možno niekým očakávaných 12, ale 11! Problém je samozrejme riešiteľný.
Úprimne, už ste sa nad tým zamysleli niekedy?
Možno si kladiete teraz otázku, či existuje podobný problém aj v prípade, keď používate Session. Odpoveď je jednoduchá, teoreticky existuje, ale je vyriešený. Dva frames(alebo paralelne exekutnuté kusy ajaxového kódu, alebo okná v IE otvorené cez Ctrl+N, keď zostávame v pôvodnom processe IE a pôvodnej session) v rámci jedného session modifikúju obe stav uložený v session.
Session implementuje tzv. reader/writer lockovací mechanizmus. Čo to vlastne znamená?
- stránka (alebo frame) ktorá modifikuje session stav(nastavujeme cez enableSessionState="true" v rámci page direktívy) bude držať nad session tzv. writer lock, až kým nebude request úplne obslúžený.
- stránka (alebo frame) ktorá len číta zo session, má povolené len čítanie (nastavujeme cez enableSessionState="readOnly" v rámci page direktívy) bude držať nad session tzv. reader lock, až kým nebude request úplne obslúžený
a teraz si popíšme všetky situácie, keď sa snažia rôzne kusy kódu konkuretne lockovať nejaký zdroj dát. Nie je to na prvé prečítanie ľahko stráviteľné, ale nakoniec zistíte, že jednoduché a pochopoteľné.
- Reader lock zablokuje kód, ktorý sa snaží vytvoriť writer lock, až kým nie je pôvodoný reader lock zrušený.
- Reader lock nezablokuje iný reader lock.
- Writer lock ale zablokuje všetky ostatné pokusy o reader lock a writer lock, až kým nie je zrušený.
Takže z praxe, toto je dôvod, prečo napr. naše dva frames(alebo paralelne exekutnuté kusy ajaxového kódu), ktoré majú write prístup do session musia čakať jeden na vykonanie toho druhého.
Výborne, takže zostáva vyriešiť otázku, čo s tým nešťastným Application. Odpoveď je ale jednodúchá. Nepoužívať. Nazvyme to kľudne prežitkom. Riešenie resp. problematika, ktorú popisujem ďalej, sa tak týka ukadalnia informácii do session, ako aj aleternatívneho využitia statických premenných. Jednoducho musíme dodržať zásady threadovej bezpečnosti. Môžeme použiť podobný reader/writer mechanizmus, ako využíva Session, alebo pre jednoduchosť sa spoľahnúť na exkluzívny lock. Rozhodujeme sa podľa situácie. Tam, kde dochádza ku zmene stavu realtívne zriedka oproti jeho čítaniu, použijeme prvý prístup(reader/writer locking), v inom prípade druhý(exclusive locking).
TODO: rozumný príklad na oba prístupy