rejs kondišnTakédata race, race hazard, závodní situacePokročilý

Definice

Race condition je chyba souběhu, při níž správnost programu závisí na náhodném pořadí nebo načasování operací nad sdíleným stavem. Projeví se typicky ve vláknech, asynchronním kódu, databázových transakcích nebo distribuovaných systémech, kde dvě části programu čtou a mění stejná data bez dostatečné synchronizace.

Kategorie: Softwarový vývojAktualizováno

Kde race condition vzniká

Race condition vzniká tam, kde více výpočtů, vláken, procesů nebo požadavků závisí na stejném stavu a zároveň není pevně určeno, kdo k němu přistoupí jako první. Problém se neomezuje na nízkoúrovňové programování. Stejný vzor se objevuje v backendu, databázích, frontách úloh, distribuovaných systémech i v JavaScriptu s asynchronními callbacky.

Typickým sdíleným stavem je proměnná v paměti, záznam v databázi, soubor, položka v cache nebo externí zdroj, například platební objednávka. Chyba se často projeví jen občas, protože závisí na plánovači vláken, latenci sítě, pořadí zpráv nebo rychlosti databáze. Právě nepravidelnost dělá race condition obtížně laditelnou.

Proč pořadí operací mění výsledek

Mnoho programátorských zápisů vypadá jako jedna operace, ale skutečný běh se skládá z několika kroků. Zvýšení čítače může znamenat načtení hodnoty, výpočet nové hodnoty a zápis zpět. Pokud dvě vlákna načtou stejnou původní hodnotu a obě potom uloží vlastní výsledek, jeden přírůstek se ztratí.

Podobná situace nastává při kontrole a následné akci. Aplikace nejdřív ověří, že položka je skladem, a teprve potom odečte kus. Mezi těmito kroky ale může jiný požadavek provést totéž. Výsledek je záporný sklad, duplicitní rezervace nebo potvrzená operace, která měla být odmítnuta.

Ochrana sdíleného stavu

Základní obrana spočívá v tom, že kritická část proběhne jako jeden bezpečný celek. V paměti k tomu slouží zámky, mutexy, semafory, atomické operace nebo návrh bez sdíleného měnitelného stavu. V databázích pomáhají transakce, vhodná izolační úroveň, unikátní omezení, podmíněné aktualizace a zámky řádků.

Správné řešení má být co nejmenší a co nejblíž místu, kde se porušuje invarianta. Zamknout celou aplikaci sice může chybu skrýt, ale často zbytečně sníží propustnost a přinese riziko deadlocku. Lepší je chránit konkrétní účet, objednávku, záznam nebo datovou strukturu.

Testování závodních chyb

Race condition se špatně potvrzuje jedním lokálním spuštěním. Užitečné jsou zátěžové testy, opakované paralelní scénáře, umělé prodlevy mezi kroky a logování identifikátorů požadavků. V databázích pomáhá testovat dvě nebo více transakcí přesně v místech, kde aplikace čte a mění stejná data.

Samotný test ale nestačí jako důkaz bezpečnosti. Race condition je vlastnost možného prokládání operací, ne pouze konkrétního času běhu. Důležité je také přečíst dokumentaci použité knihovny, databáze nebo runtime a vědět, které operace jsou atomické a které jen tak vypadají.

Příklady z praxe

  1. Ztracené zvýšení čítače v async kódu

    Dvě asynchronní úlohy čtou stejný čítač a každá chce přičíst jedničku. Protože mezi čtením a zápisem proběhne přepnutí úlohy, druhý zápis přepíše výsledek prvního. Dopad je ztracená aktualizace, i když program neběží ve více systémových vláknech.

    import asyncio
    
    counter = 0
    
    async def add_one():
        global counter
        value = counter
        await asyncio.sleep(0)  # jiná úloha se dostane mezi čtení a zápis
        counter = value + 1
    
    async def main():
        await asyncio.gather(add_one(), add_one())
        print(counter)  # může vypsat 1 místo očekávaných 2
    
    asyncio.run(main())
  2. Přeprodání posledního kusu v e-shopu

    Dva zákazníci odešlou objednávku posledního kusu téměř současně. Každá transakce nejdřív přečte, že sklad je kladný, a potom provede odečet. Bez vhodné transakční ochrany nebo podmíněné aktualizace může obchod potvrdit víc kusů, než skutečně má.

    BEGIN;
    SELECT stock FROM products WHERE id = 42;
    -- dvě transakce zároveň uvidí stock = 1
    UPDATE products SET stock = stock - 1 WHERE id = 42;
    COMMIT;
    
    -- bezpečnější vzor:
    UPDATE products
    SET stock = stock - 1
    WHERE id = 42 AND stock > 0;

Časté omyly

Mýtus„Když aplikace běží jen na jednom serveru, race condition se mě netýká.“
Ve skutečnostiRace condition může vzniknout i na jednom serveru, pokud více vláken, procesů, workerů nebo asynchronních úloh sdílí stejný stav. Jeden stroj pořád může zpracovávat více požadavků současně.
Mýtus„Transakce v databázi automaticky vyřeší každou race condition.“
Ve skutečnostiTransakce pomáhá, ale konkrétní výsledek závisí na izolační úrovni, použitých dotazech a omezeních. Některé závodní situace vyžadují podmíněný update, unikátní constraint nebo explicitní zamčení řádku.

Časté dotazy

Kdy je race condition bezpečnostní chyba?
Race condition nemusí vždy způsobit bezpečnostní zranitelnost, ale race condition se bezpečnostním problémem stává, když útočník dokáže ovlivnit pořadí operací ve svůj prospěch. Typický scénář je kontrola oprávnění následovaná použitím souboru, účtu nebo tokenu. Pokud se chráněný objekt mezi kontrolou a akcí změní, aplikace může provést operaci, kterou měla odmítnout.
Jak se race condition testuje v praxi?
Race condition se odhaluje kombinací návrhové kontroly a cílených testů. Test má spouštět stejnou operaci paralelně, vkládat umělé prodlevy mezi čtení a zápis a kontrolovat invarianty, například nezáporný zůstatek nebo unikátní rezervaci. Lokální úspěch testu není definitivní důkaz, protože závodní chyba může záviset na vzácném proložení operací.
Kdy race condition není totéž co deadlock?
Race condition není totéž co deadlock. Race condition znamená, že výsledek závisí na nepředvídaném pořadí kroků a program může doběhnout s chybnými daty. Deadlock znamená, že dvě nebo více částí programu čekají navzájem na prostředky a běh se zastaví. Špatně navržené zámky mohou race condition odstranit, ale zároveň vytvořit deadlock.
Může race condition vzniknout bez více vláken?
Race condition může vzniknout i v jednovláknovém prostředí, pokud program přerušuje práci mezi dvěma souvisejícími kroky. Asynchronní JavaScript, event loop, signály nebo callbacky mohou změnit sdílený stav mezi čtením a zápisem. Jedno systémové vlákno tedy samo o sobě nestačí jako záruka, že operace nad stavem proběhnou atomicky.

Zdroje

  1. Race condition(otevře se v novém okně)Wikipedia
  2. threading — Thread-based parallelism(otevře se v novém okně)Python Software Foundation
  3. 13.2. Transaction Isolation(otevře se v novém okně)PostgreSQL Global Development Group
  4. Isolation In SQLite(otevře se v novém okně)SQLite

Související pojmy

Potřebujete to vyřešit v praxi?

Poradíme, jak na to ve vašem projektu

Vysvětlit pojem je jedna věc, navrhnout kolem něj funkční řešení druhá. Ozvěte se a probereme, co dává smysl u vás.