Skip to content

GH-50398/GH-50829 3/6: Use the CPython Limited API in the remaining fast paths - #51374

Closed
fboudra wants to merge 17 commits into
apache:mainfrom
fboudra:pr-50409-limited-api-fast-paths
Closed

fboudra wants to merge 17 commits into
apache:mainfrom
fboudra:pr-50409-limited-api-fast-paths

Conversation

@fboudra

@fboudra fboudra commented Sep 17, 2026

Copy link
Copy Markdown

Rationale for this change

#50398
#50829

What changes are included in this PR?

Finishes the source-level conversion. After this PR every C++ translation unit in src/arrow/python/ compiles under
Py_LIMITED_API=0x030B0000, but the PR does not define the flag, so builds and CI are still on the full API.

The changes:

  • common.h: the memoryview path uses PyObject_GetBuffer / PyBuffer_Release instead of PyMemoryView_GET_BUFFER. This also fixes a use-after-free for non-contiguous memoryviews, where the previous ref was dropped without the DECREF.
  • iterators.h and numpy_convert.cc: PySequence_ITEM and PySequence_Fast_GET_ITEM become PySequence_GetItem and PySequence_Fast plus PyList_GetItem, uniform across GIL-enabled and GIL-disabled builds.
  • python_to_arrow.cc: PyTuple_GET_SIZE / PyList_GET_SIZE become PyTuple_Size / PyList_Size.
  • common.cc: the ty->tp_name struct read becomes PyObject_StdStringTypeName.
  • common.h: PyGILState_Check (full API only, declared in cpython/pystate.h) is replaced with Py_IsInitialized. For any thread that is executing Python code the two mean the same thing, and Py_IsInitialized still catches the post-finalization case that [Python] Segmentation fault when pyarrow is imported in exit handler #38626 is about.
  • The vendored pythoncapi_compat.h gets its PyFrame and PyThreadState_GetFrame shims guarded under Py_LIMITED_API; no arrow source calls them.

The last commit also fixes the remaining blockers in datetime, extension_type, numpy_to_arrow and python_to_arrow, which the per-TU syntax audit (23 translation units) found.

Are these changes tested?

Yes

Are there any user-facing changes?

No

Was AI used for this PR?

PR code and description written by:

  • [x ] Human
  • AI

Reviewed before submission by:

  • [ x] Human
  • AI
  • Not reviewed

mroeschke and others added 17 commits September 17, 2026 10:33
Convert the last 7 macro fast-paths and 1 struct-field read in
src/arrow/python/ to stable C-API equivalents so the translation units
build under Py_LIMITED_API=0x030B0000:

- common.h PyBytesView memoryview: PyMemoryView_GET_BUFFER (struct read)
  -> PyObject_GetBuffer/PyBuffer_Release, holding the contiguous
  memoryview via `ref` (fixes a use-after-free for non-contiguous
  memoryviews, where the prior ref was dropped without DECREF).
- iterators.h / numpy_convert.cc: PySequence_ITEM / PySequence_Fast_GET_*
  -> PySequence_GetItem / PySequence_Fast + PyList_GetItem (uniform
  across GIL-enabled and GIL-disabled; PySequence_ITEM is a hidden
  struct-access macro unavailable under the limited API).
- python_to_arrow.cc: PyTuple_GET_SIZE / PyList_GET_SIZE -> PyTuple_Size /
  PyList_Size.
- common.cc: ty->tp_name struct read -> PyObject_StdStringTypeName.

All introduced functions verified to resolve under Py_LIMITED_API=0x030B0000.

Signed-off-by: Fathi Boudra <fathi.boudra@linaro.org>
PyGILState_Check() is a full-C-API function (declared only in
cpython/pystate.h, absent from the public header) and is unavailable under
Py_LIMITED_API=0x030B0000. In the non-freethreading cp311-abi3 build we ship,
it reduces to "the current thread has a valid thread state and holds the GIL",
which for any thread executing Python code is equivalent to Py_IsInitialized().
Py_IsInitialized() is limited-API-safe and still guards the post-finalization
case (apacheGH-38626).

It clears PyGILState_Check from all 13 translation units that pulled it in via
common.h.

Signed-off-by: Fathi Boudra <fathi.boudra@linaro.org>
These vendored pythoncapi_compat shims reference PyFrameObject, which is
only defined when frameobject.h is pulled in, and an abi3 build
(Py_LIMITED_API) does not include it. No arrow source calls the PyFrame*
API, so guard the two frame blocks so the limited-API build compiles.

Signed-off-by: Fathi Boudra <fathi.boudra@linaro.org>
Every pyarrow C++ translation unit compiles clean under
Py_LIMITED_API=0x030B0000 (cp311-abi3).

- datetime.h: define a layout-compatible PyDateTime_CAPI struct plus the
  PyDate_Check / PyDate_FromDate / PyTime_FromTime / PyDelta_FromDSU macros
  under Py_LIMITED_API. CPython's <datetime.h> is entirely absent in an abi3
  build, but the datetime C-API is still exposed at runtime through the
  "datetime.datetime_CAPI" capsule that InitDatetime() already imports.
- datetime.h/.cc, python_to_arrow.cc: read date/time/datetime/timedelta
  fields through the stable attribute API (PyDatetimeField) instead of the
  struct-field accessors hidden under Py_LIMITED_API; change the to_s/_ms/_us
  helpers to take PyObject*.
- python_to_arrow.cc: replace PyList_GetItemRef/PyDict_GetItemStringRef-style
  3.13+ calls with stable PyList_GetItem / PyDict_GetItem* equivalents.
- extension_type.cc: replace PyWeakref_GetRef (3.12+) with the stable
  PyWeakref_GetObject.
- helpers.cc: convert np.float16 via the __float__ protocol instead of the
  numpy scalar C-API (PyArray_IsScalar/PyArrayScalar_VAL, Half) which are
  hidden under Py_LIMITED_API; use PyTuple_New(0) instead of
  Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_TUPLE).
- numpy_to_arrow.cc: cast the opaque-under-limited-API numpy types to
  PyObject* at Python C-API boundaries; replace PyDict_GetItemStringRef with
  PyDict_GetItemString.

Per-TU syntax audit: 23/23 translation units compile clean under
Py_LIMITED_API=0x030B0000.

Signed-off-by: Fathi Boudra <fathi.boudra@linaro.org>
@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

This pull request has been automatically closed because you currently have 4 open pull requests, which is more than the limit of 3.

Due to the increase in pull requests opened by AI bots, and in order to keep the review queue manageable, Apache Arrow limits contributors without repository access to at most 3 concurrently open pull requests. This helps make sure each pull request gets the attention it needs and that work in progress does not go stale.

Once one of your other open pull requests has been merged or closed, you are welcome to reopen this one.

See also:

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants