Co je config.threadsafe!

Všichni máme rádi Rails pro jeho eleganci a podporu různých featur, nehledě na to, že drží krok s dnešním technologickým věkem a neustále se inovuje a přináší nové možnosti. Rails umožňuje vývojáři, aby se soustředil na skutečnou práci a neřešil zbytečně problémy samotného jazyka a  jak některé věci vůbec nastavit apod. Dává přednost konvenci před konfigurací.

Toto může však pro někoho znamenat i komplikaci, protože Rails framework skrývá většinu konfigurace ve svém nitru a bere je jako výchozí. Pokud chce vývojář dosáhnout určitých větších změn a nastavení, musí vědět jak na to, nebo kde si to najít. Začínající vývojáři obvykle mají problém v porozumění konceptu Rails, jako je Rack, Rack Middleware, ActiveRecord, Asset Pipeline, thread safety apod. V tomto článku se budu věnovat práve thread safety. 

Co to je vlastně multithreading

Multithreading je velmi důležitý koncept v počítačové vědě a měli bychom mu dávat větší pozornost. Jedná se o vícevláknové zpracovávání určitého kusu programového kódu, instrukcí, nezávisle na sobě, kdy požadavky na procesor probíhají paralelně. Využití můžeme najít například při zpracování mnohdy dlouhých tásků, které probíhají asynchronně na pozadí. 

Jak to tedy funguje v Ruby on Rails? Thread safety bylo zavedeno do Railsů již od verze 2.3. Znamená to, že pokud máme web server podporující multivlákna, tak náš kód by měl být thread safe. Pokud na naši aplikaci přistupuje více vláken najednou, tak by naše sdílená data neměla být poškozena, jakmile všechny vlákna skončí.

Jak nám tedy Rails zaručí, aby naše aplikace byla thread safe?

Rails ve výchozím stavu přidávají middleware, který se nazývá “Rack::Lock”. Tento middleware je druhý v pořadí v balíčku výchozích middlewarů. Pokud chcete vypsat všechny middleware, které vaše aplikace využívá, stačí napsat příkaz rake middleware ve vašem rootu aplikace.

První middleware s názvem ActionDispatch::Static je využíván k servírování statických assetů jako jsou JavaScript, CSS a obrázky.

Rack::Lock nám garantuje, že bude spouštěno pouze jedno vlákno v jeden daný okamžik. Pokud tento middleware odstraníme, potom bude v jednom stejném čase spouštěno více vláken. MRI Ruby má mechanizmus nazývaný GIL (Global Interpreter Lock) nebo GVL (Global VM Lock / Giant VM Lock) od verze Ruby 1.9. GIL nám také garantuje že v jeden okamžik bude spouštěno jen jedno vlákno, ale navíc má funkci kontextového přepínání. Ruby je na tolik inteligentní, že dokáže spustit proces, pokud jiný proces čeká na nějakou operaci, aby mohl být dokončen.

Pojďme se podívat na příklad thread safety aplikace v Rails.

Vytvoříme Rails aplikaci.

rails new test_app

Nyní přistupme do složky s projektem a spusťme bundle pro nainstalování nezbytných gemů.

bundle

Poté vygenerujeme kontroller s pár akcemi.

rails generate controller thread_safety index simple infinite

Tento příkaz vygeneruje kontoller ThreadSafetyController s akcemi index, simple a infinite. Otevřeme šablonu umístěnou v app/views/thread_safety/index.html.erb a nakopírujeme tam následující kód:

 




To nám vytvoří stránku s dvěma tlačítky, jejichž účelem je poslat Ajax požadavky našim akcím v controlleru a zobrazit data v alert boxu.

Nyní přidáme trochu kódu do našeho controlleru umístěného v app/controllers/thread_safety_controller.rb

def simple
sleep(1)
render :text => "Welcome from simple method"
end
def infinite
while true
end
end

Kód uvedený výše je velmi jednoduchý. metoda simple spí 1 sekundu a poté vrátí obyčejný text klientovi, avšak metoda infinite má nekonečnou smyčku a nevrátí nikdy nic, protože bude probíhat donekonečna. Nastartujte server napsáním rails s a přejděte na adresu http://localhost:3000/thread_safety/index

Klikněte na tlačítko “Simple Request” a po vteřině dostanete odpověď v alert boxu ze serveru. Nyní klikněte na tlačítko “Infinite Request” a počkejte na odpověď.

Metoda infinite nikdy nevrátí výsledek, kvůli nekonečné smyčce. Zmáčkněte “Simple Request” znovu. Pokud očekáváte odpověď ze serveru jako před chvílí, pletete se :-).

Toto je thread safety. Rails nám garantuje, že bude naše aplikace v tomto bezpečná, tím, že spustí pouze jeden požadavek v jednom okamžiku. Žádná další vlákna nespustí, dokud nedokončí již běžící proces.

Pokud spustíme aplikaci v produkčním režimu, dostaneme stejný výsledek. Toto je dobré a správné chování, protože nám Rails zajišťuje bezpečný kód.

Ale je tu problém

Pokud jste tuto aplikaci nasadili na produkční server s použitím webového serveru WEBrick (což byste neměli), pak vaši uživatelé můžou mít s webem problémy, protože bude v jeden čas obsloužen pouze jeden dotaz v jednom vlákně. Pokud některé z vláken bude trvat delší dobu, potom ostatní vlákna budou čekat. Toto řešení bude uživatele otravovat.

Naše assety budou serverem vydávány ihned, protože se jich toto nastavení netýká. Assety obsluhuje middleware ActionDispatch::Static, čili jeden daný asset bude vydáván několika uživatelům naráz ve stejnou dobu.

Jak tedy můžeme zprostředkovat požadavky bez blokace těch ostatních? Můžeme jednoduše zapnout config.threadsafe! v development.rb nebo production.rb souboru. Když tuto volbu povolíme, způsobí to v naší aplikaci masivní změnu!

Nyní můžete klikat na tlačítko "Simple" vícekrát po stisknutí tlačítka "Infinite" a server bude vracet výsledky. Zapli jsme podporu multithreadingu, ale nyní je naše zodpovědnost, abychom psali bezpečný kód.

V tomto článku jsme si ukázali koncept thread safety v Ruby on Rails. V reálných aplikacích tomu však je jinak. Můžete použít process-based webové servery pro reprodukci násobných požadavků i s vypnutou volbou config.threadsafe! Mezi tyto webové servery patří např. Unicorn nebo Passenger. Je tomu tak, protože process-based webové servery vytvářejí tzv. workery, kde každý z nich drží instanci vaší aplikace, a proto server dokáže obsloužit několik požadavků naráz.