Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error #291

Open
psxbox opened this issue Oct 23, 2024 · 4 comments
Open

Error #291

psxbox opened this issue Oct 23, 2024 · 4 comments

Comments

@psxbox
Copy link

psxbox commented Oct 23, 2024

Error in calling python method from Blazor app:

info: CSnakes.Runtime.IPythonEnvironment[0]
      Setting up Python environment from C:\Users\adham\.nuget\packages\python\3.12.6\tools using home of D:\Projects\C#\_EXPERIMENT\CSnakes\BlazorApp1\BlazorApp1\wwwroot\modules
Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Repeat 2 times:
--------------------------------
   at CSnakes.Runtime.CPython.CPythonAPI.Py_DecRefRaw(IntPtr)
--------------------------------
   at CSnakes.Runtime.Python.GIL+PyGilState.Dispose()
   at BlazorApp1.Components.Pages.Home+<Test>d__3.MoveNext()

Python file:

from typing import Tuple

def test(string: str) -> str:
    return string

def info() -> Tuple[str, str, str, str]:
    """
    Get information about this module

    Returns a tuple containing name, description, module type and version
    """
    
    return "Demo", "This is demo", "DataReader", "1.0.0"

def callback(cb_func) -> bool:
    value = cb_func()
    return value == "Hello, World!"

C# code:

private async Task Test((string fileName, (string name, string desription, string type, string version)) item)
    {
        try
        {
            using (GIL.Acquire())
            {
                var fileName = Path.GetFileNameWithoutExtension(item.fileName);
                using PyObject module = Import.ImportModule(fileName);
                using var infoFunc = module.GetAttr("test");
                var testStr = "Hello, World!";
                using var pyStr = PyObject.From(testStr);
                using var result = infoFunc.Call(pyStr);
                var info = result.As<string>();
                if (info != testStr)
                {
                    throw new Exception("Test failed");
                }
                else
                {
                    await jsRuntime.InvokeVoidAsync("alert", "Test passed");
                }
            }
        }
        catch (Exception ex)
        {
            await jsRuntime.InvokeVoidAsync("alert", ex.Message);
            logger.LogError(ex, "Test failed");
        }
    }
@psxbox
Copy link
Author

psxbox commented Oct 23, 2024

I changed the C# code and the problem was solved

private async Task Test((string fileName, (string name, string desription, string type, string version)) item)
    {
        try
        {
            using (GIL.Acquire())
            {
                var fileName = Path.GetFileNameWithoutExtension(item.fileName);
                using PyObject module = Import.ImportModule(fileName);
                using var infoFunc = module.GetAttr("test");
                var testStr = "Hello, World!";
                using var pyStr = PyObject.From(testStr);
                using var result = infoFunc.Call(pyStr);
                var info = result.As<string>();
                if (info != testStr)
                {
                    throw new Exception("Test failed");
                }
            }
        }
        catch (Exception ex)
        {
            await jsRuntime.InvokeVoidAsync("alert", ex.Message);
            logger.LogError(ex, "Test failed");
            return;
        }

        await jsRuntime.InvokeVoidAsync("alert", "Test passed");
    }

@atifaziz
Copy link
Contributor

atifaziz commented Oct 23, 2024

I think your problem was the await call while holding on to the GIL. The PyGILState_Release documentation says that the GIL must be released on the same thread it was acquired on:

Every call to PyGILState_Ensure() must be matched by a call to PyGILState_Release() on the same thread.

However, the await call can potentially complete or wake up on a different thread than when it started awaiting. This is why moving the await outside the using fixes the problem. That said, two things:

  1. It's generally not a good practice to hold a lock across an await boundary.
  2. I don't think your user code needs to worry about the GIL since CSnakes should acquire and release as necessary.

On point 2, I think the exception here is Import.ImportModule, which doesn't acquire & release the GIL internally:

public static PyObject ImportModule(string module)
{
return CPythonAPI.Import(module);
}

and neither does CPythonAPI.Import that is delegates to:

internal static PyObject Import(string name)
{
nint pyName = AsPyUnicodeObject(name);
nint module = PyImport_Import(pyName);
Py_DecRefRaw(pyName);
return PyObject.Create(module);
}

@tonybaloney can probably comment on whether Import.ImportModule was designed for public consumption or that this is an oversight.

@psxbox
Copy link
Author

psxbox commented Oct 24, 2024

Thank you! I understand what it is. But unfortunately, I wanted to use it in a multitasking app.

@atifaziz
Copy link
Contributor

I wanted to use it in a multitasking app.

Right, but do you need the GIL for that? Couldn't you use a standard lock or synchronisation primitive to coordinate the work between the threads in your application?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants