Options

ERROR() stops NAS

shelbydzshelbydz Member Posts: 14
edited 2014-02-24 in NAV Three Tier
Hi,

tl;dr;

Is there a way to either intercept ERROR and keep it from crashing the NAS, or can I configure the NAS to not even STOP when it sees any exception?

The details:

We have a product which uses C/AL to read from tables and perform CRUD operations. With the 2013 R2 much of our product broke. We tracked it down to the ERROR function. In our code units, we using ERROR to trigger exceptions and catch them in our .NET server, and pass them to another client.

The problem in R2 is that ERROR stops the NAS and causes our client to disconnect. When ERROR gets called, I see 3 entries is the event log:

Warning: Exception.NavNCLDialogException ID 218
Error: NAV Application Server session has failed and will be restarted. Reason Type NavNCLDialogException ID 221
Information: NAV Application is scheduled to start with the following configuration. ID 219

I was able to work around much of this by triggering events from the code units into our .NET. But there's still an issue when we hit existing code. For instance, if I try to insert a record that already exists, that table's INSERT throws ERROR, killing the server. Yes, the server restarts, but only so many times (limited by the config) and not fast enough to process thousands of rows we may be dealing with.

So. Is there a way to either intercept ERROR and keep it from crashing the NAS, or can I configure the NAS to not even STOP when it sees an exception?

I had submitted a ticket with ms regarding this issue, but they said it was working as expected and couldn't help.

Thanks,

