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

GH-91079: Implement C stack limits using addresses, not counters. #130007

Open
wants to merge 45 commits into
base: main
Choose a base branch
from

Conversation

markshannon
Copy link
Member

@markshannon markshannon commented Feb 11, 2025

Copy link
Member

@encukou encukou left a comment

Choose a reason for hiding this comment

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

This adds new public API, which needs documentation.
It also removes public API; that needs deprecation or SC exception.

@bedevere-app
Copy link

bedevere-app bot commented Feb 17, 2025

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@zooba
Copy link
Member

zooba commented Feb 17, 2025

Love seeing this change! Thanks, Mark.

@markshannon
Copy link
Member Author

markshannon commented Feb 17, 2025

This adds new public API, which needs documentation.

The public API is Py_TRASHCAN_BEGIN. Py_ReachedRecursionLimit is the necessary ABI addition to support it.
I could add it to the unstable API, if I can't make it private.

It also removes public API; that needs deprecation or SC exception.

What API?

@markshannon
Copy link
Member Author

Benchmarking shows a 0.0%-0.4% speedup on 64 bit platforms, and a 2% speedup on 32 bit windows, so it isn't slower.

@markshannon
Copy link
Member Author

@encukou I've hidden the new function. I don't see any removals though, which API do you mean?

@encukou
Copy link
Member

encukou commented Feb 17, 2025

Thanks for hiding it!

There's also the newly added PyThreadState.c_stack_* members; are those meant to be private?

I don't see any removals though

I see these in Include/cpython/pystate.h:

  • PyThreadState.c_recursion_remaining
  • Py_C_RECURSION_LIMIT

I don't think getting a SC exception should be a problem.

@markshannon
Copy link
Member Author

We can add back c_recursion_remaining and Py_C_RECURSION_LIMIT, if you want.
The semantics have never been defined, so their values are meaningless to third party code and always have been.

@markshannon
Copy link
Member Author

There's also the newly added PyThreadState.c_stack_* members; are those meant to be private?

It's C code, there is no such thing as private.

@encukou
Copy link
Member

encukou commented Feb 17, 2025

The semantics have never been defined

Well, not documented, which just means people read the code to figure out what they mean.
It's probably best to remove them; should I ask the SC for you?

@markshannon
Copy link
Member Author

Well, not documented, which just means people read the code to figure out what they mean.

I really don't think we should be wasting our time on supporting incompetent C/C++ programmers who can't use APIs.
Do we really want to start prefixing every field with _DONT_USE_THIS_ just in case?

@markshannon
Copy link
Member Author

I've just discovered _PyThreadStateImpl which is a hack to avoid this problem. I'll add stuff to that.

@markshannon
Copy link
Member Author

markshannon commented Feb 18, 2025

@encukou struct _ts (the thread state) is unchanged and Py_C_RECURSION_LIMIT restored.

Copy link
Member

@encukou encukou left a comment

Choose a reason for hiding this comment

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

Thank you!

Do we really want to start prefixing every field with _DONT_USE_THIS_ just in case?

No, an underscore is fine.
It's C, nothing is private at the language level, so we have a convention to mark the private/unstable stuff.

Comment on lines -312 to -313
#ifdef USE_STACKCHECK
if (PyOS_CheckStack()) {
Copy link
Member

Choose a reason for hiding this comment

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

Could you adjust the Py_EnterRecursiveCall docs?

Python/ceval.c Outdated
Comment on lines 340 to 341
// test_descr crashed on sparc64 with >7000 but let's keep a margin of error.
# define Py_C_STACK_SIZE 1600000
Copy link
Member

Choose a reason for hiding this comment

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

The 7000 (and 8000 below) doesn't make much sense to me here.


#if defined(WIN32) && !defined(MS_WIN64) && !defined(_M_ARM) && defined(_MSC_VER) && _MSC_VER >= 1300
#if defined(WIN32)
Copy link
Member

Choose a reason for hiding this comment

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

The definition of PyOS_CheckStack still uses defined(_MSC_VER).
Looks like the #if there (and the MSVC-specific includes) can be removed.

Could you remove the MSVC reference from the PyOS_CheckStack docs?

@skip_emscripten_stack_overflow()
def test_repr_deep(self):
a = self.type2test([])
for i in range(get_c_recursion_limit() + 1):
for i in range(100_000):
Copy link
Member

Choose a reason for hiding this comment

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

Could you comment on where the 100_000, and similar magic constants below, came from?

Perhaps it would be best to reuse the name exceeds_recursion_limit, and set that to 100_000?

@@ -628,23 +628,34 @@ def test_mismatched_parens(self):
r"does not match opening parenthesis '\('",
["f'{a(4}'",
])
self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'")
self.assertRaises(SyntaxError, eval, "f'{" + "("*20 + "}'")
Copy link
Member

Choose a reason for hiding this comment

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

This looks worrying. Did the limit for nested parentheses go down over an order of magnitude?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. Previously, there was a counter and if it was exceeded a SyntaxError was raised, but it could crash if the stack overflowed before the limit was reached.
Now we raise a SyntaxError for unbalanced parens and a RecursionError for a (potential) stack overflow.
See the new tests added below (line 656)

Comment on lines +636 to +645
try:
eval(txt)
except SyntaxError:
pass
except MemoryError:
pass
except Exception as ex:
self.fail(f"Should raise SyntaxError or MemoryError, not {type(ex)}")
else:
self.fail("No exception raised")
Copy link
Member

Choose a reason for hiding this comment

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

FWIW, you could use with self.assertRaises((SyntaxError, MemoryError)):

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I don't know that was a thing. Thanks.

@@ -1538,24 +1538,21 @@ _Py_SourceAsString(PyObject *cmd, const char *funcname, const char *what, PyComp
/*
* Return non-zero when we run out of memory on the stack; zero otherwise.
*/
int
PyOS_CheckStack(void)
int PyOS_CheckStack(void)
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: please revert the style change

Suggested change
int PyOS_CheckStack(void)
int
PyOS_CheckStack(void)

}
return 1;
else {
return -1;
Copy link
Member

Choose a reason for hiding this comment

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

Could this be 1 as before? Nowadays -1 is usually used when an exception is set.

@markshannon
Copy link
Member Author

I have made the requested changes; please review again

@bedevere-app
Copy link

bedevere-app bot commented Feb 18, 2025

Thanks for making the requested changes!

@iritkatriel, @encukou: please review the changes made to this pull request.

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.

Stack overflow collecting PGO data on Windows
5 participants