Missing contexts were previously done on each individual hunk, which could be highly inefficient in some cases. The complexity was in O(p·h), where p is the size of the patch and h is the size of history, in the worst case.
This patch fixes this by doing all the context repairs in a single pass at the end of the apply function, which now has complexity O(h). Since the previous solution was to use caches, these are no longer necessary and the memory usage has also improved.
OXZVZDQZEVP7NV3HS6HK5QA7RUD35ODVQ3LL7PWJHTS7DEFM3XTAC
3KHT2M5ZWBSXYFWZFL2Q5S4YFZKYFKOI5MAJT7WAIL5WPQ2YLLOAC
L7S3MNQ4GHK7AHQETWWQJ2TXEBQKDYRRAROI3WVWT2IK5DK2SKXAC
L4EZSH6BBU46PVEG2HRPKIMIX7HUXUUEX5PGYYW2I3GXWKSFZAHAC
XNY6VDZSCNHSAS53HRVBT5AO7HBMAWPOQX3ORCYPWVWRKA4EXMFQC
PNJL5TPZLQ3VXAASTLUX7462RCRPO7TV3GKOTTHDZABDQCBMXPRQC
X6EUOQ5OZT6MKEAQG4GXYQBNSGU7KAE5CHX443E6PVP7IIOPQS6AC
T7YIRFWDA75AC2JSWVVAIDK6J5ICOGYHTGZNGLTJU6SIGXKUGCLQC
PNKAJTFZM37FD6XPC3HLVBDT54XDC3SQMFNM32N6TTZ72BVFUKWQC
SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC
X243Z3Y54ULINQMMRIKLHRV5T237B7VUOAHVJ7DMPOQ6A6GQXY2AC
I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC
7NSTS6PKVQWNUGIUYBRS4GWHJPAV57TOPBTDCOBZ5YYQNFZIZW2QC
VO5OQW4W2656DIYYRNZ3PO7TQ4JOKQ3GVWE5ALUTYVMX3WMXJOYQC
I24UEJQLCH2SOXA4UHIYWTRDCHSOPU7AFTRUOTX7HZIAV4AZKYEQC
RSFUX6MLPHII3DRELHN62DOJCHFCDNKSRMWJDSFGTWN5S4RJVSSQC
CCLLB7OIFNFYJZTG3UCI7536TOCWSCSXR67VELSB466R24WLJSDAC
AD6M434OFUCH6ISHP7GXSNNRBHEWP242ZDH7QCVCYLHMRASGE5MQC
RRCSHAYZ6RLWVPNYHF2F5FSRL2KKJNPQUQRIJYLJGB23BZQQ7JLQC
YN63NUZO4LVJ7XPMURDULTXBVJKW5MVCTZ24R7Z52QMHO3HPDUVQC
JQR4Q2NKNPVEWUWDTV55HXSDVYVYFEO5V4OG3XNAXC4HD6OVOH7QC
GHO6DWPILBBTL6CVZKERJBTFL3EY6ZT4YM4E5R4S6YPGVFKFHCVAC
FABI77LLTZYJAH4YMQ4Y6LXGTHPQ52FSA4DNZ5HHYRU2PXY5WDIAC
3I4PAA2AW3VUTA3HLS2G4TQMWB7BO25DMCC7VWJHG6WMHCBHR6JAC
6YMDOZIB5LVYLFIDGN2WNT5JTHEAMS4TFPVDEZ3OWXWOKJOC5QDAC
SFQBWL6PHSX7MQVOSQW4ZO35SLXNHN6562IWYCN4UKWAEVE6AQNQC
MQ6ERQ43MWASF2OSG65FHKWHCWT35BJVFJWKHAFPVCTLICCTJWUAC
WZVCLZKY34KQBQU6YBGJLQCDADBQ67LQVDNRVCMQVY3O3C3EIWSQC
T7CAACFBDKOZEBM7XBX7HMCDM66RWEYKLOJY6JNIGWOFKV2HQEBQC
3CFU4DQNHPPJC2B63RSVAVGHTQZT3RT5CCHHYC4ZM6DQ4GVQULSQC
ZDK3GNDBWXJ2OXFDYB72ZCEBGLBF4MKE5K3PVHDZATHJ7HJIDPRQC
NMX52UOGRCY2O7HT7Q45KWISOHNV4PEEMLDYDBJ4QPDIMTVKKJ6AC
X7OHUPL5VYT6ECER2KNGRNFLRX7SBZOM5QWSQ4PBO2UPIE7XM6MAC
ZCPGCKKYDBXUTIYGKGRA7C43CJ2WNWH3L6Q7ETSVLYB6AE4HAGTQC
JACZYXK43IU5HWLHQBY3BOAE7CUO6NGEXQ3HSZL3CQP3SLGKMBOAC
CD6XDYOHYK6FZDJLIE3AEC5U3YXT3O57VECRDF3KVDK3RHYYS7IQC
ATZ3BWSEFJBLVGDUZFESRNHVCIO6ZRZ3ALPANZSVGVO7A5BUAFQQC
UEWNF7X3S5V6N2LEJCFMZKFZZHQUCAF6FZF5DYO5XATNDEQ4G35QC
FAOGX7G362OSLMKTQLQ3S3XMGKACNRAIV2VRURS5QJRHDF577BHQC
2BKYJ2JM5PTXWO6HTVBKFQANWWSCJ4UHJYKAXWGTVZB35AZJ76CQC
HMMMKONLCRAXVT7SO2ITTFDOJIQKKVSRIZPXYVPDC34RCBHWMHVAC
use crate::pristine::*;
use crate::{HashMap, HashSet};
// use std::collections::hash_map::Entry;
/// The following is an unrolled DFS, where each alive vertex is
/// inserted into each "alive set" along the current path (which is
/// recognised by looking at the visited vertices on the stack).
pub(crate) fn find_alive_down<'a, T: GraphTxnT>(
txn: &T,
channel: &T::Graph,
vertex0: Vertex<ChangeId>,
cache: &'a mut HashMap<Vertex<ChangeId>, Option<HashSet<Vertex<ChangeId>>>>,
) -> Result<&'a Option<HashSet<Vertex<ChangeId>>>, BlockError<T::GraphError>> {
let mut stack: Vec<(_, Option<HashSet<Vertex<ChangeId>>>)> = vec![(
SerializedEdge::empty(vertex0.start_pos(), ChangeId::ROOT),
None,
)];
let mut visited = HashSet::default();
while let Some((elt, alive)) = stack.pop() {
if let Some(alive) = alive {
// We've gone through all the descendants, put this in the
// cache.
let vertex = txn.find_block(&channel, elt.dest())?;
if stack.is_empty() {
// Done!
cache.insert(*vertex, Some(alive.clone()));
assert_eq!(vertex0.start_pos(), vertex.start_pos());
return Ok(cache.get(&vertex).unwrap());
}
continue;
}
let vertex = txn.find_block(&channel, elt.dest())?;
if let Some(c) = cache.get(vertex) {
for st in stack.iter_mut() {
if let Some(ref mut st) = st.1 {
if let Some(c) = c {
st.extend(c.iter().cloned());
} else {
st.insert(*vertex);
}
}
}
continue;
}
// A `None` in the cache means that the vertex
// itself (the cache key) is alive.
debug!("elt = {:?}, vertex = {:?}", elt, vertex);
let elt_index = stack.len();
for v in iter_adj_all(txn, &channel, *vertex)? {
let v = v?;
if v.flag().contains(EdgeFlags::FOLDER) {
continue;
}
debug!("v = {:?}", v);
if v.flag().contains(EdgeFlags::PARENT) {
if (v.flag().contains(EdgeFlags::BLOCK) || vertex.is_empty())
&& !v.flag().contains(EdgeFlags::DELETED)
&& !v.flag().contains(EdgeFlags::PSEUDO)
{
if *vertex == vertex0 {
// vertex0 is alive.
stack.truncate(elt_index);
let (_, alive) = stack.pop().unwrap();
let alive = alive.unwrap();
assert!(alive.is_empty());
cache.insert(vertex0, None);
return Ok(cache.get(&vertex0).unwrap());
} else {
// vertex is alive, insert it into all the
// alive sets on the current DFS path
// (including `vertex`).
for st in stack.iter_mut() {
if let Some(ref mut st) = st.1 {
st.insert(*vertex);
}
}
stack.truncate(elt_index);
break;
}
}
} else if v.flag().contains(EdgeFlags::DELETED) {
if !has_alive_blocks {
stack.push((*v, None))
}
}
}
}
unreachable!()
}
pub fn find_alive_up<'a, T: GraphTxnT>(
txn: &T,
channel: &T::Graph,
files: &mut HashSet<Vertex<ChangeId>>,
vertex0: Vertex<ChangeId>,
change: ChangeId,
cache: &'a mut HashMap<
Vertex<ChangeId>,
(Option<HashSet<Vertex<ChangeId>>>, HashSet<Vertex<ChangeId>>),
>,
) -> Result<&'a Option<HashSet<Vertex<ChangeId>>>, BlockError<T::GraphError>> {
debug!("find alive up: {:?}", vertex0);
let mut stack: Vec<(
_,
Option<(HashSet<Vertex<ChangeId>>, HashSet<Vertex<ChangeId>>)>,
)> = vec![(
SerializedEdge::empty(vertex0.end_pos(), ChangeId::ROOT),
None,
)];
let mut visited = HashSet::default();
while let Some((elt, alive)) = stack.pop() {
debug!("pop {:?} {:?}", elt, alive);
if elt.dest().is_root() {
continue;
}
if let Some((alive, files_)) = alive {
let vertex = *txn.find_block_end(&channel, elt.dest())?;
if stack.is_empty() {
// Done!
cache.insert(vertex, (Some(alive), files_));
assert_eq!(vertex.end_pos(), vertex0.end_pos());
return Ok(&cache.get(&vertex).unwrap().0);
}
continue;
}
let vertex = *txn.find_block_end(&channel, elt.dest())?;
debug!("vertex = {:?}", vertex);
if let Some((c, d)) = cache.get(&vertex) {
debug!("Cached: {:?} {:?}", c, d);
for st in stack.iter_mut() {
if let Some((ref mut al, ref mut f)) = st.1 {
if let Some(c) = c {
al.extend(c.iter().cloned());
} else {
al.insert(vertex);
}
f.extend(d.iter().cloned());
files.extend(d.iter().cloned());
}
}
continue;
}
stack.push((elt, Some((HashSet::new(), HashSet::new()))));
continue;
}
if !visited.insert(elt.dest()) {
}
if visited.insert(elt.dest()) {
stack.push((elt, Some((HashSet::new(), HashSet::new()))));
debug!("find_alive_up: elt = {:?}, vertex = {:?}", elt, vertex);
let elt_index = stack.len();
let mut is_file = false; // Is this the "inode" vertex of a file?
let mut it = iter_adj_all(txn, &channel, vertex)?;
while let Some(v) = it.next() {
let v = v?;
debug!("find_alive_up: v = {:?} change = {:?}", v, change);
if v.flag() & EdgeFlags::pseudof() == EdgeFlags::PSEUDO {
continue;
}
if !v.flag().is_parent() {
is_file |= !v.flag().is_folder();
continue;
}
if !v.flag().is_deleted() {
if v.flag().is_folder() {
for e in it {
let e = e?;
is_file |= !e.flag().intersects(EdgeFlags::parent_folder())
}
if is_file {
debug!("is alive + is file {:?}", vertex);
for st in stack.iter_mut() {
if let Some((ref mut al, ref mut fi)) = st.1 {
al.insert(vertex);
fi.insert(vertex);
}
}
files.insert(vertex);
}
break;
} else if v.flag().is_block() || vertex.is_empty() {
debug!("is alive {:?}", vertex);
for st in stack.iter_mut() {
if let Some((ref mut st, _)) = st.1 {
st.insert(vertex);
}
}
stack.truncate(elt_index);
break;
}
}
if v.flag().is_folder() {
if is_file {
debug!("is pseudo-alive folder {:?}", vertex);
for st in stack.iter_mut() {
if let Some((ref mut al, ref mut fi)) = st.1 {
al.insert(vertex);
fi.insert(vertex);
}
}
files.insert(vertex);
}
break;
} else {
stack.push((*v, None))
}
}
}
unreachable!()
}
debug!("is_file = {:?}", is_file);
debug!("just push");
for e in it {
let e = e?;
is_file |= !e.flag().intersects(EdgeFlags::parent_folder())
}
debug!("{:?} is a file ? {:?}", vertex, is_file);
if v.flag().is_block() && vertex == vertex0 {
// vertex0 is alive.
stack.truncate(elt_index);
let (_, alive) = stack.pop().unwrap();
let (alive, _) = alive.unwrap();
assert!(alive.is_empty());
cache.insert(vertex0, (None, HashSet::new()));
return Ok(&cache.get(&vertex0).unwrap().0);
}
debug!("is_file {:?} {:?}", is_file, !v.flag().is_folder());
debug!("stack = {:?}", stack);
// Each element of the stack is a pair of (1) an edge pointing to
// the position we want to visit next and (2) optionally, a set of
// alive vertices found until now.
// Every time we find an alive vertex, we add it to the sets of
// each element on the path to that vertex.
} else {
stack.push((*v, None));
} else if v.flag().contains(EdgeFlags::BLOCK) {
has_alive_blocks = true;
stack.push((*v, None));
let mut has_alive_blocks = false;
} else {
if !visited.insert(elt.dest()) {
continue;
}
stack.push((elt, Some(HashSet::new())));
repair_edges_context(
changes,
txn,
T::graph_mut(channel),
&mut ws.apply.missing_context,
change_id,
n,
)?
fn repair_newvertex_contexts<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
) -> Result<(), UnrecordError<C::Error, T>> {
debug!("up = {:#?}", ws.up);
for (up, inode) in ws.up.drain() {
if !is_alive(txn, channel, &up)? {
continue;
}
crate::missing_context::repair_missing_down_context(
txn,
channel,
&mut ws.apply.missing_context,
inode,
up,
&[up],
)?
}
debug!("down = {:#?}", ws.down);
for (down, inode) in ws.down.drain() {
if !is_alive(txn, channel, &down)? {
continue;
}
for parent in iter_adjacent(
txn,
channel,
down,
EdgeFlags::PARENT,
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let parent = parent?;
let parent = txn.find_block_end(channel, parent.dest())?;
if !is_alive(txn, channel, parent)? {
ws.parents.insert(*parent);
}
}
debug!("parents {:#?}", ws.parents);
for up in ws.parents.drain() {
crate::missing_context::repair_missing_up_context(
txn,
channel,
&mut ws.apply.missing_context,
change_id,
inode,
up,
&[down],
)?
}
}
Ok(())
}
fn repair_edges_context<T: GraphMutTxnT + TreeTxnT, P: ChangeStore>(
changes: &P,
txn: &mut T,
channel: &mut T::Graph,
ws: &mut crate::missing_context::Workspace,
change_id: ChangeId,
n: &EdgeMap<Option<Hash>>,
) -> Result<(), UnrecordError<P::Error, T>> {
debug!("repair_edges_context");
let change_hash: Hash = txn.get_external(&change_id)?.unwrap().into();
for e in n.edges.iter() {
debug!("repair_edges_context: {:?}", e);
assert!(!e.flag.contains(EdgeFlags::PARENT));
let intro = internal(txn, &e.introduced_by, change_id)?.unwrap();
if e.previous.contains(EdgeFlags::DELETED) {
repair_context_deleted(
txn,
channel,
ws,
n.inode,
intro,
|h| changes.knows(&change_hash, &h).unwrap(),
&e.reverse(Some(change_hash)),
)?
} else {
let to = internal_pos(txn, &e.to.start_pos(), change_id)?;
let to = txn.find_block(channel, to)?;
debug!("to = {:?}", to);
if !is_alive(txn, channel, to)? {
debug!("not alive");
continue;
}
repair_context_nondeleted(
txn,
channel,
ws,
n.inode,
intro,
&e.reverse(Some(change_hash)),
)?
}
}
Ok(())
}
}
}
}
pub(crate) fn repair_missing_up_context<
'a,
T: GraphMutTxnT,
I: IntoIterator<Item = &'a Vertex<ChangeId>>,
>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
inode: Position<Option<Hash>>,
c: Vertex<ChangeId>,
d: I,
) -> Result<(), MissingError<T::GraphError>> {
let now = std::time::Instant::now();
let alive = find_alive_up(
txn,
channel,
&mut ws.files,
c,
change_id,
&mut ws.alive_up_cache,
)?;
if let Some(alive) = alive {
let mut alive = alive.clone();
debug!("files = {:?}", ws.files);
crate::TIMERS.lock().unwrap().find_alive += now.elapsed();
ws.load_graph(txn, channel, inode)?;
debug!("repair_missing_up_context, alive = {:?}", alive);
for &d in d {
if let Some((graph, vids)) = ws.graphs.0.get(&inode) {
crate::alive::remove_redundant_parents(
graph,
vids,
&mut alive,
&mut ws.covered_parents,
d,
);
}
repair_regular_up(txn, channel, &alive, d, EdgeFlags::PSEUDO)?;
}
} else {
debug!("repair_missing_up_context: {:?} is alive", c);
}
Ok(())
}
fn repair_regular_up<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
alive: &HashSet<Vertex<ChangeId>>,
d: Vertex<ChangeId>,
flag: EdgeFlags,
) -> Result<(), TxnErr<T::GraphError>> {
for &ancestor in alive.iter() {
debug!("repair_regular_up {:?} → {:?}", ancestor, d);
if ancestor == d {
info!(
"repair_missing_up_context, alive: {:?} == {:?}",
ancestor, d
);
continue;
}
put_graph_with_rev(txn, channel, flag, ancestor, d, ChangeId::ROOT)?;
}
Ok(())
}
pub(crate) fn repair_missing_down_context<
'a,
T: GraphMutTxnT,
I: IntoIterator<Item = &'a Vertex<ChangeId>>,
>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
inode: Position<Option<Hash>>,
c: Vertex<ChangeId>,
d: I,
) -> Result<(), MissingError<T::GraphError>> {
debug!("repair_missing_down_context {:?}", c);
let now = std::time::Instant::now();
let alive = find_alive_down(txn, channel, c, &mut ws.alive_down_cache)?;
if let Some(alive) = alive {
let mut alive = alive.clone();
debug!("alive = {:?}", alive);
crate::TIMERS.lock().unwrap().find_alive += now.elapsed();
ws.load_graph(txn, channel, inode)?;
if let Some((graph, vids)) = ws.graphs.0.get(&inode) {
crate::alive::remove_redundant_children(graph, vids, &mut alive, c);
if !alive.is_empty() {
debug!("repair_missing_down_context alive = {:#?}", alive);
}
for &d in d {
for &desc in alive.iter() {
if d == desc {
info!("repair_missing_down_context, alive: {:?} == {:?}", d, desc);
continue;
}
debug!("repair_missing_down {:?} {:?}", d, desc);
put_graph_with_rev(txn, channel, EdgeFlags::PSEUDO, d, desc, ChangeId::ROOT)?;
}
}
} else {
debug!("repair_missing_down: {:?} is alive", c);
pub(crate) fn repair_context_nondeleted<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
inode: Position<Option<Hash>>,
pub(crate) fn has_missing_context_nondeleted<T: GraphMutTxnT>(
txn: &T,
channel: &T::Graph,
repair_missing_up_context(txn, channel, ws, change_id, inode, source, &[target])?;
reconnect_target_up(txn, channel, ws, inode, target, change_id)?;
if e.flag.contains(EdgeFlags::BLOCK) {
let mut missing_down = std::mem::replace(&mut ws.missing_down, Vec::new());
missing_down.clear();
for e in iter_adjacent(
if is_alive(txn, channel, &source)? && e.flag.contains(EdgeFlags::BLOCK) {
Ok(iter_adjacent(
)? {
let e = e?;
ws.missing_down.push(*txn.find_block(&channel, e.dest())?)
}
repair_missing_down_context(txn, channel, ws, inode, target, &missing_down)?;
ws.missing_down = missing_down;
} else if is_alive(txn, channel, &source)? {
repair_missing_down_context(txn, channel, ws, inode, target, &[source])?;
}
Ok(())
}
fn reconnect_target_up<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
inode: Position<Option<Hash>>,
target: Vertex<ChangeId>,
change_id: ChangeId,
) -> Result<(), MissingError<T::GraphError>> {
let mut unknown = HashSet::default();
for v in iter_deleted_parents(txn, channel, target)? {
let v = v?;
if v.dest().change.is_root() || v.introduced_by().is_root() {
continue;
}
if v.introduced_by() == change_id {
unknown.clear();
break;
}
// Else change ~v.introduced_by~ is a change we don't know,
// since no change can create a conflict with itself.
unknown.insert(*txn.find_block_end(channel, v.dest())?);
}
for up in unknown.drain() {
repair_missing_up_context(txn, channel, ws, change_id, inode, up, &[target])?;
)?
.next()
.is_some())
} else {
Ok(true)
pub(crate) fn repair_context_deleted<T: GraphMutTxnT, K>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
inode: Position<Option<Hash>>,
pub(crate) fn has_missing_context_deleted<T: GraphMutTxnT, K>(
txn: &T,
channel: &T::Graph,
ws.unknown.push(*v);
}
}
}
Ok(())
}
fn repair_children_of_deleted<T: GraphMutTxnT, K>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
inode: Position<Option<Hash>>,
mut known: K,
change_id: ChangeId,
dest_vertex: Vertex<ChangeId>,
) -> Result<(), MissingError<T::GraphError>>
where
K: FnMut(Hash) -> bool,
{
trace!("repair_children_of_deleted {:?}", dest_vertex);
collect_unknown_children(txn, channel, ws, dest_vertex, change_id, &mut known)?;
let mut unknown = std::mem::replace(&mut ws.unknown, Vec::new());
debug!("dest_vertex = {:?}, unknown = {:?}", dest_vertex, unknown);
for edge in unknown.drain(..) {
let p = *txn.find_block(channel, edge.dest())?;
assert!(!edge.flag().contains(EdgeFlags::FOLDER));
debug!("dest_vertex {:?}, p {:?}", dest_vertex, p);
put_graph_with_rev(txn, channel, EdgeFlags::db(), dest_vertex, p, change_id)?;
let mut u = p;
while let Ok(&v) = txn.find_block(channel, u.end_pos()) {
if u != v {
debug!("repair_children_of_deleted: {:?} -> {:?}", u, v);
put_graph_with_rev(txn, channel, EdgeFlags::db(), u, v, change_id)?;
u = v
} else {
break;
return Ok(true);
if is_alive(txn, channel, &p)? {
repair_missing_up_context(txn, channel, ws, change_id, inode, dest_vertex, &[p])?;
} else {
let alive = find_alive_down(txn, channel, p, &mut ws.alive_down_cache)?.clone();
repair_missing_up_context(
txn,
channel,
ws,
change_id,
inode,
dest_vertex,
alive.iter().flat_map(|t| t.iter()),
)?;
}
pub(crate) fn delete_pseudo_edges<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
) -> Result<(), MissingError<T::GraphError>> {
if ws.pseudo.is_empty() {
debug!("no pseudo edges")
}
for (dest_vertex, mut e) in ws.pseudo.drain(..) {
debug!("repair_context_deleted, deleting {:?} {:?}", dest_vertex, e);
if !is_alive(txn, channel, &dest_vertex)? && !ws.repaired.contains(&dest_vertex) {
if e.flag().contains(EdgeFlags::PARENT) {
let p = *txn.find_block_end(channel, e.dest())?;
if !is_alive(txn, channel, &p)? {
debug!("delete {:?} {:?}", p, dest_vertex);
e -= EdgeFlags::PARENT;
del_graph_with_rev(txn, channel, e.flag(), p, dest_vertex, e.introduced_by())?;
}
} else {
let p = *txn.find_block(channel, e.dest())?;
if !is_alive(txn, channel, &p)? {
debug!("delete (2) {:?} {:?}", dest_vertex, p);
del_graph_with_rev(txn, channel, e.flag(), dest_vertex, p, e.introduced_by())?;
}
}
}
}
Ok(())
}
pub(crate) fn repair_parents_of_deleted<T: GraphMutTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
) -> Result<(), MissingError<T::GraphError>> {
debug!("repair_parents_of_deleted");
let mut unknown = std::mem::replace(&mut ws.unknown_parents, Vec::new());
for (dest_vertex, p, inode, flag) in unknown.drain(..) {
if flag.contains(EdgeFlags::FOLDER) {
repair_missing_down_context(txn, channel, ws, inode, dest_vertex, &[dest_vertex])?
} else {
repair_missing_down_context(txn, channel, ws, inode, dest_vertex, &[p])?
}
}
ws.unknown_parents = unknown;
Ok(())
}
{
let file = Vertex {
change: ChangeId(L64(5867590239053715538)),
start: ChangePosition(204370u64.into()),
end: ChangePosition(204370u64.into()),
};
let still_here: Vec<_> = iter_adjacent(
txn,
T::graph_mut(channel),
file,
EdgeFlags::empty(),
EdgeFlags::all(),
)?.collect();
debug!("still here! {:?}", still_here);
}
clean_obsolete_pseudo_edges(txn, T::graph_mut(channel), ws, change_id)?;
let mut inodes = clean_obsolete_pseudo_edges(txn, T::graph_mut(channel), ws, change_id)?;
collect_missing_contexts(txn, txn.graph(channel), ws, &change, change_id, &mut inodes)?;
for i in inodes {
repair_zombies(txn, T::graph_mut(channel), i)?;
}
pub(crate) fn clean_obsolete_pseudo_edges<T: GraphMutTxnT + TreeTxnT>(
while let Some((current, alive, on_path)) = stack.pop() {
stack.push((current, alive, true));
let is_first_visit = visited.insert(current);
let len = stack.len();
if !on_path {
// If this is the first visit, find the children, starting
// with in flag order (alive first), since we don't want
// to reconnect vertices multiple times.
for e in iter_adjacent(txn, channel, current, EdgeFlags::empty(), EdgeFlags::all())? {
let e = e?;
if e.flag().contains(EdgeFlags::PARENT) {
if e.flag() & (EdgeFlags::BLOCK | EdgeFlags::DELETED) == EdgeFlags::BLOCK {
// This vertex is alive!
stack[len - 1].1 = true;
}
continue;
}
if is_first_visit {
let child = txn.find_block(channel, e.dest())?;
stack.push((*child, false, false));
}
}
}
if len >= 2 && stack[len - 1].1 {
if let Some(last_on_path) = (&stack[..len - 1]).iter().rev().position(|x| x.2) {
let last_on_path = len - 2 - last_on_path;
// If the last vertex on the path to `current` is not
// alive, a reconnect is needed.
if !stack[last_on_path].1 {
if let Some(last_alive_on_path) =
(&stack[..last_on_path]).iter().rev().position(|x| x.1)
{
let last_alive_on_path = last_on_path - 1 - last_alive_on_path;
debug!("repair zombies {:?} {:?}", last_alive_on_path, len - 1);
// We need to reconnect, and we can do it now
// since we won't have a chance to visit that
// edge (because non-PARENT edge we are
// inserting now starts from a vertex that is
// on the path, which means we've already
// pushed all its children onto the stack.).
put_graph_with_rev(
txn,
channel,
EdgeFlags::PSEUDO,
stack[last_alive_on_path].0,
stack[len - 1].0,
ChangeId::ROOT,
)?;
}
}
}
}
// If no children, pop.
if stack.len() == len {
stack.pop();
}
}
Ok(())
}
pub fn clean_obsolete_pseudo_edges<T: GraphMutTxnT + TreeTxnT>(
if a_is_alive {
debug!("repair down");
debug_assert!(!b_is_alive);
crate::missing_context::repair_missing_down_context(
txn,
channel,
&mut ws.missing_context,
inode,
b,
&[a],
)
.map_err(LocalApplyError::from_missing)?
} else if b_is_alive && !p.flag().is_folder() {
debug!("repair up");
// Note: if this is a folder edge,
// repair_missing_up_context will stop immediately, so we
// don't even need to call it.
crate::missing_context::repair_missing_up_context(
txn,
channel,
&mut ws.missing_context,
change_id,
inode,
a,
&[b],
)
.map_err(LocalApplyError::from_missing)?
if a_is_alive || (b_is_alive && !p.flag().is_folder()) {
// A context repair is needed.
inodes.insert(internal_pos(txn, &inode, change_id)?);
fn repair_missing_contexts<T: GraphMutTxnT + TreeTxnT>(
txn: &mut T,
channel: &mut T::Graph,
fn collect_missing_contexts<T: GraphMutTxnT + TreeTxnT>(
txn: &T,
channel: &T::Graph,
let now = std::time::Instant::now();
crate::missing_context::repair_parents_of_deleted(txn, channel, &mut ws.missing_context)
.map_err(LocalApplyError::from_missing)?;
inodes.extend(
ws.missing_context
.unknown_parents
.drain(..)
.map(|x| internal_pos(txn, &x.2, change_id).unwrap()),
);
let vertex = Vertex {
change: change_id,
start: n.start,
end: n.end,
};
repair_new_vertex_context_up(txn, channel, ws, change_id, n, vertex)?;
repair_new_vertex_context_down(txn, channel, ws, change_id, n, vertex)?;
let inode = internal_pos(txn, &n.inode, change_id)?;
if !inodes.contains(&inode) {
for up in n.up_context.iter() {
let up =
*txn.find_block_end(channel, internal_pos(txn, &up, change_id)?)?;
if !is_alive(txn, channel, &up)? {
inodes.insert(inode);
break;
}
}
for down in n.down_context.iter() {
let down =
*txn.find_block(channel, internal_pos(txn, &down, change_id)?)?;
let mut down_has_other_parents = false;
for e in iter_adjacent(
txn,
channel,
down,
EdgeFlags::PARENT,
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let e = e?;
if e.introduced_by() != change_id {
down_has_other_parents = true;
break;
}
}
if !down_has_other_parents {
inodes.insert(inode);
break;
}
}
}
repair_edge_context(txn, channel, ws, change_id, change, n)?;
}
}
}
crate::missing_context::delete_pseudo_edges(txn, channel, &mut ws.missing_context)
.map_err(LocalApplyError::from_missing)?;
crate::TIMERS.lock().unwrap().repair_context += now.elapsed();
Ok(())
}
fn repair_new_vertex_context_up<T: GraphMutTxnT + TreeTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
n: &NewVertex<Option<Hash>>,
vertex: Vertex<ChangeId>,
) -> Result<(), LocalApplyError<T>> {
for up in n.up_context.iter() {
let up = *txn.find_block_end(channel, internal_pos(txn, &up, change_id)?)?;
if !is_alive(txn, channel, &up)? {
debug!("repairing missing up context {:?} {:?}", up, vertex);
repair_missing_up_context(
txn,
channel,
&mut ws.missing_context,
change_id,
n.inode,
up,
&[vertex],
)
.map_err(LocalApplyError::from_missing)?
}
}
Ok(())
}
fn repair_new_vertex_context_down<T: GraphMutTxnT + TreeTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
n: &NewVertex<Option<Hash>>,
vertex: Vertex<ChangeId>,
) -> Result<(), LocalApplyError<T>> {
debug!("repairing missing context for {:?}", vertex);
if n.flag.contains(EdgeFlags::FOLDER) {
return Ok(());
}
'outer: for down in n.down_context.iter() {
let down = *txn.find_block(channel, internal_pos(txn, &down, change_id)?)?;
for e in iter_adjacent(
txn,
channel,
down,
EdgeFlags::PARENT,
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let e = e?;
if e.introduced_by() != change_id {
continue 'outer;
has_missing_edge_context(txn, channel, change_id, change, n, inodes)?;
fn repair_edge_context<T: GraphMutTxnT + TreeTxnT>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
fn has_missing_edge_context<T: GraphMutTxnT + TreeTxnT>(
txn: &T,
channel: &T::Graph,
for e in n.edges.iter() {
assert!(!e.flag.contains(EdgeFlags::PARENT));
if e.flag.contains(EdgeFlags::DELETED) {
trace!("repairing context deleted {:?}", e);
repair_context_deleted(
txn,
channel,
&mut ws.missing_context,
n.inode,
change_id,
|h| change.knows(&h),
e,
)
.map_err(LocalApplyError::from_missing)?
} else {
trace!("repairing context nondeleted {:?}", e);
repair_context_nondeleted(txn, channel, &mut ws.missing_context, n.inode, change_id, e)
let inode = internal_pos(txn, &n.inode, change_id)?;
if !inodes.contains(&inode) {
for e in n.edges.iter() {
assert!(!e.flag.contains(EdgeFlags::PARENT));
if e.flag.contains(EdgeFlags::DELETED) {
trace!("repairing context deleted {:?}", e);
if has_missing_context_deleted(txn, channel, change_id, |h| change.knows(&h), e)
.map_err(LocalApplyError::from_missing)?
{
inodes.insert(inode);
break;
}
} else {
trace!("repairing context nondeleted {:?}", e);
if has_missing_context_nondeleted(
txn,
channel,
change_id,
e,
)