Skip to content

Conversation

@makslevental
Copy link
Contributor

@makslevental makslevental commented Jul 25, 2025

This PR melds #150137 and #149414 and partially reverts #124832.

The summary is the PyDenseResourceElementsAttribute finalizer/deleter has/had two problems

  1. wasn't threadsafe (can be called from a different thread than that which currently holds the GIL)
  2. can be called while the interpreter is "not initialized"

#124832 for some reason decides to re-initialize the interpreter to avoid case 2 and runs afoul of the fact that Py_IsInitialized can be false during the finalization of the interpreter itself (e.g., at the end of a script).

I don't know why this decision was made (I missed the PR) but I believe we should never be calling Py_Initialize:

In an application ****embedding Python****, this should be called before using any other Python/C API functions

but we aren't embedding Python!

So therefore we will only be in case 2 when the interpreter is being finalized and in that case we should just leak the buffer.

Note, lldb does a similar sort of thing for its finalizers.

Co-authored-by: Anton Korobeynikov [email protected]
Co-authored-by: Max Manainen [email protected]

@makslevental makslevental force-pushed the makslevental/leak-buffer branch 3 times, most recently from 7f52f4c to 47afce0 Compare July 25, 2025 01:28
@makslevental makslevental requested review from asl and naibaf7 July 25, 2025 01:29
@makslevental
Copy link
Contributor Author

cc @chrsmcgrr @mamanain

@makslevental makslevental force-pushed the makslevental/leak-buffer branch 2 times, most recently from 2c96721 to 78a80af Compare July 25, 2025 01:32
@makslevental makslevental marked this pull request as ready for review July 25, 2025 01:37
@llvmbot llvmbot added the mlir label Jul 25, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 25, 2025

@llvm/pr-subscribers-mlir

Author: Maksim Levental (makslevental)

Changes

This PR melds #150137 and #149414 and partially reverts #124832.

The summary is the PyDenseResourceElementsAttribute finalizer/deleter has/had two problems

  1. wasn't threadsafe (can be called from a different thread than that which currently holds the GIL)
  2. can be called while the interpreter is not initialized

#124832 for some reason decides to re-initialize the interpreter to avoid case 2 and runs afoul of the fact that Py_IsInitialized can be false during the finalization of the interpreter itself (e.g., at the end of a script).

I don't know why this decision was made (I missed the PR) but I believe we should never be calling Py_Initialize:

> In an application embedding Python, this should be called before using any other Python/C API functions

but we aren't embedding Python!

So therefore we will only be in case when the interpreter is being finalized and in that case we should just speak the buffer.

Note, lldb does a similar sort of thing for its finalizers.


Full diff: https://github.com/llvm/llvm-project/pull/150561.diff

2 Files Affected:

  • (modified) mlir/lib/Bindings/Python/IRAttributes.cpp (+9-2)
  • (modified) mlir/test/python/ir/array_attributes.py (+15)
diff --git a/mlir/lib/Bindings/Python/IRAttributes.cpp b/mlir/lib/Bindings/Python/IRAttributes.cpp
index 8f79caf08a6d0..7ddae4b5fd358 100644
--- a/mlir/lib/Bindings/Python/IRAttributes.cpp
+++ b/mlir/lib/Bindings/Python/IRAttributes.cpp
@@ -1428,6 +1428,12 @@ class PyDenseIntElementsAttribute
   }
 };
 
