libplctag / libplctag.NET

A .NET wrapper for libplctag.
https://libplctag.github.io/
Mozilla Public License 2.0
214 stars 53 forks source link

LibPlcTagException ErrorNullPtr #388

Open arcimus opened 3 months ago

arcimus commented 3 months ago

Hello, I have an app that continually queries @ 500 total tags from around 12 ControlLogix devices. About once a week, I get a crash due to unhandled exceptions. A couple examples:

libplctag.LibPlcTagException
  HResult=0x80131500
  Message=ErrorNullPtr
  Source=libplctag
  StackTrace:
   at libplctag.NativeTagWrapper.ThrowIfStatusNotOk(Nullable`1 status) in libplctag\NativeTagWrapper.cs:line 876
   at libplctag.NativeTagWrapper.Finalize() in libplctag\NativeTagWrapper.cs:line 462
System.AccessViolationException
  HResult=0x80004003
  Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
libplctag.LibPlcTagException
  HResult=0x80131500
  Message=ErrorNullPtr
  Source=libplctag
  StackTrace:
   at libplctag.NativeTagWrapper.ThrowIfStatusNotOk(Nullable`1 status) in libplctag\NativeTagWrapper.cs:line 876
   at libplctag.NativeTagWrapper.Finalize() in libplctag\NativeTagWrapper.cs:line 462
System.Runtime.InteropServices.SEHException
  HResult=0x80004005
  Message=External component has thrown an exception.

All of the ReadAsync calls are wrapped in try/catch like:

try
{
    var tagValue = new Tag<IntPlcMapper, short>()
    {
        Name = tagName,
        Gateway = ip,
        Path = path,
        PlcType = PlcType.ControlLogix,
        Protocol = Protocol.ab_eip
    };
    await tagValue.ReadAsync();
    tagValueString = tagValue.Value.ToString();
}
catch (LibPlcTagException ex)
{
    plcException = ex.Message;
    Debug.WriteLine("Exception INT " + ex.Message + " " + ip + " " + tagName);
}

Running libplctag v1.2.0 and libplctag.NativeImport v1.0.37. I just updated to the most recent versions after making this post.

Any suggestions? Thanks!

timyhac commented 3 months ago

That exception is being thrown in the finalizer which is why you can't catch it in the code snippet.

If you're running that code over and over you'll be generating a new Tag object every time which is not how libplctag is intended to be used. From the wiki

Tag handles are designed to be very long lived. They can live weeks or months. In order to use the library to its full potential, it is important to note this. Setting up and tearing down connections to a PLC are the heaviest operations possible. They take significant time and multiple packets to handshake the connection set up. The library automatically closes PLC connections after periods of no use (typically five seconds) and automatically reopens them when you try to do something with a tag handle.

Performance Tip: Avoid creating and destroying tags often.

If you can provide Debug logs there might be some useful information we can use to find out more.

arcimus commented 2 months ago

I refactored all the code to create separate lists like List<TagReal> and List<TagDint> etc. that contain all the various tags from various devices. Then, repeatedly, for each in the lists I then TaskList.Add() separate functions that do await tag.ReadAsync() and convert the values to strings. So, now I'm not generating new Tag objects over and over. However, to ensure I'm monitoring an updated set of tags, every 15 minutes I new() the List<TagReal> and List<TagDint> etc. lists. I now fetch 555 tags in @ 400ms which is faster than it used to be.

Regarding debugging, I added Error level debugging.

timyhac commented 2 months ago

Thanks for the information - something to keep in mind is that you can deterministically dispose of the tags by using the Dispose method rather than leaving it to the Finalizer to call it.