Comments

  • Options
    einsTeIn.NETeinsTeIn.NET Member Posts: 1,050
    First of all, if in your process you try to insert records that already exist then something in your process might be wrong. I know that doesn't help you in your current situation, but it might be worth to review the whole process and improve it.

    I don't know if that still works in 2013, but in older versions you could wrap your critical code in an extra codeunit and try to invoke it by IF Codeunit.RUN THEN.
    "Money is likewise the greatest chance and the greatest scourge of mankind."
  • Options
    BardurKnudsenBardurKnudsen Member, Microsoft Employee Posts: 137
    As Einstein.net correctly suggests, you should try to isolate your code in a codeunit and use the status := codeunit.run();
    Another way would be to run your code in a separate session using the STARTSESSION functionality (one of the features from NAV2013 that I love most). This is basically how the Job Queue functionality works; it separates everything out in separate sessions to prevent the main session to die.
    The main issue remains, of course: If the NAS process itself hits an error, it dies.
    Bardur Knudsen
    Microsoft - Dynamics NAV
  • Options
    shelbydzshelbydz Member Posts: 14
    Is that how error handling is done in the C/AL code? I was thinking about this on my way to work. It's possible that I just need to handle the ERROR coming up from the tables. In .net, i would wrap the call in a try/catch. But I can't find anything like that for C/AL.

    I'll try pushing the call out to a separate session, then inspecting its output.

    Thanks
  • Options
    einsTeIn.NETeinsTeIn.NET Member Posts: 1,050
    Yeah, in pure C/AL that could be called the equivalent of try/catch. There is no real implementation of try/catch.
    "Money is likewise the greatest chance and the greatest scourge of mankind."
  • Options
    shelbydzshelbydz Member Posts: 14
    I might need some help setting up the session stuff.

    what i have right now is one code unit that does all of the CRUD. Say it has 4 methods inside. Like (forgive me, this is mostly pseudo code):

    CrudUnit.Create(record)
    CrudUnit.Retrieve(record)
    CrudUnit.Update(record)
    CrudUnit.Delete(record)

    I'm calling this from a higher level method, which gets instructions from .NET. This is done via case statement.

    CASE command of
    'create':
    CrudUnit.Create(record);
    end

    and so on

    i assume that at the top class, I need to spin the session and inspect the output, but what's that syntax when I'm calling a method on a codeunit, not just a codeunit?

    OK := STARTSESSION(1, CODEUNIT::"CrudUnit", record);

    how do i specify which method to call?? or do i have to break out the methods to smaller code units?

    thanks,
  • Options
    einsTeIn.NETeinsTeIn.NET Member Posts: 1,050
    You can't. The IF Codeunit.RUN THEN workaround just works for the OnRun trigger of that codeunit. That means if you want to call certain methods you need to create one codeunit for each method or you implement some decision in the OnRun trigger to decide what method should be called. Maybe based on the record you pass into the codeunit?
    "Money is likewise the greatest chance and the greatest scourge of mankind."
  • Options
    shelbydzshelbydz Member Posts: 14
    that's what i was afraid of. although i think i can work with that.

    thanks for all of the help.
  • Options
    shelbydzshelbydz Member Posts: 14
    I am getting pretty close. But I'm still losing sync or getting disconnected or something. It seems to me that we're not handling any errors in our record creation and had previously relied on bubbling exceptions up through to .NET.

    Previously, my InsertRecord method looked like this:

    RecRef.OPEN(tableName); //this is a RecordRef
    RecRef.INIT;
    OtherCodeUnit.FillValuesInRecord(RecRef);

    //this line failed, with the 3 items in the event log killing the server:
    RecRef.INSERT(TRUE);

    I tried changing this last line to:
    IF NOT RecRef.INSERT(TRUE) THEN
    //some .NET code to handle errors through my stack. this doesn't throw.

    This still seems to break. In the event log, I get only one entry, the 'warning' from before:
    Warning Exceptions.NavNCLDialogException. Event Id 218.

    I would actually be okay with this BUT the server appears to stop responding. I'm doing potentially hundreds of rows in quick succession and both the CAL debugger and my client code disconnect from the NAS after one execution.

    I wasn't able to figure out the STARTSESSION using a recordref. Is there another solution or am I doing something wrong?

    Again, when it's all said and done, I'm trying to HANDLE errors. My test case is intentionally creating bogus records so I can iron out this code.

    Thanks!!
  • Options
    einsTeIn.NETeinsTeIn.NET Member Posts: 1,050
    I think there is some code for user interaction in the OnInsert trigger of the table you want to insert your record in. Anything that creates some GUI isn't allowed in NAS, e.g. dialog, forms, request forms, etc. Messages and errors will be displayed in the Windows Event Log, so they're no problem. But anything else should be covered by a GUIALLOWED statement to make sure that it doesn't cause any issue in NAS.
    "Money is likewise the greatest chance and the greatest scourge of mankind."
  • Options
    shelbydzshelbydz Member Posts: 14
    that makes sense. we have some custom code wrapped into the triggers. Can I:

    Trap those with STARTSESSION and not kill the server

    or

    do I need to go into that code and handle the errors at that level??

    Thanks
  • Options
    einsTeIn.NETeinsTeIn.NET Member Posts: 1,050
    I'm afraid you need to handle it beforehand in the code. I mean if you use STARTSESSION then your "NAS main thread" will stay alive but the solution won't work as all the "child threads" will run into that GUI issue.
    "Money is likewise the greatest chance and the greatest scourge of mankind."
  • Options
    shelbydzshelbydz Member Posts: 14
    As best I can tell, none of our code is throwing the offending ERROR in the triggers. When trying to create a new Customer, we call
    RecordRef.INSERT(TRUE);

    the line that kills my stack is
    Table 5054 Contact Business Relation
    The only code of mine that's getting called is the initial RecordRef.INSERT(TRUE);

    How do I handle the error that far down and keep it from killing the whole stack? I tried wrapping it like
    IF RecordRef.INSERT(TRUE) THEN
    but that didn't work. The lower ERROR still tries to pop the dialog. I can't seem to handle that one.

    thanks so much.

  • Options
    einsTeIn.NETeinsTeIn.NET Member Posts: 1,050
    IF RecordRef.INSERT(TRUE) THEN
    
    doesn't help you to prevent NAS from stopping when it hits an error. The only way to keep NAS alive is to use the IF Codeunit.RUN statement. Take a look at the Job Queue module, it's using the same idea.

    Also, you could use the STARTSESSION function. But in both cases you need to make sure that your "main thread" keeps running after one of your "child threads" hits an error.

    Is your initial starting codeunit for NAS single instance?
    "Money is likewise the greatest chance and the greatest scourge of mankind."
  • Options
    shelbydzshelbydz Member Posts: 14
    that helps. thanks. i think with some refactoring, I can wrap my CRUD code in smaller units and execute them in separate sessions.

    Yes, I believe this is all a single instance.

    Thank you for the help and the patience. I'm not a cal dev. I'm a .net dev (:
  • Options
    einsTeIn.NETeinsTeIn.NET Member Posts: 1,050
    shelbydz wrote:
    ...and the patience. I'm not a cal dev. I'm a .net dev (:
    No worries, one day or the other you probably will help me out regarding one of my .net issues. :D

    Back to your issue, I think if you just wrap your code into a separate session then maybe NAS will stay alive but your C/AL code to create certain records won't finish properly. So, your records are not created. I guess that's not what you would expect.

    How does your solution work in detail? Is it a single instance codeunit with timer trigger? How is your .net component instantiated? How is the initial request started? How does the process workflow look like?
    "Money is likewise the greatest chance and the greatest scourge of mankind."
  • Options
    shelbydzshelbydz Member Posts: 14
    We got it all sorted. I had to do a lot of refactoring, putting each CRUD operation into its own code unit, then wrapping it in IF NOT THEN. But that did it for me.

    Thanks a bunch for the help.
Sign In or Register to comment.