Title: Concurrent partial.__setstate__() can free args during flattening · Issue #157099 · python/cpython · GitHub
Open Graph Title: Concurrent partial.__setstate__() can free args during flattening · Issue #157099 · python/cpython
X Title: Concurrent partial.__setstate__() can free args during flattening · Issue #157099 · python/cpython
Description: Description functools.partial.__setstate__() independently replaces fn, args, kw, dict, phcount, and vectorcall without a critical section. partial_new(), partial_call(), and partial_vectorcall() read these fields and borrow tuple items ...
Open Graph Description: Description functools.partial.__setstate__() independently replaces fn, args, kw, dict, phcount, and vectorcall without a critical section. partial_new(), partial_call(), and partial_vectorcall() r...
X Description: Description functools.partial.__setstate__() independently replaces fn, args, kw, dict, phcount, and vectorcall without a critical section. partial_new(), partial_call(), and partial_vectorcall() r...
Opengraph URL: https://github.com/python/cpython/issues/157099
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Concurrent partial.__setstate__() can free args during flattening","articleBody":"**Description**\n\n`functools.partial.__setstate__()` independently replaces `fn`, `args`, `kw`, `dict`, `phcount`, and `vectorcall` without a critical section. `partial_new()`, `partial_call()`, and `partial_vectorcall()` read these fields and borrow tuple items without taking owned references to one coherent state. A setter can therefore release the old args tuple while another thread concatenates or indexes it, and readers can observe mixed state from different replacements.\n\n**Observed Behavior**\n\nOne thread repeatedly installed fresh valid states while seven threads called the partial and flattened it into another partial. The free-threaded ASan build crashed after 0.6 seconds in `tuple_concat()` from `partial_new()` at `Modules/_functoolsmodule.c:287`. The same executable completed 340,357 call/construction cycles in eight seconds with the GIL enabled.\n\n**Affected Version**\n\nCPython 3.14.7 at commit `823f0323ee6ec1402088b73bce1a38473cac36dc`, tested with a free-threaded ASan build.\n\n**Reproduction**\n\n```sh\nASAN_OPTIONS=abort_on_error=1:detect_leaks=0 PYTHON_GIL=0 python3.14 poc/reproduce.py 10\n```\n\nSetting `PYTHON_GIL=1` provides the GIL-enabled control.\n\n**PoC Source Code**\n\n`poc/reproduce.py`\n\n```python\nimport functools\nimport sys\nimport threading\nimport time\n\n\nclass Payload:\n __slots__ = (\"data\",)\n\n def __init__(self):\n self.data = bytearray(8192)\n\n\ndef target_a(*args, **kwargs):\n return len(args)\n\n\ndef target_b(*args, **kwargs):\n return len(kwargs)\n\n\ndef fresh_state(turn):\n func = target_a if turn & 1 else target_b\n args = tuple(Payload() for _ in range(96))\n return (func, args, {}, None)\n\n\nduration = float(sys.argv[1]) if len(sys.argv) > 1 else 10.0\nshared = functools.partial(target_a, *fresh_state(1)[1])\nstop = threading.Event()\ncounts = [0] * 7\n\n\ndef mutate():\n turn = 0\n while not stop.is_set():\n shared.__setstate__(fresh_state(turn))\n turn += 1\n\n\ndef exercise(slot):\n while not stop.is_set():\n try:\n shared()\n functools.partial(shared, Payload())\n counts[slot] += 1\n except (TypeError, ValueError):\n pass\n\n\nthreads = [threading.Thread(target=mutate)]\nthreads.extend(threading.Thread(target=exercise, args=(i,)) for i in range(7))\nfor thread in threads:\n thread.start()\ntime.sleep(duration)\nstop.set()\nfor thread in threads:\n thread.join()\nprint(\"completed\", sum(counts))\n```\n","author":{"url":"https://github.com/Nievesjyl","@type":"Person","name":"Nievesjyl"},"datePublished":"2026-09-07T18:11:04Z","url":"https://github.com/python/cpython/issues/157099"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:937e314e-4888-8aa3-1e99-b6aa3812b0db |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | D94E:535A8:601E78:84927C:6AF85B1F |
| html-safe-nonce | 7a36e96cfb3b9caa9909b78ca62d3dfbcd495d6a0ee9c2ff94e7b9c49506b5d4 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEOTRFOjUzNUE4OjYwMUU3ODo4NDkyN0M6NkFGODVCMUYiLCJ2aXNpdG9yX2lkIjoiNzc2MjU0MjIwNjg3ODUwNTc1OSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | f96c8a5b9cdb754f694a6b9c65542ccdfed56600b55715a82923140aeb0a1f18 |
| hovercard-subject-tag | issue:5377842085 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/cpython/157099/issue_layout |
| twitter:image | https://opengraph.githubassets.com/991e9bfa0cab37a707cf77cb28edd24688697f9ca48e71bc9c13016be486b44e/python/cpython/issues/157099 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/991e9bfa0cab37a707cf77cb28edd24688697f9ca48e71bc9c13016be486b44e/python/cpython/issues/157099 |
| og:image:alt | Description functools.partial.__setstate__() independently replaces fn, args, kw, dict, phcount, and vectorcall without a critical section. partial_new(), partial_call(), and partial_vectorcall() r... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | Nievesjyl |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5713ecd83001b2f55c3509fb7a292bf7d66d0f167c7ae87a9f7eaec5a52bf8db |
| turbo-cache-control | no-preview |
| go-import | github.com/python/cpython git https://github.com/python/cpython.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 81598961 |
| octolytics-dimension-repository_nwo | python/cpython |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 81598961 |
| octolytics-dimension-repository_network_root_nwo | python/cpython |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 653be32ff5c755082a4db662491bfbbcfc9112c1 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width