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

Komentáre

# duracellko said:

No Application ma vlastny zamykaci mechanizmus. Metody Lock a Unlock. Takze kod by mal vyzerat nasledovne.

Application.Lock();

int counter = (int)Application["counter"];

Application["counter"] = ++counter;

Application.Unlock();

teda samozrejme podla uplne spravnosti to dat do try..finally bloku.

Pri session tieto metody nie su. Ale je mozne, ze HTTP requesty s tou istou poziadavkou sa spracovavaju seriovo a nie paralelne. Necital som o tom, ale tipujem, ze je to tak. Na zamykanie session nie je mozne pouzit techniky pre synchronizovanie threadov. A to z dovodu, ak bezi webova aplikacia na clusteri, tak sa to zvycajne riesi tak, ze sa session uklada na SQL Server, aby obidva uzly clustera pracovali s tou istou session. Takze zamknut session pomocou mutexu alebo semaforu nema vyznam, kedze sa s tou session moze pracovat aj na inom serveri (uzle). Ale vsimol som si, ze v tabulke, kde sa ukladaju session je aj stlpec Locked, takze je dost mozne, ze ak pride request, tak sa session uzamkne a ostatne requesty s tou istou session sa odlozia az kym sa session neodomkne. Dokonca je tam aj cas zamknutia, takze zamky mozno casom vyprsia, pre pripad, ak by sa jeden uzol zrutil alebo vypli prud pocas spracovavania requestu.

teraz sme nasadzovali jednu aplikaciu na cluster, takze mozno o tom napisem vlastny blog.. este potrebujem dotiahnut par detailov

Monday, June 18, 2007 11:54 AM
# T said:

Vdaka, za doplnenie, je to dalsie mozne  riesenie problemu -

nemal by vyzerat, chcel som na nom ilustrovat v com je problem :)

Nie, nad session je vyrobeny reader/wrtier locking mechanizmus ako popisujem a vies deklarativne povedat, ci stranka lockuje pre write alebo len read alebo vobec nie.(z clanku mi to vyhodilo zo zatvoriek tie deklaracie enableSessionState, skusim to tam nejako nanutit).

Seriovo sa spracuju requesty v zavislosti od toho, ako sa snazia lockovat a aky je aktualny Lock.(vid clanok)

"Na zamykanie session nie je mozne pouzit techniky pre synchronizovanie threadov."

In memory state na clustry je nezmysel, takze to vobec nemusime riesit(preto SQL)

Ten locking mechanizmus pre sessions samozrejme funguje transparentne aj pri SQL mode, preto su tam tie stlpce. Takze ak prechadzas na cluster, mas enablenute sessions a zmenis mod na SQL, tak Ti to pobezi uplne transparetne aj na clustry.

Monday, June 18, 2007 12:17 PM
# T said:

Apropo SQL session su vrazda ak sa v aplikacii pouzivaju nejako intenzivne, preto sa im treba vyhnut. A ak sa neda, tak StateServer.

Monday, June 18, 2007 12:27 PM
# duracellko said:

no mne sa zdal tento clanok nejaky nedokonceny.. teda, ze tu chyba, ze ako sa to nastavuje v asp.net.. len ako vravis, ze ti to vyhodil zo zatvoriek. Ja som najprv myslel, ze tu popisujes nejaky vlastny zamykaci mechanizmus. Az teraz som pochopil, ze to je vlastne o EnableSessionState atribute. Asi som to precital moc rychlo.

Ale nevedel som, ze EnableSessionState sa da nastavit na ReadOnly.

StateServer ma ale jednu nevyhodu oproti SQL state. Pri jeho pouziti web cluster straca svoj vyznam. Ak vypadne StateServer, tak nefunguju vsetky uzly clustera. Ale SQL state sa da znova clustrovat a teda ak vypadne jeden SQL server, tak ho zastupi druhy.

Monday, June 18, 2007 2:09 PM
# T said:

Vdaka Duracellko, skusim s tym nieco este urobit, ono je niekedy tazke pozriet sa na vec ocami ineho, ked nieco clovek nosi v hlave.(preto napisat kvalitny clanok je IMHO umenie)

Jasne, tam je otazka, ci chces zvysit performance clustrom alebo garantovat dostupnost(alebo oboje). Zvacsa sa dava doraz na to prve, ale v principe mas o tych dosledkoch pravdu. Najlepsie je obist state uplne.

Inak pisal som "in memory"...malo byt "in proc"...aj stateserver je "in memory"

Monday, June 18, 2007 3:42 PM
# skippo said:

Zaujimalo by ma, ci ASP.NET v pripade EREW (exclusive read exclusive write) riesi pripade deadlocky, Lebo takto deklarativne zadefinovanie lockovania priam vola po deadlockoch ( aj ked v pripade Session je tato otazka mozno irelevantna, pravdepdobne tam po case vyskoci nejaky timeout alebo nieco, co request a jeho data zabije).

Monday, June 25, 2007 2:52 PM
# T said:

Deadlocky su problem aj pri excluzivnom locku (Lock). V pripade, ze ide len o jeden nezavisle lockovany objekt, problem nastat samozrejme nemoze.(tento pripad).

.NET neriesi deadlocky(ako napr. ms sql), musis si dat na ne pozor.

A potom ako si napisal, v pripade ASP.NET aplikacie, v zavislosti, od toho, ci nastane v page resp. httpHandlery, alebo niekde "vyssie", Ti to vyleti na request timeoute.

Monday, June 25, 2007 3:47 PM
Prihlásiť | Registrovať | Pomoc