12.11.2015, 13:13 | #1 |
Участник
|
Вызов AIF сервиса в отдельном соединении
Всем добрый день,
есть одна проблема, которая заключается в следующем: при вызове AIF custom сервиса происходит импорт данных, во время которого пишутся логи. НО: если хоть где-то в коде выбросится ошибка или вызов ttsabort, то соответственно ни лога, ни данных... Если бы это была одна какая-то таблица, я не задумываясь использовал бы UserConnection, но здесь сервис вызывает класс, который выполняет всю логику, поэтому классу я передать соединение не могу, к тому же класс написан внешниками и зашифрован при помощи макросов (можно конечно парсер написать, но по лицензионному соглашению запрещено). Есть предложение от коллеги использовать anonymous pipes, примерно вот так: X++: pipeServer = new System.IO.Pipes.AnonymousPipeServerStream(System.IO.Pipes.PipeDirection::Out , System.IO.HandleInheritability::Inheritable); thread = new Thread(); thread.setInputParm([clr2XppStr(pipeServer.GetClientHandleAsString())]); thread.removeOnComplete(true); thread.run(classNum(ApIpcExample), staticMethodStr(ApIpcExample, messageReader)); pipeServer.DisposeLocalCopyOfClientHandle(); Может кто сталкивался с подобной проблемой? |
|
13.11.2015, 00:46 | #2 |
Боец
|
это известная проблема X++, когда выполнение кода просто прерывается без возможности отлова исключений. Чаще всего происходит при вызовах внешнего кода.
Знаю два варианта решения: #1: На C# пишем простую функцию, которая вызывает внешний сервис (или, в общем случае, любой, потенциально нестабильный внешний код). Функция будет вызывать внешнюю 1:1 как бы вы это делали из X++. Но на C# вы делаете полноценный try/catch, который гарантированно вернет управление обратно в АХ. Функцию подключаем как .net reference в АОТ и юзаем. Это решение простое и надежное, правильное. Но получаем все нюансы использования .net references, что требует бережного обращения, в зависимости от версии АХ #2 реализовать класс MultithreadHandler, который: 1) запустит необходимое кол-во потоков в асинхронном режиме 2) периодически, опрашивая их статус, дождется их окончания 3) в зависимости от того, вернул поток результат, либо скоропостижно скончался (это можно отследить в классе Thread), залогирует соотвествующий результат. Тут правда, в отличие от первого способа, вы никогда не узнаете, от чего поток умер (или какое исключение вернул внеший вызов) #3 Гм, или я не понял задачу , Последний раз редактировалось DSPIC; 13.11.2015 в 00:49. |
|
13.11.2015, 10:42 | #3 |
Участник
|
Спасибо за совет, идею понял. Если сформулировать точнее, то проблема заключается в следующем:
на вход импорт-класса я последовательно скармливаю XML-и, которые должны в определенной последовательности обрабатываться. Скажем, у меня есть 5 XML-ей: подал первую - скушал, подал вторую - скушал, на третьей подавился, выбросил ошибку, оборвал транзакцию, в итоге вся информация про 1-ю и 2-ю вместе с логами пропали, поскольку вызов сервиса происходит в контексте одной транзакции. Значит теоретически я могу, используя метод #2, для каждого прохода открыть новый поток и в нем новое соединение? Или все открываемые потоки будут обрабатываться как одна транзакция? |
|
16.11.2015, 17:26 | #4 |
Британский учённый
|
Если я правильно понял проблему, то я бы смотрел в сторону модификации AIF - так что бы каждый вызов внешнего класса выполнялся отдельной транзакцией, но это мне видится далеко не тривиальной задачей. К тому же, лог в этом случае, вероятно будет потерян. Другой вариант - изменить принцип логирования\обработки исключений во внешнем классе. Можно, например в случае исключения отсылать лог Аксапте через другой простенький AIF сервис.
__________________
Людям физического труда для восстановления своих сил нужен 7-8 часовой ночной сон. Людям умственного труда нужно спать часов 9-10. Ну а программистов будить нельзя вообще. Последний раз редактировалось Link; 16.11.2015 в 18:41. |
|
16.11.2015, 23:50 | #5 |
Модератор
|
Нельзя несколько вызовов AIF завернуть в некую внешнюю транзакцию. Но, как вариант, можно упаковать несколько документов в одно сообщение - смотрите в сторону Processing batched messages in AIF [AX 2012]
__________________
-ТСЯ или -ТЬСЯ ? |
|
17.11.2015, 05:26 | #6 |
Участник
|
Цитата:
можно упаковать несколько документов в одно сообщение
Цитата:
НО: если хоть где-то в коде выбросится ошибка или вызов ttsabort, то соответственно ни лога, ни данных...
Посмотрите как AIF вызывает ваш метод в AifWcfProcessor, сделайте трейс. И как транзакция открывается/закрывается - AifRequestProcessor.processWcfRequest - AIF делает ttsbegin в конце AifRequestProcessor.processWcfResponse - AIF делает ttscommit в начале Имейте ввиду что AIF логика может быть разная для Basic и Enhanced портов.
__________________
AxAssist 2012 - Productivity Tool for Dynamics AX 2012/2009/4.0/3.0 |
|
|
За это сообщение автора поблагодарили: Logger (1), sgt.Pepper (1), A_BAS (2). |
19.11.2015, 15:24 | #7 |
Участник
|
В методе который выполняется AIF запускайте thread.
|
|
26.04.2016, 20:32 | #8 |
Участник
|
Если кому интересно, нашел решение проблемы (спасибо подсказке Alex_KD).
При вызове сервиса AIF создает "глобальную" транзакцию, соттветственно если что-то случается в самом сервисе, все try-catch блоки самого сервиса просто игнорируются и идут выше в обработчики AIF, где собственно было начало транзакции. Все попытки использовать catch Error, ClrError, Internal и просто catch не увенчались успехом. Что сделано: в точке входа сервиса сделал проверку на уровень tts и сразу сделал ttscommit. X++: ttsInitialLevel = appl.ttsLevel(); if(ttsInitialLevel > 0) { ttscommit; ttsbegin; } ... После отработки метода делаю ttscommit своей транзакции, и перед возвратом создаю новую. X++: if(ttsInitialLevel >0 && appl.ttsLevel() == 0) ttsbegin; return something Последний раз редактировалось sgt.Pepper; 26.04.2016 в 20:43. |
|
|
За это сообщение автора поблагодарили: gl00mie (2). |
27.04.2016, 13:26 | #9 |
Участник
|
|
|
|
За это сообщение автора поблагодарили: gl00mie (2). |
27.04.2016, 15:52 | #10 |
Участник
|
Ну я собственно с единицей нигде не сравниваю, проверяю только, что транзакция открыта (ttsInitialLevel > 0)
А в чем вы видите ошибку? |
|
27.04.2016, 15:55 | #11 |
Участник
|
ttsBegin у вас один, когда все транзакции возвращаетесь
|
|
27.04.2016, 16:44 | #12 |
Участник
|
Вы правы, надо будет что-то вроде такого написать
X++: for (i==1; I<= ttsInitialLevel;i++) ttsbegin; |
|
27.04.2016, 17:35 | #13 |
Участник
|
Цитата:
Сегодня Вы не видите несколько транзакций, сделаете как вам видится, уволитесь, пройдет 5ть лет и кто-нибудь влезет в этот механизм, потом пол компании будет затылок чесать: "а что же произошло"? P.S. Может вместо ttsAbort написать ttsCommit? |
|
27.04.2016, 17:44 | #14 |
Участник
|
Вообще-то я нигде не писал ttsAbort, а при перехвате исключения ttsLevel итак сбрасывается на 0.
А через 5 лет наверно и X++ уже не будет. |
|
29.04.2016, 20:06 | #15 |
Участник
|
Цитата:
Поверьте мне, Ах стоит в компаниях годами, я сам сейчас на проекте, где Ах 3.0 уже 16 лет исправно служит добрую службу. |
|
19.05.2016, 12:44 | #16 |
Британский учённый
|
Вот еще по теме: Preserve data when AIF service call fails
Dynamics AX AIF is great for all kind of interfaces and integration with 3rd party applications. By default AIF has an built-in transaction mechanism that prevents your system to become inconsistent if something goes wrong during processing. Here is an example of a simple method which writes message data and the current transaction level into a table X++: [SysEntryPointAttribute] public void call(Description _message) { ERPServiceTable serviceTable; serviceTable.Message = _message; serviceTable.Info = strFmt("%1", appl.ttsLevel()); serviceTable.insert(); } PHP код:
AIF service call But what happens when something goes wrong during processing the service call (e.g. posting a journal fails). Here is as simple method which can be used to simulate such a situation: X++: [SysEntryPointAttribute] public void callWithError(Description _message,boolean _error) { this.call(_message); if(_error) { throw error("Exception!"); } } X++: context.MessageId = Guid.NewGuid().ToString(); client.callWithError(context, "Service Call without error", false); X++: try { context.MessageId = Guid.NewGuid().ToString(); client.callWithError(context, "Service Call 2 with error", true); } catch { Console.WriteLine("The service failed as expected!"); } Service call with error not saved However, if you want to keep the initial data for a post mortem analysis, this behavior is a problem. One way to work around this rollback is to reduce the transaction level back to 0 by calling an additional ttscommit. This will ensure no rollback will delete the transmitted data. Finally, raise the ttslevel back to 1 for further processing. X++: [SysEntryPointAttribute] public void callWithError(Description _message,boolean _error = false) { this.call(_message); ttsCommit; ttsBegin; if(_error) { throw error("Exception!"); } } Service call with error preserverd BTW. A more relaxed way to address this issue is to separate interfaces from business logic. For example provide two methods, one to transmit the data and another to process the data after it was successfully transmitted. If a second method is not an option (for what reason ever) write a batch class which processes the data. However, if you require immediate processing and a second call is not feasible this workaround may help.
__________________
Людям физического труда для восстановления своих сил нужен 7-8 часовой ночной сон. Людям умственного труда нужно спать часов 9-10. Ну а программистов будить нельзя вообще. |
|
|
За это сообщение автора поблагодарили: Logger (1). |