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

bpo-44914: Maintain invariants of type version tags. #27773

Conversation

markshannon
Copy link
Member

@markshannon markshannon commented Aug 15, 2021

  • Do not reset global version number counter: makes sure that version numbers are unique.
  • Do not invalidate type versions unnecessarily.

https://bugs.python.org/issue44914

@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @markshannon for commit 68fd56e 🤖

If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again.

@markshannon markshannon added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Aug 15, 2021
@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Aug 15, 2021
@Fidget-Spinner
Copy link
Member

Fidget-Spinner commented Aug 15, 2021

LGTM. If you want tests, I opened another PR at https://github.com/python/cpython/pull/27774/files for your consideration please (thought it also has my alternative fix in there).

entry->value = NULL;
}

// Mark all version tags as invalid
PyType_Modified(&PyBaseObject_Type);
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I'm confused as to why we don't need this anymore?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because we don't reuse the version numbers anymore, so the old ones remain valid.

Copy link
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

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

FWIW: test_descr is now passing on my LOAD_METHOD specialization branch (without requiring any type identity checks :). Woohoo!

@markshannon markshannon added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Aug 15, 2021
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @markshannon for commit 3850ef2 🤖

If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Aug 15, 2021
@markshannon markshannon added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Aug 15, 2021
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @markshannon for commit 68da204 🤖

If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Aug 15, 2021
Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

Can you please add a NEWS entry?

@@ -45,7 +45,7 @@ class object "PyObject *" "&PyBaseObject_Type"

// bpo-42745: next_version_tag remains shared by all interpreters because of static types
// Used to set PyTypeObject.tp_version_tag
static unsigned int next_version_tag = 0;
static unsigned int next_version_tag = 1;
Copy link
Member

Choose a reason for hiding this comment

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

Would it be worth it to change PyTypeObject.tp_version_tag type to uint64_t to reduce the risk of overflow?

2^64 version changes is less likely than 2^32 changes.

Copy link
Member

@Fidget-Spinner Fidget-Spinner Aug 16, 2021

Choose a reason for hiding this comment

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

PyType_ClearCache is documented to return unsigned int, not uint64_t, so I don't know if we can change that without deprecation. https://docs.python.org/3/c-api/type.html#c.PyType_ClearCache.

Copy link
Member Author

Choose a reason for hiding this comment

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

64 bit versions would make the type cache twice the size and might well make performance worse.

Classes don't change that much. If it happens that long running programs use 2**32 versions, then it is likely that a small set of classes change often. In that case we could limit the number of version that an individual class can use up.

// Wrap-around or just starting Python - clear the whole cache
type_cache_clear(cache, 1);
if (next_version_tag == 0) {
/* We have run out of version numbers */
Copy link
Member

Choose a reason for hiding this comment

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

Can you please elaborate the comment to explain what happen on next_version_tag overflow? I understand that old types keep their caches, but types which are are changed are no longer cached.

Copy link
Member

Choose a reason for hiding this comment

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

Random thought which I haven't verified:
Why not call PyType_Modified on base object type and clear cache on overflow, then continue incrementing version tag counter again?

That way even after overflow, new version tags are assigned. Just that every UINT32_TMAX type cache miss event will cause temporarily bad performance. But that seems worth it vs bad performance all the way after we hit UINT32_TMAX.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you please elaborate the comment to explain what happen on next_version_tag overflow? I understand that old types keep their caches, but types which are are changed are no longer cached.

It doesn't overflow. Once next_version_tag reaches 0, we stop issuing new versions.

Copy link
Member Author

Choose a reason for hiding this comment

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

Random thought which I haven't verified:
Why not call PyType_Modified on base object type and clear cache on overflow, then continue incrementing version tag counter again?

That way even after overflow, new version tags are assigned. Just that every UINT32_TMAX type cache miss event will cause temporarily bad performance. But that seems worth it vs bad performance all the way after we hit UINT32_TMAX.

We would also need to find and clear every specialized instruction that uses the version tag. It is possible, but I don't think it is worth it. 2**32 is a large number.

entry->version = 0;
Py_CLEAR(entry->name);
entry->value = NULL;
}
Copy link
Member

Choose a reason for hiding this comment

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

Why not reusing type_cache_clear() with a parameter to decide how to handle entry->name, to factorize the code?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because type_cache_clear() does something different. It resets the cache to its starting state.
This code clears it out during finalization.

Any function with a boolean argument that changes it behavior is really two functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've refactored type_cache_clear() to take a PyObject *.

@markshannon markshannon force-pushed the dont-invalidate-type-version-unnecessarily branch from 165ad11 to 6bbb1cf Compare August 16, 2021 10:55
@vstinner
Copy link
Member

Would it make sense to emit a warning once Python cannot issue new version?

@markshannon
Copy link
Member Author

Would it make sense to emit a warning once Python cannot issue new version?

I don't think we should worry about it.
A program that modifies a class, and calls methods on that class, over 4 billion times is pathological.

In the highly unlikely case that it happens in practice, we can reconsider.

else {
Py_CLEAR(entry->name);
}
Py_XSETREF(entry->name, _Py_XNewRef(value));
Copy link
Member

Choose a reason for hiding this comment

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

Remark: _Py_XNewRef() should not be used, you should use the public Py_XNewRef() instead.

@markshannon markshannon deleted the dont-invalidate-type-version-unnecessarily branch August 23, 2021 13:31
facebook-github-bot pushed a commit to facebookincubator/cinder that referenced this pull request Mar 2, 2023
Summary:
This is a backport of python/cpython#27773 and
python/cpython#27774 from CPython 3.12. They allow us
to perform lookups that assign a new valid version tag to a type in callbacks
invoked by `PyType_Lookup()`, which I need to do in D42512608 (and probably
others in the future).

I moved the call to `_PyClassLoader_ClearCache()` from `_PyType_ClearCache()`
to `type_cache_clear()` to ensure that it's called by both `_PyType_Fini()` and
`PyType_ClearCache()` (the upstream changes made it so `_PyType_Fini()` no
longer calls `_PyType_ClearCache()`, instead calling `type_cache_clear()`
directly).

Original Summaries:

bpo-44914: Maintain invariants of type version tags. (GH-27773)

* Do not invalidate type versions unnecessarily.

----

bpo-44914: Add tests for some invariants of tp_version_tag (GH-27774)

Reviewed By: carljm

Differential Revision: D42760412

fbshipit-source-id: 9d5f2a5
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

Successfully merging this pull request may close these issues.

5 participants