19.10.2011, 19:48 | #1 |
Участник
|
Развалились InventSum - InventTrans
Обнаружил интересную особенность :
Код обновляющий, InventSum (если быть точнее - пишущий данные об изменения в InventsumDelta - но в данном случае это непринципиально) для методов InventTrans.insert() InventTrans.delete() расположен после Super() а для InventTrans.update() до Super() что приводит в ряде случаев к тому, что расходятся данные InventSum - InventTrans. Например : в коде есть вызов такого типа : X++: ... ttsBegin; try { // здесь расположен код обновляющий InventTrans - например комплектация или резервирование } catch(Exception::Error) { // обработка } catch { // сюда попадает обработка конфликта обновления записи // ничего не делаем } ttsCommit; ... информацию в InventSumDelta успеет выполниться и транзакция не откатывается (такова особенность этого исключения). Далее после коммита транзакции изменения сбрасываются в InventSum, а Inventtrans не изменился. Получаем расхождения. Можно конечно возразить что пример несколько искусственный, но мы на практике с этим столкнулись. Получается, что отсутствие отката транзакции при Exception::UpdateConflict - достаточно опасная вещь. По крайней, мере лучше всегда этот тип исключения обрабатывать. А еще лучше поправить метод InventTrans.update(), переместив обновление InventSumDelta после вызова super(). Это будет дополнительной защитой от небрежно написанного кода. Правда все последствия такого переноса пока не изучил. Как думаете, рискуем чем нибудь ? Последний раз редактировалось Logger; 19.10.2011 в 19:50. |
|
|
За это сообщение автора поблагодарили: Pustik (3). |
20.10.2011, 15:26 | #2 |
MCTS
|
Улыбнула "защита от небрежно написанного кода" .
Если есть две обновляемые записи, то тут по-моему, все равно, какую из них первой обновлять, при наличии "небрежно написанного кода". Если описанная ситуация действительно для Вас актуальна, то можно порекомендовать вставить блок try-catch внутрь методов insert(), update(), delete(). Тогда при исключениях, которые не откатывают транзакцию автоматически, в catch можно будет выбросить исключение Exception::Error и откатить транзакцию принудительно.
__________________
Dynamics AX Experience |
|
|
За это сообщение автора поблагодарили: Logger (3). |
20.10.2011, 15:34 | #3 |
Участник
|
|
|
20.10.2011, 15:45 | #4 |
Участник
|
хотя я про 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 |
Участник
|
Logger, вы про какую версию системы пишите? Ошибка происходит на стандартной функциональности или есть кастомизации?
__________________
Ivanhoe as is.. |
|
20.10.2011, 18:28 | #6 |
Участник
|
2009-я Аксапта.
Кастомизаций конечно дофига, но в части разноски данных в InventSum мы ничего не меняли. Ближе к вечеру выложу демонстрационный джоб, чтобы на любой инсталляции можно было глюк проверить. |
|
20.10.2011, 20:45 | #7 |
Участник
|
Цитата:
Logger, спасибо за гипотезу наших парадоксов).
__________________
-Ты в гномиков веришь? -Нет. -А они в тебя верят, смотри, не подведи их. |
|
06.11.2011, 19:41 | #8 |
Участник
|
Вот пример джоба :
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 |
Участник
|
Если вышеупомянутый джоб переписать с использованием макроса по обработке исключений, то упомянутых расхождений 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 |
Участник
|
Любопытно, что в стандартной документации в разделе 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 |
Участник
|
Также решил воспользоваться советом 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; } Развалились InventSum - InventTrans Так пожалуй будет надежней. Нельзя полагаться на то, что никто из разработчиков не ошибется при обработке исключений, для кода содержащего InventTrans.Update() |
|
|
За это сообщение автора поблагодарили: Pustik (3), someOne (13). |
06.11.2011, 21:59 | #12 |
Участник
|
Так все-таки, указанная проблема есть в стандартной функциональности? Т.е. вы знаете места, где "некорректно" обрабатываются исключения?
__________________
Ivanhoe as is.. |
|
06.11.2011, 22:05 | #13 |
Участник
|
Таких примеров не знаю в стандарте.
Может есть, а может и нет. Суть проблемы в том, что если следовать стандарту, описанному в документации, то легко написать код, который приведет к проблемам. Например, если вы посмотрите на приведенный мной джоб, то, наверно, не заподозрите подвоха. А он есть |
|
08.11.2011, 16:13 | #14 |
Участник
|
Поискал по ветке классов в AOT по такому выражению
Цитата:
catch\s*[^( Exception)]
X++: catch Данное регулярное выражении при поиске работает не совсем правильно, но большинство ненужных случаев позволяет отбросить. В общем, вывалилась куча примеров на sys слое когда стоит просто X++: try { ... } Catch { ... } Но все равно очень много мест где есть обновление данных и потенциально будут описанные в этой ветке проблемы |
|
25.11.2016, 10:57 | #15 |
Участник
|
На эту проблему обратили внимание в Майкрософте
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 |
Участник
|
Говорят в 4-ке баг с транзакциями не воспроизводился.
Кто-нибудь может проверить, это так или нет ? |
|
26.11.2016, 02:22 | #17 |
Британский учённый
|
Цитата:
Сообщение от Logger
На эту проблему обратили внимание в Майкрософте
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
__________________
Людям физического труда для восстановления своих сил нужен 7-8 часовой ночной сон. Людям умственного труда нужно спать часов 9-10. Ну а программистов будить нельзя вообще. |
|
26.11.2016, 09:47 | #18 |
Участник
|
|
|
02.02.2017, 03:43 | #19 |
Участник
|
Да, это оказались довольно опасные грабли, и поведение при наступлении на них может быть весьма и весьма непредсказуемым и, что еще хуже, трудновоспроизводимым.
|
|
24.08.2017, 16:12 | #20 |
Участник
|
Цитата:
Сообщение от Logger
Обнаружил интересную особенность :
Код обновляющий, InventSum (если быть точнее - пишущий данные об изменения в InventsumDelta - но в данном случае это непринципиально) для методов InventTrans.insert() InventTrans.delete() расположен после Super() а для InventTrans.update() до Super() что приводит в ряде случаев к тому, что расходятся данные InventSum - InventTrans. Подскажите, где вы нашли "безусловный" catch?
__________________
// no comments |
|
Теги |
exception, inventsum, inventtrans, occ, try/catch, баг, исключения |
|
|