Insufficient memory to run script

Občas se v Dynamics AX můžete setkat s následující chybovou zprávou:

Error executing code: Insufficient memory to run script.
(Česky: Chyba při vykonávání kódu: Nedostatečná paměť pro běh skriptu.)

Informace o této výjimce lze najít v článku na Technical Support Blogu, ten ale obsahuje určité nepřesnosti a řadou aspektů se vůbec nezabývá. V tomto příspěvku chci lépe popsat, co způsobuje tuto chybu, a přidat pár dalších detailů.

Jak to funguje

Možná byste očekávali, že Insufficient memory to run script se vztahuje k volné operační paměti, ale není tomu tak. Dynamics AX uměle limituje, kolik dat můžete najednout uložit do proměnné, například jak dlouhý text můžete uložit do proměnné typu str.

Předpokládejme, že limit je nastaven na 9 MB (o konfiguraci bude řeč později). Následující kód se snaží načíst text o velikosti 10 MB, což vyvolá výjimku.

s = System.IO.File::ReadAllText(@'c:\10MB'); //Insufficient memory to run script

Pokud ale načtete stejný obsah po dvou menších blocích, uloží se bez problémů. Po proběhnutí následujícího kódu je hodnota proměnné s 10 MB, tedy přesahuje nastavený limit (9 MB).

str s, t;
t = System.IO.File::ReadAllText(@'c:\5MB');
s += t;
t = System.IO.File::ReadAllText(@'c:\5MB');
s += t;

Pokud se ale pokusíte uložit hodnotu proměnné s do jiné proměnné, opět dojde k chybě.

t = s; //Insufficient memory to run script

To samé platí i pro přiřazení do parametru metody.

void doNothing(str _parm){}
doNothing(s); //Insufficient memory to run script

S těmito znalostmi je pak také snadné napsat funkci, který zkoumá aktuální hodnotu limitu. Následující kód postupně přidává data do proměnné a volá metodu strLen(), což nakonec způsobí vyvolání výjimky.

str s;
int len;
int unitSize = 512 * 1024;
 
while (true)
{
    s += strRep('x', unitSize);
    len = strlen(s);
 
    infolog.clear(0);
    info(strFmt("%1 znaků, %2 MB", len, (len*2) / (1024 * 1024)));
}

Stojí za zmínku, že při pokusu o nastavení vyšší hodnoty unitSize můžete neočekáváně obdržet stejnou chybu, přestože se pohybujete hluboko pod povoleným limitem. Názorně:

s = strRep('x', (1024*1024)-2); //OK
s = strRep('x', (1024*1024)-1); //Insufficient memory to run script

Funkce strRep() zkrátka používá svůj vlastní limit, který nezávisí na nastavení. To se může týkat i dalších method, takže je vždy nutné analyzovat, jaké konkrétní volání chybu způsobilo.

Výchozí hodnota limitu

Informace zdokumentované výše jsem využil pro hrubé změření výchozího limitu v Dynamics AX 2009 a 2012.

V AX 2012 je limit na klientu i na AOS nastaven na 50 MB.

V AX 2009 není vztah mezi nastaveným limitem a přípustnou velikostí proměnné tak jasný, zejména na AOS (pravděpodobně je část paměti pro něco rezervována). Limit je na klientu i AOS nastaven na cca 7 MB, nevím to zcela přesně. Rozhodně je to ale více než 4 MB uvedené na Technical Support Blogu.

Konfigurace

Limit velikosti proměnné lze konfigrovat v registrech Windows – detaily naleznete na Technical Support Blogu. Identický postup funguje i v Dynamics AX 2012.

Co tam není popsáno je možnost definovat limit také v souboru .axc, což se velice hodí pro sdílené konfigurace. Například pro nastavení limitu na 15 MB otevřete .axc v textovém editoru a přidejte řádku s textem maxbuffersize,Text,15.

Pokud se rozhodnete nastavit limity odlišně na klientu a AOS, nezapomeňte, že se na váš kód mohou aplikovat oba limity (efektivně tedy ten nižší), pokud hodnotu proměnné používáte na klientu i na serveru. Obecně byste se měli takovým voláním vyhnout, pokud nejsou nezbytně potřeba, protože budou dost pomalá a budou zabírat přenosové pásmo mezi klientem a AOS.

Pokud ale víte, že potřebujete změnit limit jen na jedné vrstvě, můžete to samozřejmě udělat. Například můžete nastavit vyšší limit na AOS dedikovaném pro dávkové úlohy.

K čemu je to dobré

Předpokládám, že vývojáři Dynamics AX měli svůj vlastní důvod pro implementaci tohoto limitu, každopádně nám pomáhá najít místa, kde se plýtvá pamětí. Často vůbec není třeba natahovat všechna data do paměti, ale je možné je zpracovávat v menších blocích, ukládat mezivýsledky do souboru a podobně.

Například jsem viděl funkci, která načítala data z databáze, vytvářela z nich XML řetězec a ten na konci ukládala do souboru. Bohužel velikost výsledného řetězce přesahovala povolený limit, takže vše skončilo zprávou o nedostatečné paměti. Přitom stačilo ukládat data do souboru častěji, například po sto záznamech. Pokud by bylo kriticky důležité zapsat soubor buď celý nebo vůbec, ukládal bych data do dočasného souboru a na konci ho přesunul do cílového umístění. Sestavovat celý řetězec v paměti bylo zkrátka plýtvání prostředky.

Právě pro tuto detekci může být užitečné limit na testovacích prostředích snížit.