+// Check if the python version is less than 3.13. Py_IsFinalizing is a part
+// of stable ABI since 3.13 and before it was available as _Py_IsFinalizing.
+#if PY_VERSION_HEX < 0x030d0000
+#define Py_IsFinalizing _Py_IsFinalizing
+#endif
+
 class PyDenseResourceElementsAttribute
     : public PyConcreteAttribute<PyDenseResourceElementsAttribute> {
 public:
@@ -1474,8 +1480,9 @@ class PyDenseResourceElementsAttribute
     // The userData is a Py_buffer* that the deleter owns.
     auto deleter = [](void *userData, const void *data, size_t size,
                       size_t align) {
-      if (!Py_IsInitialized())
-        Py_Initialize();
+      if (Py_IsFinalizing())
+        return;
+      assert(Py_IsInitialized() && "expected interpreter to be initialized");
       Py_buffer *ownedView = static_cast<Py_buffer *>(userData);
       nb::gil_scoped_acquire gil;
       PyBuffer_Release(ownedView);
diff --git a/mlir/test/python/ir/array_attributes.py b/mlir/test/python/ir/array_attributes.py
index ef1d835fc6401..67b934dd19bdb 100644
--- a/mlir/test/python/ir/array_attributes.py
+++ b/mlir/test/python/ir/array_attributes.py
@@ -617,3 +617,18 @@ def test_attribute(context, mview):
     # CHECK: BACKING MEMORY DELETED
     # CHECK: EXIT FUNCTION
     print("EXIT FUNCTION")
+
+
+# CHECK-LABEL: TEST: testDanglingResource
+print("TEST: testDanglingResource")
+# see https://github.com/llvm/llvm-project/pull/149414, https://github.com/llvm/llvm-project/pull/150137
+# This error occurs only when there is an alive context with a DenseResourceElementsAttr
+# in the end of the program, so we put it here without an encapsulating function.
+ctx = Context()
+
+with ctx, Location.unknown():
+    DenseResourceElementsAttr.get_from_buffer(
+        memoryview(np.array([1, 2, 3])),
+        "some_resource",
+        RankedTensorType.get((3,), IntegerType.get_signed(32)),
+    )

@makslevental makslevental requested a review from jpienaar July 25, 2025 01:39
@makslevental
Copy link
Contributor Author

@mamanain if you tell me your email I can update the Co-authored to cite you.

@mamanain
Copy link
Contributor

[email protected]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! This seems to be what was causing issues on our end. It now works in python 3.11.

@makslevental makslevental force-pushed the makslevental/leak-buffer branch 3 times, most recently from fa718cb to 8d90a49 Compare July 25, 2025 11:54
Co-authored-by: Anton Korobeynikov <[email protected]>
Co-authored-by: Max Manainen <[email protected]>
@makslevental makslevental force-pushed the makslevental/leak-buffer branch from 8d90a49 to e5d3fd3 Compare July 25, 2025 11:55
@makslevental makslevental merged commit 2177448 into llvm:main Jul 25, 2025
9 checks passed
@makslevental makslevental deleted the makslevental/leak-buffer branch July 25, 2025 12:05
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Jul 28, 2025
…50561)

This PR melds llvm#150137 and
llvm#149414 *and* partially reverts
llvm#124832.

The summary is the `PyDenseResourceElementsAttribute` finalizer/deleter
has/had two problems

1. wasn't threadsafe (can be called from a different thread than that
which currently holds the GIL)
2. can be called while the interpreter is "not initialized"

llvm#124832 for some reason decides
to re-initialize the interpreter to avoid case 2 and runs afoul of the
fact that `Py_IsInitialized` can be false during the finalization of the
interpreter itself (e.g., at the end of a script).

I don't know why this decision was made (I missed the PR) but I believe
we should never be calling
[Py_Initialize](https://docs.python.org/3/c-api/init.html#c.Py_Initialize):

> In an application \*\*\*\***embedding Python**\*\*\*\*, this should be
called before using any other Python/C API functions

**but we aren't embedding Python**!

So therefore we will only be in case 2 when the interpreter is being
finalized and in that case we should just leak the buffer.

Note,
[lldb](https://github.com/llvm/llvm-project/blob/548ca9e97673a168023a616d311d901ca04b29a3/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp#L81-L93)
does a similar sort of thing for its finalizers.

Co-authored-by: Anton Korobeynikov <[email protected]>
Co-authored-by: Max Manainen <[email protected]>

Co-authored-by: Anton Korobeynikov <[email protected]>
Co-authored-by: Max Manainen <[email protected]>
@asl
Copy link
Collaborator

asl commented Aug 20, 2025

@makslevental Likely we'd not need the check at all. See #152226 for similar LLDB change (and notes around).

@makslevental
Copy link
Contributor Author

Likely we'd not need the check at all.

Isn't that the original version prior to #124832? Ie isn't that bug reporting that we do indeed sometimes release when (Py_IsFinalizing()) is true?

@asl
Copy link
Collaborator

asl commented Aug 21, 2025

Isn't that the original version prior to #124832? Ie isn't that bug reporting that we do indeed sometimes release when (Py_IsFinalizing()) is true?

It seems, there are multiple cases here:

  • We're deleting an attribute from the thread with active interpreter
  • We're deleting an attribute from the thread without active interpreter
  • Dense resource is deleted when holding context is deleted during overall shutdown

If I'm reading the pytorch version and pylifecycle.c correctly, then we'd need to hold the gil if the interpreter is initialized. And simply destroy the buffer otherwise. Currently we're leaking ownedView but this seems to be fine only if the whole process is terminating. Which might not be ok in the shared library context... So, I'm thinking if something like this would be more correct?

      Py_buffer *ownedView = static_cast<Py_buffer *>(userData);
      if (Py_IsInitialized()) {
        nb::gil_scoped_acquire gil;
        PyBuffer_Release(ownedView);
      }
      delete ownedView;

@makslevental
Copy link
Contributor Author

yea sure this seems reasonable - do you want to send the PR that removes Py_IsFinalizing and etc?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants