-
Notifications
You must be signed in to change notification settings - Fork 78
Lock mark sweep block list during release #1106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,19 +4,23 @@ use crate::util::linear_scan::Region; | |
| use crate::vm::VMBinding; | ||
| use std::sync::atomic::AtomicBool; | ||
| use std::sync::atomic::Ordering; | ||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| use std::sync::Mutex; | ||
|
|
||
| /// List of blocks owned by the allocator | ||
| #[repr(C)] | ||
| pub struct BlockList { | ||
| pub first: Option<Block>, | ||
| pub last: Option<Block>, | ||
| pub size: usize, | ||
| pub lock: AtomicBool, | ||
| first: Option<Block>, | ||
| last: Option<Block>, | ||
| size: usize, | ||
| lock: AtomicBool, | ||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| sanity_list: Mutex<Vec<Block>>, | ||
| } | ||
|
|
||
| impl std::fmt::Debug for BlockList { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
| write!(f, "BlockList {:?}", self.iter().collect::<Vec<Block>>()) | ||
| write!(f, "{:?}", self.iter().collect::<Vec<Block>>()) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -27,16 +31,45 @@ impl BlockList { | |
| last: None, | ||
| size, | ||
| lock: AtomicBool::new(false), | ||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| sanity_list: Mutex::new(vec![]), | ||
| } | ||
| } | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| fn verify_block_list(&self, sanity_list: &mut Vec<Block>) { | ||
| if !sanity_list | ||
| .iter() | ||
| .cloned() | ||
| .eq(BlockListIterator { cursor: self.first }) | ||
| { | ||
| eprintln!("Sanity block list: {:?}", sanity_list); | ||
| eprintln!("First {:?}", sanity_list.first()); | ||
| eprintln!("Actual block list: {:?}", self); | ||
| eprintln!("First {:?}", self.first); | ||
| eprintln!("Block list {:?}", self as *const _); | ||
| panic!("Incorrect block list"); | ||
| } | ||
| } | ||
|
|
||
| /// List has no blocks | ||
| #[allow(clippy::let_and_return)] | ||
| pub fn is_empty(&self) -> bool { | ||
| self.first.is_none() | ||
| let ret = self.first.is_none(); | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
| self.verify_block_list(&mut sanity_list); | ||
| assert_eq!(sanity_list.is_empty(), ret); | ||
| } | ||
|
|
||
| ret | ||
| } | ||
|
|
||
| /// Remove a block from the list | ||
| pub fn remove(&mut self, block: Block) { | ||
| trace!("Blocklist {:?}: Remove {:?}", self as *const _, block); | ||
| match (block.load_prev_block(), block.load_next_block()) { | ||
| (None, None) => { | ||
| self.first = None; | ||
|
|
@@ -45,23 +78,40 @@ impl BlockList { | |
| (None, Some(next)) => { | ||
| next.clear_prev_block(); | ||
| self.first = Some(next); | ||
| next.store_block_list(self); | ||
| // next.store_block_list(self); | ||
| debug_assert_eq!(next.load_block_list(), self as *mut _); | ||
| } | ||
| (Some(prev), None) => { | ||
| prev.clear_next_block(); | ||
| self.last = Some(prev); | ||
| prev.store_block_list(self); | ||
| // prev.store_block_list(self); | ||
| debug_assert_eq!(prev.load_block_list(), self as *mut _); | ||
| } | ||
| (Some(prev), Some(next)) => { | ||
| prev.store_next_block(next); | ||
| next.store_prev_block(prev); | ||
| } | ||
| } | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if there is a contention on this lock, there must be a race because no two threads should call |
||
| if let Some((index, _)) = sanity_list | ||
| .iter() | ||
| .enumerate() | ||
| .find(|&(_, &val)| val == block) | ||
| { | ||
| sanity_list.remove(index); | ||
| } else { | ||
| panic!("Cannot find {:?} in the block list", block); | ||
| } | ||
| self.verify_block_list(&mut sanity_list); | ||
| } | ||
| } | ||
|
|
||
| /// Pop the first block in the list | ||
| pub fn pop(&mut self) -> Option<Block> { | ||
| if let Some(head) = self.first { | ||
| let ret = if let Some(head) = self.first { | ||
| if let Some(next) = head.load_next_block() { | ||
| self.first = Some(next); | ||
| next.clear_prev_block(); | ||
|
|
@@ -75,11 +125,27 @@ impl BlockList { | |
| Some(head) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||
| let sanity_ret = if sanity_list.is_empty() { | ||
| None | ||
| } else { | ||
| Some(sanity_list.remove(0)) // pop first | ||
| }; | ||
| self.verify_block_list(&mut sanity_list); | ||
| assert_eq!(sanity_ret, ret); | ||
| } | ||
|
|
||
| trace!("Blocklist {:?}: Pop = {:?}", self as *const _, ret); | ||
| ret | ||
| } | ||
|
|
||
| /// Push block to the front of the list | ||
| pub fn push(&mut self, block: Block) { | ||
| trace!("Blocklist {:?}: Push {:?}", self as *const _, block); | ||
| if self.is_empty() { | ||
| block.clear_next_block(); | ||
| block.clear_prev_block(); | ||
|
|
@@ -93,10 +159,33 @@ impl BlockList { | |
| self.first = Some(block); | ||
| } | ||
| block.store_block_list(self); | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the same. |
||
| sanity_list.insert(0, block); // push front | ||
| self.verify_block_list(&mut sanity_list); | ||
| } | ||
| } | ||
|
|
||
| /// Moves all the blocks of `other` into `self`, leaving `other` empty. | ||
| pub fn append(&mut self, other: &mut BlockList) { | ||
| trace!( | ||
| "Blocklist {:?}: Append Blocklist {:?}", | ||
| self as *const _, | ||
| other as *const _ | ||
| ); | ||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| // Check before merging | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
| self.verify_block_list(&mut sanity_list); | ||
| let mut sanity_list_other = other.sanity_list.lock().unwrap(); | ||
| other.verify_block_list(&mut sanity_list_other); | ||
| } | ||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| let mut sanity_list_in_other = other.sanity_list.lock().unwrap().clone(); | ||
|
|
||
| debug_assert_eq!(self.size, other.size); | ||
| if !other.is_empty() { | ||
| debug_assert!( | ||
|
|
@@ -128,12 +217,26 @@ impl BlockList { | |
| } | ||
| other.reset(); | ||
| } | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
| sanity_list.append(&mut sanity_list_in_other); | ||
| self.verify_block_list(&mut sanity_list); | ||
| } | ||
| } | ||
|
|
||
| /// Remove all blocks | ||
| fn reset(&mut self) { | ||
| trace!("Blocklist {:?}: Reset", self as *const _); | ||
| self.first = None; | ||
| self.last = None; | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
| sanity_list.clear(); | ||
| } | ||
| } | ||
|
|
||
| /// Lock the list. The MiMalloc allocator mostly uses thread-local block lists, and those operations on the list | ||
|
|
@@ -152,10 +255,12 @@ impl BlockList { | |
| .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) | ||
| .is_ok(); | ||
| } | ||
| trace!("Blocklist {:?}: locked", self as *const _); | ||
| } | ||
|
|
||
| /// Unlock list. See the comments on the lock method. | ||
| pub fn unlock(&mut self) { | ||
| trace!("Blocklist {:?}: unlock", self as *const _); | ||
| self.lock.store(false, Ordering::SeqCst); | ||
| } | ||
|
|
||
|
|
@@ -172,6 +277,25 @@ impl BlockList { | |
| } | ||
| } | ||
| } | ||
|
|
||
| /// Get the size of this block list. | ||
| #[allow(clippy::let_and_return)] | ||
| pub fn size(&self) -> usize { | ||
| let ret = self.size; | ||
|
|
||
| #[cfg(feature = "ms_block_list_sanity")] | ||
| { | ||
| let mut sanity_list = self.sanity_list.lock().unwrap(); | ||
| self.verify_block_list(&mut sanity_list); | ||
| } | ||
|
|
||
| ret | ||
| } | ||
|
|
||
| /// Get the first block in the list. | ||
| pub fn first(&self) -> Option<Block> { | ||
| self.first | ||
| } | ||
| } | ||
|
|
||
| pub struct BlockListIterator { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this method is not thread-safe, we can usetry_lock()instead oflock(), assert thattry_lock()always succeeds. If the assertion fails, it means there is a contention on the lock, and the only case that would happen is that two threads attempted to call thread-unsafe methods concurrently.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the method
is_emptyitself can be called from multiple threads concurrently as long as there isn't another thread calling other methods that mutate this BlockList. In theory, Rust's ownership model and borrow checker can prevent that from happening because it disallows a&to coexist with&mut.