Tipy a Triky s Caché

Primary tabs

Tip dvacátý šestý: objekty a concurrency 2 - swizzling

Když tento seriál před několika lety začínal, byl jeho první díl věnován zajištění izolace instance objektů pro exkluzivní přístup a popisu příslušných API funkcí. Nedávno se mi ale stalo, že mě tento díl dostihl. Jeden ze zákazníků začal mít problémy v aplikaci, přestože důsledně používal exkluzivní zámky pro editování instancí svých objektů. Když po příčině pátral důsledněji, zjistil (a přiznám se že jsem to sám nevěděl) že zámky aplikované na instance objektů mají jednu velmi nepříjemnou vlastnost, a to tu, že se nešíří prostřednictvím odkazů (swizzling) na další propojené objekty.

A to byl právě zdroj oněch problémů. V aplikaci se nacházelo několik míst, kde byla dvěma procesy zároveň modifikována stejná instance objektu. Jednou přímo, podruhé prostřednictvím právě odkazu z jiné, připojené třídy. Jak toto může nastat a jak takové situace ošetřit si ukážeme v následujícícm modelovém příkladu, jehož zdrojový kód najdete zde.

Po stažení, rozbalení a nainstalování projektu můžeme testovat. Otevřeme si 2 konzole Caché a přepneme se do testovacího názvového prostoru, přiravíme si, ALE ZATÍM NESPUSTÍME, příkazy:

    // konzole 1
    >w ##class(User.Test).GoObjectB()
    
    // konzole 2
    >w ##class(User.Test).GoObjectA(0)  

Nejdřív spustíme proces na konzoli 1 a během 10 vteřin i proces na konzoli 2 a sledujeme výstupy. To co uvidíme, může vypadat na první pohled divně, ale je to v pořádku, proces v konzoli 2 se snaží otevřít novou instanci objektu User.B, a jakmile se mu to podaří (poté co proces v konzoli 1 uvolní zámek), načte jej z DISKU a změní, mezitím proces v konzoli 2 provede opětovné načtení instance z disku a zobrazí výsledek. V ideálním případě budou hodnoty obou instancí konzistentní (v praxi se nám totiž kód obou konzolí může vykonat různě rychle a vrátit pak tomu odpovídající - od níže uvedeného obrázku odlišné - výsledky).

A teď zavoláme

    // konzole 1
    >w ##class(User.Test).GoObjectB()
    
    // konzole 2
    >w ##class(User.Test).GoObjectA(1)  

Výsledek bude odlišný - stejná instance objektu vrací různé hodnoty v každém procesu, jak ukazuje souhrnný obrázek:

Obr. 1

Kritický kód uvádím ještě separátně: takto je potřeba vždy ošetřit přístup k odkazovaným objektům, pokud je chceme izolovat.

	#dim oB as User.B
	w !,"Trying to Open B from A"
	set oB=oA.relaceB.%Open(oA.relaceB.%Oid(),4,.sc)
	$$$THROWONERROR(sc,sc)
	set oB.B2 = "B2 Done"

Rozdíly v chování zámků při přímém přístupu a při použítí swizzlingu mohou, jak bylo ukázáno, mít velké důsledky pro aplikaci. Bohužel dokumentace k tomuto chování není dostatečně podrobná.

Možná se Vám tento příklad může jevit jako přitažený za vlasy, ale v praxi je velmi dobře možné na takovou kombinaci dvou procesů pracujících nad stejnou instancí jednou přímo a jednou nepřímo snadno narazit. například, provádíte-li účetní zápis, pak aktualizujete zůstatky na jednotlivých účtech analytické evidence v účetní knize ale také měníte zůstatky na příslušných účtech syntetické evidence. No, a pokud se někdo rozhodne změnit cokoliv na syntetickém účtě v okamžiku provádění účetní operace, je zaděláno na neštěstí - pokud tedy nebudete dbát výše uvedených doporučení.

  • 0
  • 0
  • 81
  • 0