Skip to content

Commit

Permalink
snap
Browse files Browse the repository at this point in the history
  • Loading branch information
Leandros committed Oct 30, 2024
1 parent da8f781 commit ded1517
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 131 deletions.
199 changes: 99 additions & 100 deletions ferrunix-core/src/cycle_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,36 +80,19 @@ pub(crate) struct DependencyValidator {
/// The visitor callbacks. Those are necessary because we only want to register each type once
/// we have collected them all.
visitor: NonAsyncRwLock<HashMap<TypeId, Visitor>>,
/// Dependency graph.
graph:
NonAsyncRwLock<petgraph::Graph<&'static str, (), petgraph::Directed>>,
/// Cache of all previously visited types. To avoid infinite recursion and as an optimization.
visited: NonAsyncRwLock<HashMap<TypeId, petgraph::graph::NodeIndex>>,
/// All missing dependencies.
missing: NonAsyncRwLock<HashMap<TypeId, MissingDependencies>>,
/// Whether we have already visited all visitors.
visitor_visited: AtomicBool,
/// Cached validation result.
validation_cache: NonAsyncRwLock<
Option<
Result<
Vec<petgraph::graph::NodeIndex>,
petgraph::algo::Cycle<petgraph::graph::NodeIndex>,
>,
>,
>,
/// Context for visitors.
context: NonAsyncRwLock<VisitorContext>,
}

