The `Clone` impl allows two `ItemSliceSend` to be backed by the same slice. That makes the behavior of `get_mut()` harder to reason about because we have to consider clones of the `ItemSliceSend` to determine if it's safe. By making it `Sync`, we make it more obvious that multiple threads referring to the same instance need to be considered. I renamed the type to `ItemSliceSync` since it's now also `Sync`. We could make the type also check the bounds and that an individual element has not been borrowed more than once. Then we could make `get_mut()` not `unsafe`.
Unified · Split
| @@ -9,7 +9,7 @@ use gix_features::{ · 8 unmodified lines | |||
| 9 | }; | 9 | }; |
| 10 | 10 | ||
| 11 | use crate::{ | 11 | use crate::{ |
| 12 | cache::delta::{traverse::util::ItemSliceSend, Item, Tree}, | 12 | cache::delta::{traverse::util::ItemSliceSync, Item, Tree}, |
| 13 | data::EntryRange, | 13 | data::EntryRange, |
| 14 | }; | 14 | }; |
| 15 | 15 | ||
| @@ -133,25 +133,23 @@ where · 117 unmodified lines | |||
| 133 | let object_progress = OwnShared::new(Mutable::new(object_progress)); | 133 | let object_progress = OwnShared::new(Mutable::new(object_progress)); |
| 134 | 134 | ||
| 135 | let start = std::time::Instant::now(); | 135 | let start = std::time::Instant::now(); |
| 136 | let child_items = ItemSliceSync::new(&mut self.child_items); | ||
| 137 | let child_items = &child_items; | ||
| 136 | in_parallel_with_slice( | 138 | in_parallel_with_slice( |
| 137 | &mut self.root_items, | 139 | &mut self.root_items, |
| 138 | thread_limit, | 140 | thread_limit, |
| 139 | { | 141 | { |
| 140 | let child_items = ItemSliceSend::new(&mut self.child_items); | ||
| 141 | { | 142 | { |
| 142 | let object_progress = object_progress.clone(); | 143 | let object_progress = object_progress.clone(); |
| 143 | move |thread_index| { | 144 | move |thread_index| resolve::State { |
| 144 | let _ = &child_items; | 145 | delta_bytes: Vec::<u8>::with_capacity(4096), |
| 145 | resolve::State { | 146 | fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096), |
| 146 | delta_bytes: Vec::<u8>::with_capacity(4096), | 147 | progress: Box::new( |
| 147 | fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096), | 148 | threading::lock(&object_progress).add_child(format!("thread {thread_index}")), |
| 148 | progress: Box::new( | 149 | ), |
| 149 | threading::lock(&object_progress).add_child(format!("thread {thread_index}")), | 150 | resolve: resolve.clone(), |
| 150 | ), | 151 | modify_base: inspect_object.clone(), |
| 151 | resolve: resolve.clone(), | 152 | child_items, |
| 152 | modify_base: inspect_object.clone(), | ||
| 153 | child_items: child_items.clone(), | ||
| 154 | } | ||
| 155 | } | 153 | } |
| 156 | } | 154 | } |
| 157 | }, | 155 | }, |
| @@ -7,7 +7,7 @@ use gix_features::{progress::Progress, threading, zlib}; · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | use crate::{ | 8 | use crate::{ |
| 9 | cache::delta::{ | 9 | cache::delta::{ |
| 10 | traverse::{util::ItemSliceSend, Context, Error}, | 10 | traverse::{util::ItemSliceSync, Context, Error}, |
| 11 | Item, | 11 | Item, |
| 12 | }, | 12 | }, |
| 13 | data, | 13 | data, |
| @@ -17,7 +17,7 @@ use crate::{ · 3 unmodified lines | |||
| 17 | /// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`]. | 17 | /// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`]. |
| 18 | struct Node<'a, T: Send> { | 18 | struct Node<'a, T: Send> { |
| 19 | item: &'a mut Item<T>, | 19 | item: &'a mut Item<T>, |
| 20 | child_items: ItemSliceSend<'a, Item<T>>, | 20 | child_items: &'a ItemSliceSync<'a, Item<T>>, |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | impl<'a, T: Send> Node<'a, T> { | 23 | impl<'a, T: Send> Node<'a, T> { |
| @@ -51,7 +51,7 @@ impl<'a, T: Send> Node<'a, T> { · 27 unmodified lines | |||
| 51 | #[allow(unsafe_code)] | 51 | #[allow(unsafe_code)] |
| 52 | self.item.children.iter().map(move |&index| Node { | 52 | self.item.children.iter().map(move |&index| Node { |
| 53 | item: unsafe { children.get_mut(index as usize) }, | 53 | item: unsafe { children.get_mut(index as usize) }, |
| 54 | child_items: children.clone(), | 54 | child_items: children, |
| 55 | }) | 55 | }) |
| 56 | } | 56 | } |
| 57 | } | 57 | } |
| @@ -62,7 +62,7 @@ pub(crate) struct State<'items, F, MBFN, T: Send> { · 4 unmodified lines | |||
| 62 | pub progress: Box<dyn Progress>, | 62 | pub progress: Box<dyn Progress>, |
| 63 | pub resolve: F, | 63 | pub resolve: F, |
| 64 | pub modify_base: MBFN, | 64 | pub modify_base: MBFN, |
| 65 | pub child_items: ItemSliceSend<'items, Item<T>>, | 65 | pub child_items: &'items ItemSliceSync<'items, Item<T>>, |
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | #[allow(clippy::too_many_arguments)] | 68 | #[allow(clippy::too_many_arguments)] |
| @@ -106,13 +106,7 @@ where · 37 unmodified lines | |||
| 106 | // each node is a base, and its children always start out as deltas which become a base after applying them. | 106 | // each node is a base, and its children always start out as deltas which become a base after applying them. |
| 107 | // These will be pushed onto our stack until all are processed | 107 | // These will be pushed onto our stack until all are processed |
| 108 | let root_level = 0; | 108 | let root_level = 0; |
| 109 | let mut nodes: Vec<_> = vec![( | 109 | let mut nodes: Vec<_> = vec![(root_level, Node { item, child_items })]; |
| 110 | root_level, | ||
| 111 | Node { | ||
| 112 | item, | ||
| 113 | child_items: child_items.clone(), | ||
| 114 | }, | ||
| 115 | )]; | ||
| 116 | while let Some((level, mut base)) = nodes.pop() { | 110 | while let Some((level, mut base)) = nodes.pop() { |
| 117 | if should_interrupt.load(Ordering::Relaxed) { | 111 | if should_interrupt.load(Ordering::Relaxed) { |
| 118 | return Err(Error::Interrupted); | 112 | return Err(Error::Interrupted); |
| @@ -1,6 +1,6 @@ | |||
| 1 | use std::marker::PhantomData; | 1 | use std::marker::PhantomData; |
| 2 | 2 | ||
| 3 | pub(crate) struct ItemSliceSend<'a, T> | 3 | pub(crate) struct ItemSliceSync<'a, T> |
| 4 | where | 4 | where |
| 5 | T: Send, | 5 | T: Send, |
| 6 | { | 6 | { |
| @@ -8,12 +8,12 @@ where · 1 unmodified line | |||
| 8 | phantom: PhantomData<&'a T>, | 8 | phantom: PhantomData<&'a T>, |
| 9 | } | 9 | } |
| 10 | 10 | ||
| 11 | impl<'a, T> ItemSliceSend<'a, T> | 11 | impl<'a, T> ItemSliceSync<'a, T> |
| 12 | where | 12 | where |
| 13 | T: Send, | 13 | T: Send, |
| 14 | { | 14 | { |
| 15 | pub fn new(items: &'a mut [T]) -> Self { | 15 | pub fn new(items: &'a mut [T]) -> Self { |
| 16 | ItemSliceSend { | 16 | ItemSliceSync { |
| 17 | items: items.as_mut_ptr(), | 17 | items: items.as_mut_ptr(), |
| 18 | phantom: PhantomData, | 18 | phantom: PhantomData, |
| 19 | } | 19 | } |
| @@ -27,20 +27,10 @@ where · 7 unmodified lines | |||
| 27 | } | 27 | } |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | /// SAFETY: This would be unsafe if this would ever be abused, but it's used internally and only in a way that assure that the pointers | 30 | // SAFETY: T is `Send`, and we only use the pointer for creating new pointers. |
| 31 | /// don't violate aliasing rules. | 31 | #[allow(unsafe_code)] |
| 32 | impl<T> Clone for ItemSliceSend<'_, T> | 32 | unsafe impl<T> Send for ItemSliceSync<'_, T> where T: Send {} |
| 33 | where | 33 | // SAFETY: T is `Send`, and as long as the user follows the contract of |
| 34 | T: Send, | 34 | // `get_mut()`, we only ever access one T at a time. |
| 35 | { | ||
| 36 | fn clone(&self) -> Self { | ||
| 37 | ItemSliceSend { | ||
| 38 | items: self.items, | ||
| 39 | phantom: self.phantom, | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | // SAFETY: T is `Send`, and we only ever access one T at a time. And, ptrs need that assurance, I wonder if it's always right. | ||
| 45 | #[allow(unsafe_code)] | 35 | #[allow(unsafe_code)] |
| 46 | unsafe impl<T> Send for ItemSliceSend<'_, T> where T: Send {} | 36 | unsafe impl<T> Sync for ItemSliceSync<'_, T> where T: Send {} |