AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: Программирование
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 19.10.2011, 19:48   #1  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Развалились InventSum - InventTrans
Обнаружил интересную особенность :
Код обновляющий, InventSum (если быть точнее - пишущий данные об изменения в InventsumDelta - но в данном случае это непринципиально)
для методов
InventTrans.insert()
InventTrans.delete()

расположен после Super()
а для
InventTrans.update()
до Super()

что приводит в ряде случаев к тому, что расходятся данные InventSum - InventTrans.

Например :
в коде есть вызов такого типа :
X++:
            ...
            ttsBegin;
            try
            {
                // здесь расположен код обновляющий InventTrans - например комплектация или резервирование
            }
            catch(Exception::Error)
            {
                // обработка                    
            }
            catch 
            {   // сюда попадает обработка конфликта обновления записи
                // ничего не делаем
            }
            ttsCommit;
            ...
В случае если на обновлении InventTrans в блоке try случится исключительная ситуация конфликта обновления записи, то код пишуший
информацию в InventSumDelta успеет выполниться и транзакция не откатывается (такова особенность этого исключения).
Далее после коммита транзакции изменения сбрасываются в InventSum, а Inventtrans не изменился.
Получаем расхождения.

Можно конечно возразить что пример несколько искусственный, но мы на практике с этим столкнулись. Получается, что отсутствие отката транзакции при Exception::UpdateConflict - достаточно опасная вещь. По крайней, мере лучше всегда этот тип исключения обрабатывать.

А еще лучше поправить метод InventTrans.update(), переместив обновление InventSumDelta после вызова super(). Это будет дополнительной защитой от небрежно написанного кода. Правда все последствия такого переноса пока не изучил.

Как думаете, рискуем чем нибудь ?

Последний раз редактировалось Logger; 19.10.2011 в 19:50.
За это сообщение автора поблагодарили: Pustik (3).
Старый 20.10.2011, 15:26   #2  
CDR is offline
CDR
MCTS
MCBMSS
 
236 / 175 (6) ++++++
Регистрация: 27.11.2003
Цитата:
Сообщение от Logger Посмотреть сообщение
Это будет дополнительной защитой от небрежно написанного кода.
Улыбнула "защита от небрежно написанного кода" .

Если есть две обновляемые записи, то тут по-моему, все равно, какую из них первой обновлять, при наличии "небрежно написанного кода".

Если описанная ситуация действительно для Вас актуальна, то можно порекомендовать вставить блок try-catch внутрь методов insert(), update(), delete(). Тогда при исключениях, которые не откатывают транзакцию автоматически, в catch можно будет выбросить исключение Exception::Error и откатить транзакцию принудительно.
__________________
Dynamics AX Experience
За это сообщение автора поблагодарили: Logger (3).
Старый 20.10.2011, 15:34   #3  
lvan is offline
lvan
Участник
Аватар для lvan
Лучший по профессии 2014
 
858 / 82 (4) ++++
Регистрация: 15.04.2011
Записей в блоге: 1
Цитата:
Сообщение от CDR Посмотреть сообщение
можно порекомендовать вставить блок try-catch внутрь методов insert(), update(), delete().
а ничо, что try..catch внутри транзакции не работает?
автору просто надо завернуть обновление inventSum и super() в транзакцию
вообще странно, что её там нет
Старый 20.10.2011, 15:45   #4  
lvan is offline
lvan
Участник
Аватар для lvan
Лучший по профессии 2014
 
858 / 82 (4) ++++
Регистрация: 15.04.2011
Записей в блоге: 1
хотя я про updateConflict не подумал, но кстати обычно он всегда обрабатывается
в Tutorial_RunbaseBatch есть правильная конструкция

