Skip to content

Fix list.__repr__ deadlock#7275

Merged
youknowone merged 2 commits intoRustPython:mainfrom
ShaharNaveh:deadlock-repr
Mar 1, 2026
Merged

Fix list.__repr__ deadlock#7275
youknowone merged 2 commits intoRustPython:mainfrom
ShaharNaveh:deadlock-repr

Conversation

@ShaharNaveh
Copy link
Contributor

@ShaharNaveh ShaharNaveh commented Feb 28, 2026

closes #6591

Summary by CodeRabbit

  • Refactor
    • Improved internal list representation implementation for enhanced performance and maintainability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

📝 Walkthrough

Walkthrough

The PyList repr implementation is refactored to manually build string representations using a Wtf8Buf writer instead of delegating to the collection_repr helper. This eliminates holding a read-lock across element repr calls, preventing deadlocks when element repr mutates the list.

Changes

Cohort / File(s) Summary
List repr refactoring
crates/vm/src/builtins/list.rs
Replaced collection_repr-based implementation with manual writer-based approach using Wtf8Buf. New implementation refetches list elements during iteration to avoid holding locks during element repr calls, preventing deadlocks from re-entrant mutations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A lock was held while repr'd the list,
When mutations called, a deadlock twisted!
Now fetching fresh, each element's tale,
The lock releases—no more to fail!
Hopping through code, no cycles remain,
Just happy rabbits with cleaner lane! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Fix list.repr deadlock" clearly and concisely describes the main change—addressing a deadlock issue in list representation.
Linked Issues check ✅ Passed The code changes directly address issue #6591 by replacing the deadlock-prone read-lock pattern with a manual writer-based approach that re-fetches list elements, preventing lock contention.
Out of Scope Changes check ✅ Passed All changes are focused on fixing the list.repr deadlock by modifying the repr implementation in list.rs; no unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

Error: 'frozenset' object has no attribute 'discard'

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/builtins/list.rs`:
- Around line 533-555: The loop captures a snapshot in elements and only
refreshes it when length changes, which yields stale items if an element's
__repr__ mutates contents without resizing; update the loop to re-fetch the
current element by index on every iteration instead of indexing into the stale
elements snapshot (use zelf.borrow_vec() each iteration or fetch a cloned
element via zelf.borrow_vec().get(i).cloned().unwrap() before calling
item.repr(vm)), and keep using zelf.__len__() to track size changes; adjust
variables: elements, item, i, and the repr call (item.repr(vm)) so the borrow
does not persist across the repr call.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2833cd8 and f9956b2.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_list.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • crates/vm/src/builtins/list.rs

Comment on lines +533 to +555
let mut elements = zelf.borrow_vec().to_vec();
let mut size = zelf.__len__();
let mut first = true;
let mut i = 0;
while i < size {
if elements.len() != size {
// `repr` mutated the list. refetch it.
elements = zelf.borrow_vec().to_vec();
}

let item = &elements[i];

if first {
first = false;
} else {
writer.push_str(", ");
}

writer.push_wtf8(item.repr(vm)?.as_wtf8());

size = zelf.__len__(); // Refetch list size as `repr` may mutate the list.
i += 1;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Refresh elements by index each iteration, not only on length changes.

The current logic only refetches when length changes. If an element __repr__ mutates list contents without changing length, repr can emit stale items (snapshot values), which is a correctness mismatch.

Suggested fix
-            let mut elements = zelf.borrow_vec().to_vec();
-            let mut size = zelf.__len__();
-            let mut first = true;
-            let mut i = 0;
-            while i < size {
-                if elements.len() != size {
-                    // `repr` mutated the list. refetch it.
-                    elements = zelf.borrow_vec().to_vec();
-                }
-
-                let item = &elements[i];
-
-                if first {
-                    first = false;
-                } else {
-                    writer.push_str(", ");
-                }
-
-                writer.push_wtf8(item.repr(vm)?.as_wtf8());
-
-                size = zelf.__len__(); // Refetch list size as `repr` may mutate the list.
-                i += 1;
-            }
+            let mut i = 0;
+            while let Some(item) = {
+                let elements = zelf.borrow_vec();
+                elements.get(i).cloned()
+            } {
+                if i != 0 {
+                    writer.push_str(", ");
+                }
+                writer.push_wtf8(item.repr(vm)?.as_wtf8());
+                i += 1;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut elements = zelf.borrow_vec().to_vec();
let mut size = zelf.__len__();
let mut first = true;
let mut i = 0;
while i < size {
if elements.len() != size {
// `repr` mutated the list. refetch it.
elements = zelf.borrow_vec().to_vec();
}
let item = &elements[i];
if first {
first = false;
} else {
writer.push_str(", ");
}
writer.push_wtf8(item.repr(vm)?.as_wtf8());
size = zelf.__len__(); // Refetch list size as `repr` may mutate the list.
i += 1;
}
let mut i = 0;
while let Some(item) = {
let elements = zelf.borrow_vec();
elements.get(i).cloned()
} {
if i != 0 {
writer.push_str(", ");
}
writer.push_wtf8(item.repr(vm)?.as_wtf8());
i += 1;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/list.rs` around lines 533 - 555, The loop captures a
snapshot in elements and only refreshes it when length changes, which yields
stale items if an element's __repr__ mutates contents without resizing; update
the loop to re-fetch the current element by index on every iteration instead of
indexing into the stale elements snapshot (use zelf.borrow_vec() each iteration
or fetch a cloned element via zelf.borrow_vec().get(i).cloned().unwrap() before
calling item.repr(vm)), and keep using zelf.__len__() to track size changes;
adjust variables: elements, item, i, and the repr call (item.repr(vm)) so the
borrow does not persist across the repr call.

@youknowone youknowone merged commit bef46f1 into RustPython:main Mar 1, 2026
14 checks passed
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.

Deadlock when element __repr__ mutates the list via list __repr__

2 participants