impl DependencyValidator {
/// Create a new dependency validator.
pub(crate) fn new() -> Self {
Self {
visitor: NonAsyncRwLock::new(HashMap::new()),
graph: NonAsyncRwLock::new(petgraph::Graph::new()),
visited: NonAsyncRwLock::new(HashMap::new()),
missing: NonAsyncRwLock::new(HashMap::new()),
visitor_visited: AtomicBool::new(false),
validation_cache: NonAsyncRwLock::new(None),
context: NonAsyncRwLock::new(VisitorContext::new()),
}
}

Expand All @@ -118,28 +101,18 @@ impl DependencyValidator {
where
T: Registerable,
{
let visitor = Visitor(|this, _visitors| {
if let Some(index) = this.visited.read().get(&TypeId::of::<T>()) {
let visitor = Visitor(|_this, _visitors, context| {
if let Some(index) = context.visited.get(&TypeId::of::<T>()) {
return *index;
}

let index = {
let mut graph = this.graph.write();
graph.add_node(std::any::type_name::<T>())
};
let index = context.graph.add_node(std::any::type_name::<T>());

{
let mut visited = this.visited.write();
visited.insert(TypeId::of::<T>(), index);
}
context.visited.insert(TypeId::of::<T>(), index);

index
});

// TODO: Make this lock free!
{
let _ = self.validation_cache.write().take();
}
self.visitor_visited.store(false, Ordering::Release);
self.visitor.write().insert(TypeId::of::<T>(), visitor);
}
Expand All @@ -160,46 +133,43 @@ impl DependencyValidator {
>(
&self,
) {
let visitor = Visitor(|this, visitors| {
let visitor = Visitor(|this, visitors, context| {
// We already visited this type.
if let Some(index) = this.visited.read().get(&TypeId::of::<T>()) {
if let Some(index) = context.visited.get(&TypeId::of::<T>()) {
return *index;
}

let current = {
let mut graph = this.graph.write();
graph.add_node(std::any::type_name::<T>())
};
let current = context.graph.add_node(std::any::type_name::<T>());

// We visited this type. This must be added before we visit dependencies.
{
let mut visited = this.visited.write();
visited.insert(TypeId::of::<T>(), current);
context.visited.insert(TypeId::of::<T>(), current);
}

let type_ids =
Deps::as_typeids(dependency_builder::private::SealToken);

for (type_id, type_name) in &type_ids {
// We have been to the dependency type before, we don't need to do it again.
if let Some(index) = this.visited.read().get(type_id) {
this.graph.write().add_edge(current, *index, ());
if let Some(index) = context.visited.get(type_id) {
context.graph.add_edge(current, *index, ());
continue;
}

// Never seen the type before, visit it.
if let Some(visitor) = visitors.get(type_id) {
let index = (visitor.0)(this, visitors);
this.graph.write().add_edge(current, index, ());
let index = (visitor.0)(this, visitors, context);
context.graph.add_edge(current, index, ());
continue;
}

{
let mut missing = this.missing.write();
if let Some(ty) = missing.get_mut(&TypeId::of::<T>()) {
if let Some(ty) =
context.missing.get_mut(&TypeId::of::<T>())
{
ty.deps.push((*type_id, type_name));
} else {
missing.insert(
context.missing.insert(
TypeId::of::<T>(),
MissingDependencies {
ty: (
Expand All @@ -222,10 +192,6 @@ impl DependencyValidator {
current
});

// TODO: Make this lock free!
{
let _ = self.validation_cache.write().take();
}
self.visitor_visited.store(false, Ordering::Release);
self.visitor.write().insert(TypeId::of::<T>(), visitor);
}
Expand All @@ -245,66 +211,62 @@ impl DependencyValidator {
/// are fulfillable and there are no cycles in the graph.
pub(crate) fn validate_all(&self) -> Result<(), ValidationError> {
// This **must** be a separate `if`, otherwise the lock is held also in the `else`.
if let Some(cache) = &*self.validation_cache.read() {
// Validation is cached.
{
let missing = self.missing.read();
if missing.len() > 0 {
let mut vec = Vec::with_capacity(missing.len());
for (_, ty) in missing.iter() {
vec.push(ty.clone());
}
return Err(ValidationError::Missing(vec));
}
}

// EARLY RETURN ABSOLUTELY REQUIRED!
return match cache {
Ok(_) => Ok(()),
Err(_err) => Err(ValidationError::Cycle),
};
}
// if let Some(cache) = &*self.validation_cache.read() {
// // Validation is cached.
// {
// let missing = self.missing_cache.read();
// if missing.len() > 0 {
// let mut vec = Vec::with_capacity(missing.len());
// for (_, ty) in missing.iter() {
// vec.push(ty.clone());
// }
// return Err(ValidationError::Missing(vec));
// }
// }

// // EARLY RETURN ABSOLUTELY REQUIRED!
// return match cache {
// Ok(_) => Ok(()),
// Err(_err) => Err(ValidationError::Cycle),
// };
// }

// Validation is **not** cached.

if self
.visitor_visited
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
// if self
// .visitor_visited
// .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
// .is_ok()
// {
let mut context = self.context.write();

// Make sure we have all types registered.
{
// Make sure we have all types registered.
let visitor = self.visitor.read();
for (_type_id, cb) in visitor.iter() {
// To avoid a dead lock due to other visitors needing to be called, we pass in the
// visitors hashmap.
(cb.0)(self, &visitor);
(cb.0)(self, &visitor, &mut context);
}
}

{
let missing = self.missing.read();
if missing.len() > 0 {
let mut vec = Vec::with_capacity(missing.len());
for (_, ty) in missing.iter() {
vec.push(ty.clone());
}
return Err(ValidationError::Missing(vec));
if !context.missing.is_empty() {
let mut vec = Vec::with_capacity(context.missing.len());
for (_, ty) in &context.missing {
vec.push(ty.clone());
}
return Err(ValidationError::Missing(vec));
}

let graph = self.graph.read();
let mut space = petgraph::algo::DfsSpace::new(&*graph);
let result = petgraph::algo::toposort(&*graph, Some(&mut space));
let ret = match result {
Ok(_) => Ok(()),
Err(_) => Err(ValidationError::Cycle),
};
let mut space = petgraph::algo::DfsSpace::new(&context.graph);
context.validation_cache =
Some(petgraph::algo::toposort(&context.graph, Some(&mut space)));

// Cache the result.
{
let mut cache = self.validation_cache.write();
*cache = Some(result);
}
let ret = match context.validation_cache {
Some(Ok(_)) => Ok(()),
Some(Err(_)) => Err(ValidationError::Cycle),
_ => unreachable!("it's written above"),
};

ret
}
Expand All @@ -322,12 +284,49 @@ impl DependencyValidator {
pub(crate) fn dotgraph(&self) -> Result<String, ValidationError> {
self.validate_all()?;

let graph = self.graph.read();
let context = self.context.read();
let dot = petgraph::dot::Dot::with_config(
&*graph,
&context.graph,
&[petgraph::dot::Config::EdgeNoLabel],
);

Ok(format!("{dot:?}"))
}
}

/// Context that's passed into every `visitor`.
pub(crate) struct VisitorContext {
/// Dependency graph.
graph: petgraph::Graph<&'static str, (), petgraph::Directed>,
/// All missing dependencies.
missing: HashMap<TypeId, MissingDependencies>,
/// Cache of all previously visited types. To avoid infinite recursion and as an optimization.
visited: HashMap<TypeId, petgraph::graph::NodeIndex>,
/// Cached validation result.
validation_cache: Option<
Result<
Vec<petgraph::graph::NodeIndex>,
petgraph::algo::Cycle<petgraph::graph::NodeIndex>,
>,
>,
}

impl VisitorContext {
/// Create a new default context.
pub fn new() -> Self {
Self {
graph: petgraph::Graph::new(),
missing: HashMap::new(),
visited: HashMap::new(),
validation_cache: None,
}
}

/// Reset the context.
pub fn reset(&mut self) {
self.graph.clear();
self.missing.clear();
self.visited.clear();
self.validation_cache = None;
}
}
1 change: 1 addition & 0 deletions ferrunix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod cycle_detection;
pub mod dependencies;
pub mod dependency_builder;
pub mod error;
pub mod lazy_locked_cache;
pub mod object_builder;
pub mod registration;
pub mod registry;
Expand Down
Loading

0 comments on commit ded1517

Please sign in to comment.