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 | 11 | use crate::{ |
| 12 | − cache::delta::{traverse::util::ItemSliceSend, Item, Tree}, | |
| 12 | + cache::delta::{traverse::util::ItemSliceSync, Item, Tree}, | |
| 13 | 13 | data::EntryRange, |
| 14 | 14 | }; |
| 15 | 15 | |
| @@ -133,25 +133,23 @@ where · 117 unmodified lines | ||
| 133 | 133 | let object_progress = OwnShared::new(Mutable::new(object_progress)); |
| 134 | 134 | |
| 135 | 135 | let start = std::time::Instant::now(); |
| 136 | + let child_items = ItemSliceSync::new(&mut self.child_items); | |
| 137 | + let child_items = &child_items; | |
| 136 | 138 | in_parallel_with_slice( |
| 137 | 139 | &mut self.root_items, |
| 138 | 140 | thread_limit, |
| 139 | 141 | { |
| 140 | − let child_items = ItemSliceSend::new(&mut self.child_items); | |
| 141 | 142 | { |
| 142 | 143 | let object_progress = object_progress.clone(); |
| 143 | − move |thread_index| { | |
| 144 | − let _ = &child_items; | |
| 145 | − resolve::State { | |
| 146 | − delta_bytes: Vec::<u8>::with_capacity(4096), | |
| 147 | − fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096), | |
| 148 | − progress: Box::new( | |
| 149 | − threading::lock(&object_progress).add_child(format!("thread {thread_index}")), | |
| 150 | − ), | |
| 151 | − resolve: resolve.clone(), | |
| 152 | − modify_base: inspect_object.clone(), | |
| 153 | − child_items: child_items.clone(), | |
| 154 | − } | |
| 144 | + move |thread_index| resolve::State { | |
| 145 | + delta_bytes: Vec::<u8>::with_capacity(4096), | |
| 146 | + fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096), | |
| 147 | + progress: Box::new( | |
| 148 | + threading::lock(&object_progress).add_child(format!("thread {thread_index}")), | |
| 149 | + ), | |
| 150 | + resolve: resolve.clone(), | |
| 151 | + modify_base: inspect_object.clone(), | |
| 152 | + child_items, | |
| 155 | 153 | } |
| 156 | 154 | } |
| 157 | 155 | }, |
| @@ -7,7 +7,7 @@ use gix_features::{progress::Progress, threading, zlib}; · 6 unmodified lines | ||
| 7 | 7 | |
| 8 | 8 | use crate::{ |
| 9 | 9 | cache::delta::{ |
| 10 | − traverse::{util::ItemSliceSend, Context, Error}, | |
| 10 | + traverse::{util::ItemSliceSync, Context, Error}, | |
| 11 | 11 | Item, |
| 12 | 12 | }, |
| 13 | 13 | data, |
| @@ -17,7 +17,7 @@ use crate::{ · 3 unmodified lines | ||
| 17 | 17 | /// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`]. |
| 18 | 18 | struct Node<'a, T: Send> { |
| 19 | 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 | 23 | impl<'a, T: Send> Node<'a, T> { |
| @@ -51,7 +51,7 @@ impl<'a, T: Send> Node<'a, T> { · 27 unmodified lines | ||
| 51 | 51 | #[allow(unsafe_code)] |
| 52 | 52 | self.item.children.iter().map(move |&index| Node { |
| 53 | 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 | 62 | pub progress: Box<dyn Progress>, |
| 63 | 63 | pub resolve: F, |
| 64 | 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 | 68 | #[allow(clippy::too_many_arguments)] |
| @@ -106,13 +106,7 @@ where · 37 unmodified lines | ||
| 106 | 106 | // each node is a base, and its children always start out as deltas which become a base after applying them. |
| 107 | 107 | // These will be pushed onto our stack until all are processed |
| 108 | 108 | let root_level = 0; |
| 109 | − let mut nodes: Vec<_> = vec![( | |
| 110 | − root_level, | |
| 111 | − Node { | |
| 112 | − item, | |
| 113 | − child_items: child_items.clone(), | |
| 114 | − }, | |
| 115 | − )]; | |
| 109 | + let mut nodes: Vec<_> = vec![(root_level, Node { item, child_items })]; | |
| 116 | 110 | while let Some((level, mut base)) = nodes.pop() { |
| 117 | 111 | if should_interrupt.load(Ordering::Relaxed) { |
| 118 | 112 | return Err(Error::Interrupted); |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | use std::marker::PhantomData; |
| 2 | 2 | |
| 3 | −pub(crate) struct ItemSliceSend<'a, T> | |
| 3 | +pub(crate) struct ItemSliceSync<'a, T> | |
| 4 | 4 | where |
| 5 | 5 | T: Send, |
| 6 | 6 | { |
| @@ -8,12 +8,12 @@ where · 1 unmodified line | ||
| 8 | 8 | phantom: PhantomData<&'a T>, |
| 9 | 9 | } |
| 10 | 10 | |
| 11 | −impl<'a, T> ItemSliceSend<'a, T> | |
| 11 | +impl<'a, T> ItemSliceSync<'a, T> | |
| 12 | 12 | where |
| 13 | 13 | T: Send, |
| 14 | 14 | { |
| 15 | 15 | pub fn new(items: &'a mut [T]) -> Self { |
| 16 | − ItemSliceSend { | |
| 16 | + ItemSliceSync { | |
| 17 | 17 | items: items.as_mut_ptr(), |
| 18 | 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 | |
| 31 | −/// don't violate aliasing rules. | |
| 32 | −impl<T> Clone for ItemSliceSend<'_, T> | |
| 33 | −where | |
| 34 | − T: Send, | |
| 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. | |
| 30 | +// SAFETY: T is `Send`, and we only use the pointer for creating new pointers. | |
| 31 | +#[allow(unsafe_code)] | |
| 32 | +unsafe impl<T> Send for ItemSliceSync<'_, T> where T: Send {} | |
| 33 | +// SAFETY: T is `Send`, and as long as the user follows the contract of | |
| 34 | +// `get_mut()`, we only ever access one T at a time. | |
| 45 | 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 {} | |