Skip to content

Commit

Permalink
Pass a Layout to oom
Browse files Browse the repository at this point in the history
As discussed in
rust-lang/rust#49668 (comment)
and subsequent, there are use-cases where the OOM handler needs to know
the size of the allocation that failed. The alignment might also be a
cause for allocation failure, so providing it as well can be useful.
  • Loading branch information
glandium committed May 29, 2018
1 parent 94562d3 commit b98a762
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 21 deletions.
39 changes: 29 additions & 10 deletions map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use self::Entry::*;
use self::VacantEntryState::*;

use alloc::{CollectionAllocErr, oom};
use alloc::CollectionAllocErr;
use cell::Cell;
use borrow::Borrow;
use cmp::max;
Expand All @@ -23,8 +23,10 @@ use mem::{self, replace};
use ops::{Deref, Index};
use sys;

use super::table::{self, Bucket, EmptyBucket, FullBucket, FullBucketMut, RawTable, SafeHash};
use super::table::{self, Bucket, EmptyBucket, Fallibility, FullBucket, FullBucketMut, RawTable,
SafeHash};
use super::table::BucketState::{Empty, Full};
use super::table::Fallibility::{Fallible, Infallible};

const MIN_NONZERO_RAW_CAPACITY: usize = 32; // must be a power of two

Expand Down Expand Up @@ -783,11 +785,11 @@ impl<K, V, S> HashMap<K, V, S>
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn reserve(&mut self, additional: usize) {
match self.try_reserve(additional) {
match self.reserve_internal(additional, Infallible) {
Err(CollectionAllocErr::CapacityOverflow) => panic!("capacity overflow"),
Err(CollectionAllocErr::AllocErr) => oom(),
Err(CollectionAllocErr::AllocErr) => unreachable!(),
Ok(()) => { /* yay */ }
}
}
}

/// Tries to reserve capacity for at least `additional` more elements to be inserted
Expand All @@ -809,17 +811,24 @@ impl<K, V, S> HashMap<K, V, S>
/// ```
#[unstable(feature = "try_reserve", reason = "new API", issue="48043")]
pub fn try_reserve(&mut self, additional: usize) -> Result<(), CollectionAllocErr> {
self.reserve_internal(additional, Fallible)
}

