Skip to content

Commit 1fdd4fd

Browse files
authored
Fix dict concurrency stability (#7231)
1 parent 93099e3 commit 1fdd4fd

1 file changed

Lines changed: 46 additions & 37 deletions

File tree

crates/vm/src/dict_inner.rs

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,11 @@ impl<T: Clone> Dict<T> {
292292
// The dict was changed since we did lookup. Let's try again.
293293
}
294294
} else {
295-
// New key:
295+
// New key - validate slot is still what lookup found
296+
if inner.indices.get(index_index) != Some(&entry_index) {
297+
// Dict was resized since lookup, retry
298+
continue;
299+
}
296300
inner.unchecked_push(index_index, hash, key.to_pyobject(vm), value, entry_index);
297301
break None;
298302
}
@@ -431,6 +435,9 @@ impl<T: Clone> Dict<T> {
431435
}
432436
} else {
433437
let mut inner = self.write();
438+
if inner.indices.get(index_index) != Some(&entry) {
439+
continue;
440+
}
434441
inner.unchecked_push(index_index, hash, key.to_owned(), value, entry);
435442
break None;
436443
}
@@ -444,31 +451,32 @@ impl<T: Clone> Dict<T> {
444451
F: FnOnce() -> T,
445452
{
446453
let hash = key.key_hash(vm)?;
447-
let res = loop {
448-
let lookup = self.lookup(vm, key, hash, None)?;
449-
let (index_entry, index_index) = lookup;
454+
let mut default = Some(default);
455+
loop {
456+
let (index_entry, index_index) = self.lookup(vm, key, hash, None)?;
450457
if let Some(index) = index_entry.index() {
451458
let inner = self.read();
452459
if let Some(entry) = inner.get_entry_checked(index, index_index) {
453-
break entry.value.clone();
454-
} else {
455-
// The dict was changed since we did lookup, let's try again.
456-
continue;
460+
return Ok(entry.value.clone());
457461
}
458-
} else {
459-
let value = default();
460-
let mut inner = self.write();
461-
inner.unchecked_push(
462-
index_index,
463-
hash,
464-
key.to_pyobject(vm),
465-
value.clone(),
466-
index_entry,
467-
);
468-
break value;
462+
continue;
469463
}
470-
};
471-
Ok(res)
464+
let mut inner = self.write();
465+
if inner.indices.get(index_index) != Some(&index_entry) {
466+
continue;
467+
}
468+
let value = default
469+
.take()
470+
.expect("default must only be computed on insertion")();
471+
inner.unchecked_push(
472+
index_index,
473+
hash,
474+
key.to_pyobject(vm),
475+
value.clone(),
476+
index_entry,
477+
);
478+
return Ok(value);
479+
}
472480
}
473481

474482
#[allow(dead_code)]
@@ -483,27 +491,28 @@ impl<T: Clone> Dict<T> {
483491
F: FnOnce() -> T,
484492
{
485493
let hash = key.key_hash(vm)?;
486-
let res = loop {
487-
let lookup = self.lookup(vm, key, hash, None)?;
488-
let (index_entry, index_index) = lookup;
494+
let mut default = Some(default);
495+
loop {
496+
let (index_entry, index_index) = self.lookup(vm, key, hash, None)?;
489497
if let Some(index) = index_entry.index() {
490498
let inner = self.read();
491499
if let Some(entry) = inner.get_entry_checked(index, index_index) {
492-
break (entry.key.clone(), entry.value.clone());
493-
} else {
494-
// The dict was changed since we did lookup, let's try again.
495-
continue;
500+
return Ok((entry.key.clone(), entry.value.clone()));
496501
}
497-
} else {
498-
let value = default();
499-
let key = key.to_pyobject(vm);
500-
let mut inner = self.write();
501-
let ret = (key.clone(), value.clone());
502-
inner.unchecked_push(index_index, hash, key, value, index_entry);
503-
break ret;
502+
continue;
504503
}
505-
};
506-
Ok(res)
504+
let mut inner = self.write();
505+
if inner.indices.get(index_index) != Some(&index_entry) {
506+
continue;
507+
}
508+
let value = default
509+
.take()
510+
.expect("default must only be computed on insertion")();
511+
let key_obj = key.to_pyobject(vm);
512+
let ret = (key_obj.clone(), value.clone());
513+
inner.unchecked_push(index_index, hash, key_obj, value, index_entry);
514+
return Ok(ret);
515+
}
507516
}
508517

509518
pub fn len(&self) -> usize {

0 commit comments

Comments
 (0)