Skip to content

Commit 43bca11

Browse files
committed
remove Clone impl of ItemSliceSend and make it Sync instead
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`.
1 parent 7301ca1 commit 43bca11

File tree

3 files changed

+26
-44
lines changed

3 files changed

+26
-44
lines changed

gix-pack/src/cache/delta/traverse/mod.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use gix_features::{
99
};
1010

1111
use crate::{
12-
cache::delta::{traverse::util::ItemSliceSend, Item, Tree},
12+
cache::delta::{traverse::util::ItemSliceSync, Item, Tree},
1313
data::EntryRange,
1414
};
1515

@@ -133,25 +133,23 @@ where
133133
let object_progress = OwnShared::new(Mutable::new(object_progress));
134134

135135
let start = std::time::Instant::now();
136+
let child_items = ItemSliceSync::new(&mut self.child_items);
137+
let child_items = &child_items;
136138
in_parallel_with_slice(
137139
&mut self.root_items,
138140
thread_limit,
139141
{
140-
let child_items = ItemSliceSend::new(&mut self.child_items);
141142
{
142143
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,
155153
}
156154
}
157155
},

gix-pack/src/cache/delta/traverse/resolve.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use gix_features::{progress::Progress, threading, zlib};
77

88
use crate::{
99
cache::delta::{
10-
traverse::{util::ItemSliceSend, Context, Error},
10+
traverse::{util::ItemSliceSync, Context, Error},
1111
Item,
1212
},
1313
data,
@@ -17,7 +17,7 @@ use crate::{
1717
/// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`].
1818
struct Node<'a, T: Send> {
1919
item: &'a mut Item<T>,
20-
child_items: ItemSliceSend<'a, Item<T>>,
20+
child_items: &'a ItemSliceSync<'a, Item<T>>,
2121
}
2222

2323
impl<'a, T: Send> Node<'a, T> {
@@ -51,7 +51,7 @@ impl<'a, T: Send> Node<'a, T> {
5151
#[allow(unsafe_code)]
5252
self.item.children.iter().map(move |&index| Node {
5353
item: unsafe { children.get_mut(index as usize) },
54-
child_items: children.clone(),
54+
child_items: children,
5555
})
5656
}
5757
}
@@ -62,7 +62,7 @@ pub(crate) struct State<'items, F, MBFN, T: Send> {
6262
pub progress: Box<dyn Progress>,
6363
pub resolve: F,
6464
pub modify_base: MBFN,
65-
pub child_items: ItemSliceSend<'items, Item<T>>,
65+
pub child_items: &'items ItemSliceSync<'items, Item<T>>,
6666
}
6767

6868
#[allow(clippy::too_many_arguments)]
@@ -106,13 +106,7 @@ where
106106
// each node is a base, and its children always start out as deltas which become a base after applying them.
107107
// These will be pushed onto our stack until all are processed
108108
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 })];
116110
while let Some((level, mut base)) = nodes.pop() {
117111
if should_interrupt.load(Ordering::Relaxed) {
118112
return Err(Error::Interrupted);
Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
use std::marker::PhantomData;
22

3-
pub(crate) struct ItemSliceSend<'a, T>
3+
pub(crate) struct ItemSliceSync<'a, T>
44
where
55
T: Send,
66
{
77
items: *mut T,
88
phantom: PhantomData<&'a T>,
99
}
1010

11-
impl<'a, T> ItemSliceSend<'a, T>
11+
impl<'a, T> ItemSliceSync<'a, T>
1212
where
1313
T: Send,
1414
{
1515
pub fn new(items: &'a mut [T]) -> Self {
16-
ItemSliceSend {
16+
ItemSliceSync {
1717
items: items.as_mut_ptr(),
1818
phantom: PhantomData,
1919
}
@@ -27,20 +27,10 @@ where
2727
}
2828
}
2929

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.
4535
#[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 {}

0 commit comments

Comments
 (0)