Search code examples
c#c++pinvoke

Parallel invocations of C++ library wrapped with PInvoke raises System.AccessViolationException


I'm calling from .NET framework 4.6.1 a C++ dll (CoolProp library) using the PInvoke technology.

The following C# code:

[DllImport("CoolProp", EntryPoint = "CSharp_PropsSI")]
public static extern double PropsSI(string Output, string Name1, double Prop1, string Name2, double Prop2, string Ref);

wraps the declaration of one of the main functions of the library.

When it is invoked in a single-thread environment everything is fine. The problem raises when I tried to call this external function in parallel using the System.Threading.Tasks:

[TestMethod]
public async Task TestAsyncMethod()
{
    double tco = 30;
    string refrigerant = "R134a";

    var t1 = Task.Run(() => CoolProp.PropsSI("P", "T", tco + 273.15, "Q", 0.0, refrigerant));
    var t2 = Task.Run(() => CoolProp.PropsSI("P", "T", tco + 273.15, "Q", 0.0, refrigerant));

    var res = await Task.WhenAll(t1, t2);

    Assert.AreEqual(res[0], res[1]);
}

The unit test above fails and raises the following Exception:

Message: 
    Test method XRACModel_UnitTest.CoolingCircuitTest.TestAsyncMethod threw exception: 
    System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  Stack Trace: 
    CoolPropPINVOKE.PropsSI(String jarg1, String jarg2, Double jarg3, String jarg4, Double jarg5, String jarg6)
    CoolProp.PropsSI(String Output, String Name1, Double Prop1, String Name2, Double Prop2, String FluidName) line 141
    CoolingCircuitTest.Method(Double tco, Double tev, Double sbc, String refrigerant) line 226
    <>c__DisplayClass3_0.<TestAsyncMethod>b__0() line 214
    Task`1.InnerInvoke()
    Task.Execute()
    --- End of stack trace from previous location where exception was thrown ---
    TaskAwaiter.ThrowForNonSuccess(Task task)
    TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    TaskAwaiter`1.GetResult()
    <TestAsyncMethod>d__3.MoveNext() line 217
    --- End of stack trace from previous location where exception was thrown ---
    TaskAwaiter.ThrowForNonSuccess(Task task)
    TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    ThreadOperations.ExecuteWithAbortSafety(Action action)

What is the cause of the issue? Why using a single thread is ok, but it isn't in multi-threading? Could it be the C++ library (from an issue in the repo it seems not link)?


Solution

  • Certainly the C++ library isn't itself thread safe, for example there is a static error string https://github.com/CoolProp/CoolProp/blob/master/src/CoolProp.cpp#L59 which can be simultaneously set by the exception handling and a few other places. If this is set on multiple threads it could be a failure mode that results in memory corruption, it certainly is undefined behaviour and should be avoided.