Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions crates/vm/src/stdlib/_collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,16 @@ mod _collections {

#[pymethod]
fn clear(&self) {
self.state.fetch_add(1);
self.borrow_deque_mut().clear()
let removed = {
let mut deque = self.borrow_deque_mut();
if deque.is_empty() {
return;
}
self.state.fetch_add(1);
core::mem::take(&mut *deque)
};
// Finalizers may read or repopulate the now-empty deque.
drop(removed);
}

#[pymethod(name = "__copy__")]
Expand Down Expand Up @@ -387,11 +395,16 @@ mod _collections {
}

fn __setitem__(&self, idx: isize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let mut deque = self.borrow_deque_mut();
idx.wrapped_at(deque.len())
.and_then(|i| deque.get_mut(i))
.map(|x| *x = value)
.ok_or_else(|| vm.new_index_error("deque index out of range"))
let removed = {
let mut deque = self.borrow_deque_mut();
let item = idx
.wrapped_at(deque.len())
.and_then(|i| deque.get_mut(i))
.ok_or_else(|| vm.new_index_error("deque index out of range"))?;
core::mem::replace(item, value)
};
drop(removed);
Ok(())
}

fn __delitem__(&self, idx: isize, vm: &VirtualMachine) -> PyResult<()> {
Expand Down
26 changes: 26 additions & 0 deletions extra_tests/snippets/stdlib_collections_deque.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,19 @@ def test_deque_bulk_noops_preserve_iterators():
iterator = factory(d)
d.__init__([])
assert list(iterator) == []
d = deque()
iterator = factory(d)
d.clear()
assert list(iterator) == []


def test_deque_bulk_mutation_releases_lock_before_finalizing():
operations = (
(lambda d: d.extendleft([9]), [9], ["finalized"]),
(lambda d: d.__imul__(0), [], ["finalized"]),
(lambda d: d.__init__([9]), [], ["finalized", 9]),
(lambda d: d.clear(), [], ["finalized"]),
(lambda d: d.__setitem__(0, 9), [9], ["finalized"]),
)
for operation, observed, expected in operations:
d = deque(maxlen=1)
Expand All @@ -381,6 +387,25 @@ def __del__(self):
assert list(d) == expected, d


def test_deque_failed_assignment_releases_lock_before_finalizing():
d = deque()
snapshots = []

class Item:
def __del__(self):
snapshots.append(list(d))
d.append("finalized")

try:
d[0] = Item()
except IndexError:
pass
else:
raise AssertionError("assignment to an empty deque must fail")
assert snapshots == [[]], snapshots
assert list(d) == ["finalized"], d


def test_deque_reinitialization_order_and_limit():
d = deque([1, 2, 3])
d.__init__(d)
Expand Down Expand Up @@ -413,6 +438,7 @@ def __del__(self):
test_deque_bulk_mutation_invalidates_iterators()
test_deque_bulk_noops_preserve_iterators()
test_deque_bulk_mutation_releases_lock_before_finalizing()
test_deque_failed_assignment_releases_lock_before_finalizing()
test_deque_reinitialization_order_and_limit()


Expand Down
Loading