X++:
/// <summary>
///    Contains the code that does the actual job of the class.
/// </summary>
public void run()
{
    #OCCRetryCount
    if (! this.validate())
        throw error("");

    try
    {
        ttsbegin;

        // this.Update();

        ttscommit;
    }
    catch (Exception::Deadlock)
    {
        retry;
    }
    catch (Exception::UpdateConflict)
    {
        if (appl.ttsLevel() == 0)
        {
            if (xSession::currentRetryCount() >= #RetryNum)
            {
                throw Exception::UpdateConflictNotRecovered;
            }
            else
            {
                retry;
            }
        }
        else
        {
            throw Exception::UpdateConflict;
        }
    }

}

Последний раз редактировалось lvan; 20.10.2011 в 15:54.
За это сообщение автора поблагодарили: Logger (0).
Старый 20.10.2011, 16:46   #5  
Ivanhoe is offline
Ivanhoe
Участник
Аватар для Ivanhoe
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
 
4,143 / 2156 (80) +++++++++
Регистрация: 29.09.2005
Адрес: Санкт-Петербург
Logger, вы про какую версию системы пишите? Ошибка происходит на стандартной функциональности или есть кастомизации?
__________________
Ivanhoe as is..
Старый 20.10.2011, 18:28   #6  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
2009-я Аксапта.
Кастомизаций конечно дофига, но в части разноски данных в InventSum мы ничего не меняли.
Ближе к вечеру выложу демонстрационный джоб, чтобы на любой инсталляции можно было глюк проверить.
Старый 20.10.2011, 20:45   #7  
Pustik is offline
Pustik
Участник
 
807 / 372 (14) ++++++
Регистрация: 04.06.2004
Цитата:
Сообщение от Logger Посмотреть сообщение
Можно конечно возразить что пример несколько искусственный, но мы на практике с этим столкнулись.
Мы тоже с этим столкнулись. Это происходит периодически, но не постоянно.Вот в чем у нас вопрос. Меня периодически просят разобраться, почему по оборотам одно количество, по кнопке "в наличии" другое. Пересчет InventSum по номенклатуре стандартным образом возвращает все на свое место. В большинстве своем - все ок.
Logger, спасибо за гипотезу наших парадоксов).
__________________
-Ты в гномиков веришь?
-Нет.
-А они в тебя верят, смотри, не подведи их.
Старый 06.11.2011, 19:41   #8  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Вот пример джоба :
X++:
// GRD_R4453_InventSumInventTrans_pkoz, Разъехались остатки в InventSum и проводки в InventTrans., pkoz, 04.11.2011
static void Job791_15(Args _args)
{
    #define.recIdTrans(5637151827)
    InventTrans     InventTrans;
    InventSum       InventSum;
    ;
    try
    {
        ttsBegin;
        try
        {
            InventTrans = InventTrans::findRecId(#recIdTrans, true);
            InventTrans.Qty += 1.0;
            InventTrans.Update();
            breakpoint;
        }
        catch
        {
            info("поймали внутрений catch");
        }
        ttsCommit;
    }
    catch
    {
        info("поймали внешний catch");
    }

    info(strFMT("InventTrans.RowCount() = %1, InventTrans.Qty = %2, InventSum : %3 ",
        InventTrans.RowCount(),
        InventTrans.Qty,
        con2str(Global::buf2Con(InventSum::find(InventTrans.ItemId, InventTrans.inventDimId)), ";  ")
        ));

}
Использовать так :
1. Проставляем в джобе recID существующей проводки InventTrans
2. Открываем 2 клиента аксапты.
3. Запускаем джоб на 1-м клиенте, ждем когда он выпадет в отладчик на точке останова.
4. Запускаем тот же джоб на 2-м клиенте. Он повиснет.
5. Жмем F5 в отладчике на 1-м клиенте.

В итоге после выполнения джоба на обоих клиентах, получаем что InventTrans.Qty увеличилось на 1, а соответствующая колонка в InventSum увеличилась на 2.

Причина такого поведения, в том что мы просто поставили обработку catch в которую попадают исключения
Exception::UpdateConflict
и
Exception:uplicateKeyException
которые в свою очередь не откатывают транзакцию.

Поэтому чтобы избегать в модификациях подобных ошибок, нельзя ставить обработку catch без указания вида исключения.
Ну или по крайней мере перед ней всегда делать обработку вышеупомянутых типов исключений.

Для удобства, я завел макрос Catch_DangerousException, который можно везде вставлять в блок обработки.
Текст макроса
X++:
catch (Exception::UpdateConflict)
{
    if (appl.ttsLevel() == 0)
    {
        if (xSession::currentRetryCount() >= 5)
        {
            throw Exception::UpdateConflictNotRecovered;
        }
        else
        {
            retry;
        }
    }
    else
    {
        throw Exception::UpdateConflict;
    }
}

catch (Exception::DuplicateKeyException)
{
    // info(strfmt("@SYS123267", ));

    if (appl.ttsLevel() != 0)
    {
        throw Exception::DuplicateKeyExceptionNotRecovered;
    }
}
Упомянутый в заголовке темы пример следует переписать так :

X++:
            ...
            ttsBegin;
            try
            {
                // здесь расположен код обновляющий InventTrans - например комплектация или резервирование
            }
            catch(Exception::Error)
            {
                // обработка                    
            }

            #catch_DangerousException

            catch 
            {   // сюда попадает обработка конфликта обновления записи
                // ничего не делаем
            }
            ttsCommit;
            ...
За это сообщение автора поблагодарили: Vadik (25), Ruff (10), kashperuk (20).
Старый 06.11.2011, 19:44   #9  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Если вышеупомянутый джоб переписать с использованием макроса по обработке исключений, то упомянутых расхождений InventSum и InventTrans не возникнет.
X++:
// GRD_R4453_InventSumInventTrans_pkoz, Разъехались остатки в InventSum и проводки в InventTrans., pkoz, 04.11.2011
static void Job791_16(Args _args)
{
    #define.recIdTrans(5637151827)
    InventTrans     InventTrans;
    InventSum       InventSum;
    ;
    try
    {
        ttsBegin;
        try
        {
            InventTrans = InventTrans::findRecId(#recIdTrans, true);
            InventTrans.Qty += 1.0;
            InventTrans.Update();
            breakpoint;
        }

        #catch_DangerousException

        catch
        {
            info("поймали внутрений catch");
        }
        ttsCommit;
    }

    #catch_DangerousException

    catch
    {
        info("поймали внешний catch");
    }

    info(strFMT("InventTrans.RowCount() = %1, InventTrans.Qty = %2, InventSum : %3 ",
        InventTrans.RowCount(),
        InventTrans.Qty,
        con2str(Global::buf2Con(InventSum::find(InventTrans.ItemId, InventTrans.inventDimId)), ";  ")
        ));

}
Старый 06.11.2011, 19:52   #10  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Любопытно, что в стандартной документации в разделе Exception Handling про такую особенность исключений ничего не сказано.

Более того написано буквально следующее :

Цитата:
One strategy is to have the last catch statement leave the exception type unspecified. This means it handles all exceptions that are not handled by a previous catch. This strategy is appropriate for the outermost try - catch blocks.

X++:
try { /* Code here. */ } 

catch (Exception::Numeric) { info("Caught a Numeric exception."); } 

catch { info("Caught an exception."); }
Т.е. если четко следовать документации, то можно нагородить ошибок.
Хотя формально все правильно, просто в некоторых редких случаях разъезжается InventSum и InventTrans.

Также аналогичная проблема может проявиться в любом месте при конфликте обновления записи и выглядеть это будет так словно транзакция закоммитилась посередине.

Последний раз редактировалось Logger; 06.11.2011 в 20:00.
Старый 06.11.2011, 19:58   #11  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Также решил воспользоваться советом CDR и модифицировал методы :
InventTrans.Update()
InventTrans.Insert()
InventTrans.Delete()

поставив тело каждого метода в блок try и написав такой блок catch в каждом методе :

X++:
    catch (Exception::UpdateConflict)
    {
        // warning(this.GRD_getWarningStr4UpdateConflict());
        throw Exception::UpdateConflictNotRecovered;
    }

    catch (Exception::DuplicateKeyException)
    {
        // warning(this.GRD_getWarningStr4DuplicateKeyException());
        throw Exception::DuplicateKeyExceptionNotRecovered;
    }
При такой модификации ошибка расхождения InventTrans и InventSum не возникнет, даже используя джоб
Развалились InventSum - InventTrans

Так пожалуй будет надежней.
Нельзя полагаться на то, что никто из разработчиков не ошибется при обработке исключений, для кода содержащего InventTrans.Update()
За это сообщение автора поблагодарили: Pustik (3), someOne (13).
Старый 06.11.2011, 21:59   #12  
Ivanhoe is offline
Ivanhoe
Участник
Аватар для Ivanhoe
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
 
4,143 / 2156 (80) +++++++++
Регистрация: 29.09.2005
Адрес: Санкт-Петербург
Так все-таки, указанная проблема есть в стандартной функциональности? Т.е. вы знаете места, где "некорректно" обрабатываются исключения?
__________________
Ivanhoe as is..
Старый 06.11.2011, 22:05   #13  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Таких примеров не знаю в стандарте.
Может есть, а может и нет.

Суть проблемы в том, что если следовать стандарту, описанному в документации, то легко написать код, который приведет к проблемам.

Например, если вы посмотрите на приведенный мной джоб, то, наверно, не заподозрите подвоха. А он есть
Старый 08.11.2011, 16:13   #14  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Поискал по ветке классов в AOT по такому выражению
Цитата:
catch\s*[^( Exception)]
(задумка была такая - отфильтровать в поиске все строки где написано просто
X++:
catch
и пропустить те, где после catch стоит упоминание Exception
Данное регулярное выражении при поиске работает не совсем правильно, но большинство ненужных случаев позволяет отбросить.

В общем, вывалилась куча примеров на sys слое когда стоит просто
X++:
try
{
...
}
Catch
{
...
}
Конечно не все они приведут к описанному здесь глюку, так как не везде в блоку try стоит обновление данных, кое где стоит вызов com объектов и.т.п.

Но все равно очень много мест где есть обновление данных и потенциально будут описанные в этой ветке проблемы
Старый 25.11.2016, 10:57   #15  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
На эту проблему обратили внимание в Майкрософте
mfp: X++, the catch
Kashperuk Ivan: Tutorial Link: Handling exceptions the right way in X++

Оригинальные ссылки
https://blogs.msdn.microsoft.com/mfp...4/x-the-catch/
http://kashperuk.blogspot.ru/2016/11...ons-right.html
За это сообщение автора поблагодарили: Link (1).
Старый 25.11.2016, 18:36   #16  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Говорят в 4-ке баг с транзакциями не воспроизводился.
Кто-нибудь может проверить, это так или нет ?
Старый 26.11.2016, 02:22   #17  
Link is offline
Link
Британский учённый
Аватар для Link
Соотечественники
 
568 / 523 (19) +++++++
Регистрация: 25.11.2005
Адрес: UK
Записей в блоге: 9
Цитата:
Сообщение от Logger Посмотреть сообщение
Пять лет, Карл! И каков вывод? "Вы просто держите его неправильно." (с)
__________________
Людям физического труда для восстановления своих сил нужен 7-8 часовой ночной сон. Людям умственного труда нужно спать часов 9-10. Ну а программистов будить нельзя вообще.
Старый 26.11.2016, 09:47   #18  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,932 / 3227 (115) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Цитата:
Сообщение от Link Посмотреть сообщение
Пять лет, Карл!
Чёртова бюрократия !
Если бы вовремя донести до нужных лиц эту инфу
Старый 02.02.2017, 03:43   #19  
kashperuk is offline
kashperuk
Участник
Аватар для kashperuk
MCBMSS
Соотечественники
Сотрудники Microsoft Dynamics
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии 2011
Лучший по профессии 2009
 
4,361 / 2084 (78) +++++++++
Регистрация: 30.05.2004
Адрес: Atlanta, GA, USA
Цитата:
Сообщение от Logger Посмотреть сообщение
Чёртова бюрократия !
Если бы вовремя донести до нужных лиц эту инфу
Да, это оказались довольно опасные грабли, и поведение при наступлении на них может быть весьма и весьма непредсказуемым и, что еще хуже, трудновоспроизводимым.
Старый 24.08.2017, 16:12   #20  
dech is offline
dech
Участник
Аватар для dech
Самостоятельные клиенты AX
 
647 / 350 (13) ++++++
Регистрация: 25.06.2009
Адрес: Омск
Записей в блоге: 3
Цитата:
Сообщение от Logger Посмотреть сообщение
Обнаружил интересную особенность :
Код обновляющий, InventSum (если быть точнее - пишущий данные об изменения в InventsumDelta - но в данном случае это непринципиально)
для методов
InventTrans.insert()
InventTrans.delete()

расположен после Super()
а для
InventTrans.update()
до Super()

что приводит в ряде случаев к тому, что расходятся данные InventSum - InventTrans.
Недавно натолкнулись на расхождение InventSum/InventTrans в 4-ке.
Подскажите, где вы нашли "безусловный" catch?
__________________
// no comments
Теги
exception, inventsum, inventtrans, occ, try/catch, баг, исключения

 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
Dynamics AX Sustained Engineering: Fields modifiedDateTime and modifiedBy on Table InventSum Blog bot DAX Blogs 0 30.12.2010 00:12
InventSum Alexanderrrr DAX: Функционал 18 12.01.2010 07:43
Ошибка при разноске складских движений Starling DAX: Администрирование 9 12.10.2007 14:21
Связь таблиц InventTrans и PurchLine Pustik DAX: Программирование 2 25.11.2004 12:23
InventSum vs. InventTrans Wamr DAX: Программирование 4 18.09.2002 15:07

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 05:32.
Powered by vBulletin® v3.8.5. Перевод: zCarot
Контактная информация, Реклама.