fn reserve_internal(&mut self, additional: usize, fallibility: Fallibility)
-> Result<(), CollectionAllocErr> {

let remaining = self.capacity() - self.len(); // this can't overflow
if remaining < additional {
let min_cap = self.len().checked_add(additional)
let min_cap = self.len()
.checked_add(additional)
.ok_or(CollectionAllocErr::CapacityOverflow)?;
let raw_cap = self.resize_policy.try_raw_capacity(min_cap)?;
self.try_resize(raw_cap)?;
self.try_resize(raw_cap, fallibility)?;
} else if self.table.tag() && remaining <= self.len() {
// Probe sequence is too long and table is half full,
// resize early to reduce probing length.
let new_capacity = self.table.capacity() * 2;
self.try_resize(new_capacity)?;
self.try_resize(new_capacity, fallibility)?;
}
Ok(())
}
Expand All @@ -831,11 +840,21 @@ impl<K, V, S> HashMap<K, V, S>
/// 2) Ensure `new_raw_cap` is a power of two or zero.
#[inline(never)]
#[cold]
fn try_resize(&mut self, new_raw_cap: usize) -> Result<(), CollectionAllocErr> {
fn try_resize(
&mut self,
new_raw_cap: usize,
fallibility: Fallibility,
) -> Result<(), CollectionAllocErr> {
assert!(self.table.size() <= new_raw_cap);
assert!(new_raw_cap.is_power_of_two() || new_raw_cap == 0);

let mut old_table = replace(&mut self.table, RawTable::try_new(new_raw_cap)?);
let mut old_table = replace(
&mut self.table,
match fallibility {
Infallible => RawTable::new(new_raw_cap),
Fallible => RawTable::try_new(new_raw_cap)?,
}
);
let old_size = old_table.size();

if old_table.size() == 0 {
Expand Down
43 changes: 32 additions & 11 deletions table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,11 +711,21 @@ fn test_offset_calculation() {
assert_eq!(calculate_offsets(6, 12, 4), (8, 20, false));
}

pub(crate) enum Fallibility {
Fallible,
Infallible,
}

use self::Fallibility::*;

impl<K, V> RawTable<K, V> {
/// Does not initialize the buckets. The caller should ensure they,
/// at the very least, set every hash to EMPTY_BUCKET.
/// Returns an error if it cannot allocate or capacity overflows.
unsafe fn try_new_uninitialized(capacity: usize) -> Result<RawTable<K, V>, CollectionAllocErr> {
unsafe fn new_uninitialized_internal(
capacity: usize,
fallibility: Fallibility,
) -> Result<RawTable<K, V>, CollectionAllocErr> {
if capacity == 0 {
return Ok(RawTable {
size: 0,
Expand Down Expand Up @@ -754,8 +764,12 @@ impl<K, V> RawTable<K, V> {
return Err(CollectionAllocErr::CapacityOverflow);
}

let buffer = Global.alloc(Layout::from_size_align(size, alignment)
.map_err(|_| CollectionAllocErr::CapacityOverflow)?)?;
let layout = Layout::from_size_align(size, alignment)
.map_err(|_| CollectionAllocErr::CapacityOverflow)?;
let buffer = Global.alloc(layout).map_err(|e| match fallibility {
Infallible => oom(layout),
Fallible => e,
})?;

Ok(RawTable {
capacity_mask: capacity.wrapping_sub(1),
Expand All @@ -768,9 +782,9 @@ impl<K, V> RawTable<K, V> {
/// Does not initialize the buckets. The caller should ensure they,
/// at the very least, set every hash to EMPTY_BUCKET.
unsafe fn new_uninitialized(capacity: usize) -> RawTable<K, V> {
match Self::try_new_uninitialized(capacity) {
match Self::new_uninitialized_internal(capacity, Infallible) {
Err(CollectionAllocErr::CapacityOverflow) => panic!("capacity overflow"),
Err(CollectionAllocErr::AllocErr) => oom(),
Err(CollectionAllocErr::AllocErr) => unreachable!(),
Ok(table) => { table }
}
}
Expand All @@ -794,22 +808,29 @@ impl<K, V> RawTable<K, V> {
}
}

/// Tries to create a new raw table from a given capacity. If it cannot allocate,
/// it returns with AllocErr.
pub fn try_new(capacity: usize) -> Result<RawTable<K, V>, CollectionAllocErr> {
fn new_internal(
capacity: usize,
fallibility: Fallibility,
) -> Result<RawTable<K, V>, CollectionAllocErr> {
unsafe {
let ret = RawTable::try_new_uninitialized(capacity)?;
let ret = RawTable::new_uninitialized_internal(capacity, fallibility)?;
ptr::write_bytes(ret.hashes.ptr(), 0, capacity);
Ok(ret)
}
}

/// Tries to create a new raw table from a given capacity. If it cannot allocate,
/// it returns with AllocErr.
pub fn try_new(capacity: usize) -> Result<RawTable<K, V>, CollectionAllocErr> {
Self::new_internal(capacity, Fallible)
}

/// Creates a new raw table from a given capacity. All buckets are
/// initially empty.
pub fn new(capacity: usize) -> RawTable<K, V> {
match Self::try_new(capacity) {
match Self::new_internal(capacity, Infallible) {
Err(CollectionAllocErr::CapacityOverflow) => panic!("capacity overflow"),
Err(CollectionAllocErr::AllocErr) => oom(),
Err(CollectionAllocErr::AllocErr) => unreachable!(),
Ok(table) => { table }
}
}
Expand Down

0 comments on commit b98a762

Please sign in to comment.