@@ -505,12 +505,50 @@ impl ResolvedWorkload {
505505
506506 /// This function plugs a components imports with the exports of other components
507507 /// that are already loaded in the plugin system.
508+ ///
509+ /// Components are processed in topological order based on their inter-component
510+ /// dependencies. This ensures that when a component imports from another component,
511+ /// the exporting component has already had its imports resolved and can be
512+ /// pre-instantiated.
508513 async fn resolve_workload_imports (
509514 & mut self ,
510515 interface_map : & HashMap < String , Arc < str > > ,
511516 ) -> anyhow:: Result < ( ) > {
512- let component_ids: Vec < Arc < str > > = self . components . read ( ) . await . keys ( ) . cloned ( ) . collect ( ) ;
513- for component_id in component_ids {
517+ // Build a dependency graph: for each component, track which other components it imports from
518+ let mut dependencies: HashMap < Arc < str > , HashSet < Arc < str > > > = HashMap :: new ( ) ;
519+
520+ {
521+ let components = self . components . read ( ) . await ;
522+ for ( component_id, component) in components. iter ( ) {
523+ let mut deps = HashSet :: new ( ) ;
524+ let ty = component. metadata . component . component_type ( ) ;
525+ for ( import_name, import_item) in ty. imports ( component. metadata . component . engine ( ) )
526+ {
527+ if matches ! ( import_item, ComponentItem :: ComponentInstance ( _) )
528+ && let Some ( exporter_id) = interface_map. get ( import_name)
529+ && exporter_id != component_id
530+ {
531+ // This import is provided by another component in the workload
532+ deps. insert ( exporter_id. clone ( ) ) ;
533+ }
534+ }
535+ dependencies. insert ( component_id. clone ( ) , deps) ;
536+ }
537+ }
538+
539+ // Topologically sort components: components with no dependencies (or dependencies
540+ // already processed) come first. This ensures that when we process a component
541+ // that imports from another component, the exporter has already been resolved.
542+ let sorted_component_ids = topological_sort_components ( & dependencies) . context (
543+ "failed to determine component processing order - possible circular dependency" ,
544+ ) ?;
545+
546+ trace ! (
547+ order = ?sorted_component_ids. iter( ) . map( |id| id. as_ref( ) ) . collect:: <Vec <_>>( ) ,
548+ "processing components in topological order"
549+ ) ;
550+
551+ for component_id in sorted_component_ids {
514552 // In order to have mutable access to both the workload component and components that need
515553 // to be instantiated as "plugins" during linking, we remove and re-add the component to the list.
516554 let mut workload_component = {
@@ -1408,6 +1446,84 @@ impl UnresolvedWorkload {
14081446 }
14091447}
14101448
1449+ /// Performs a topological sort on components based on their inter-component dependencies.
1450+ ///
1451+ /// This function uses Kahn's algorithm to produce an ordering where components
1452+ /// that export interfaces are processed before components that import those interfaces.
1453+ /// This ensures that when linking components, the exporting component's linker has
1454+ /// already been fully configured before it needs to be pre-instantiated.
1455+ ///
1456+ /// # Arguments
1457+ /// * `dependencies` - A map from component ID to the set of component IDs it depends on
1458+ /// (i.e., components whose exports it imports)
1459+ ///
1460+ /// # Returns
1461+ /// A vector of component IDs in topological order (dependencies first), or an error
1462+ /// if a circular dependency is detected.
1463+ fn topological_sort_components (
1464+ dependencies : & HashMap < Arc < str > , HashSet < Arc < str > > > ,
1465+ ) -> anyhow:: Result < Vec < Arc < str > > > {
1466+ // Build in-degree map: count how many dependencies each component has
1467+ // (only counting dependencies on other components within this workload)
1468+ let mut in_degree: HashMap < Arc < str > , usize > = HashMap :: new ( ) ;
1469+
1470+ for ( component_id, deps) in dependencies {
1471+ // Initialize entry for this component
1472+ in_degree. entry ( component_id. clone ( ) ) . or_insert ( 0 ) ;
1473+
1474+ // Count only dependencies that are part of this workload
1475+ let dep_count = deps
1476+ . iter ( )
1477+ . filter ( |d| dependencies. contains_key ( * d) )
1478+ . count ( ) ;
1479+ * in_degree. get_mut ( component_id) . unwrap ( ) = dep_count;
1480+ }
1481+
1482+ // Start with components that have no dependencies (in-degree == 0)
1483+ // Sort for deterministic ordering
1484+ let mut queue: Vec < Arc < str > > = in_degree
1485+ . iter ( )
1486+ . filter ( |& ( _, degree) | * degree == 0 )
1487+ . map ( |( id, _) | id. clone ( ) )
1488+ . collect ( ) ;
1489+ queue. sort ( ) ;
1490+
1491+ let mut result = Vec :: with_capacity ( dependencies. len ( ) ) ;
1492+
1493+ while let Some ( component_id) = queue. pop ( ) {
1494+ result. push ( component_id. clone ( ) ) ;
1495+
1496+ // Find components that depend on this one and decrease their in-degree
1497+ for ( other_id, deps) in dependencies {
1498+ if deps. contains ( & component_id)
1499+ && let Some ( degree) = in_degree. get_mut ( other_id)
1500+ {
1501+ * degree = degree. saturating_sub ( 1 ) ;
1502+ if * degree == 0 && !result. contains ( other_id) {
1503+ queue. push ( other_id. clone ( ) ) ;
1504+ // Re-sort to maintain determinism
1505+ queue. sort ( ) ;
1506+ }
1507+ }
1508+ }
1509+ }
1510+
1511+ // Check for circular dependencies
1512+ if result. len ( ) != dependencies. len ( ) {
1513+ let unprocessed: Vec < _ > = dependencies
1514+ . keys ( )
1515+ . filter ( |id| !result. contains ( id) )
1516+ . map ( |id| id. as_ref ( ) )
1517+ . collect ( ) ;
1518+ bail ! (
1519+ "circular dependency detected among components: {:?}" ,
1520+ unprocessed
1521+ ) ;
1522+ }
1523+
1524+ Ok ( result)
1525+ }
1526+
14111527#[ cfg( test) ]
14121528mod tests {
14131529 use super :: * ;
@@ -1838,4 +1954,107 @@ mod tests {
18381954 // Show the difference between includes and includes_bidirectional
18391955 assert ! ( !world. includes( & interface3) ) ;
18401956 }
1957+
1958+ /// Tests topological sort with a chain dependency: A -> B -> C
1959+ /// Expected order: C, B, A (or any valid topological order)
1960+ #[ test]
1961+ fn test_topological_sort_chain ( ) {
1962+ let a: Arc < str > = Arc :: from ( "component-a" ) ;
1963+ let b: Arc < str > = Arc :: from ( "component-b" ) ;
1964+ let c: Arc < str > = Arc :: from ( "component-c" ) ;
1965+
1966+ // A depends on B, B depends on C
1967+ let mut dependencies: HashMap < Arc < str > , HashSet < Arc < str > > > = HashMap :: new ( ) ;
1968+ dependencies. insert ( a. clone ( ) , HashSet :: from ( [ b. clone ( ) ] ) ) ;
1969+ dependencies. insert ( b. clone ( ) , HashSet :: from ( [ c. clone ( ) ] ) ) ;
1970+ dependencies. insert ( c. clone ( ) , HashSet :: new ( ) ) ;
1971+
1972+ let result = topological_sort_components ( & dependencies) . unwrap ( ) ;
1973+
1974+ // C should come before B, and B should come before A
1975+ let c_pos = result. iter ( ) . position ( |x| x == & c) . unwrap ( ) ;
1976+ let b_pos = result. iter ( ) . position ( |x| x == & b) . unwrap ( ) ;
1977+ let a_pos = result. iter ( ) . position ( |x| x == & a) . unwrap ( ) ;
1978+
1979+ assert ! (
1980+ c_pos < b_pos,
1981+ "C should be processed before B: C at {c_pos}, B at {b_pos}"
1982+ ) ;
1983+ assert ! (
1984+ b_pos < a_pos,
1985+ "B should be processed before A: B at {b_pos}, A at {a_pos}"
1986+ ) ;
1987+ }
1988+
1989+ /// Tests topological sort with no dependencies
1990+ #[ test]
1991+ fn test_topological_sort_no_dependencies ( ) {
1992+ let a: Arc < str > = Arc :: from ( "component-a" ) ;
1993+ let b: Arc < str > = Arc :: from ( "component-b" ) ;
1994+ let c: Arc < str > = Arc :: from ( "component-c" ) ;
1995+
1996+ let mut dependencies: HashMap < Arc < str > , HashSet < Arc < str > > > = HashMap :: new ( ) ;
1997+ dependencies. insert ( a. clone ( ) , HashSet :: new ( ) ) ;
1998+ dependencies. insert ( b. clone ( ) , HashSet :: new ( ) ) ;
1999+ dependencies. insert ( c. clone ( ) , HashSet :: new ( ) ) ;
2000+
2001+ let result = topological_sort_components ( & dependencies) . unwrap ( ) ;
2002+
2003+ // All components should be present
2004+ assert_eq ! ( result. len( ) , 3 ) ;
2005+ assert ! ( result. contains( & a) ) ;
2006+ assert ! ( result. contains( & b) ) ;
2007+ assert ! ( result. contains( & c) ) ;
2008+ }
2009+
2010+ /// Tests topological sort with diamond dependency: A -> B, A -> C, B -> D, C -> D
2011+ #[ test]
2012+ fn test_topological_sort_diamond ( ) {
2013+ let a: Arc < str > = Arc :: from ( "component-a" ) ;
2014+ let b: Arc < str > = Arc :: from ( "component-b" ) ;
2015+ let c: Arc < str > = Arc :: from ( "component-c" ) ;
2016+ let d: Arc < str > = Arc :: from ( "component-d" ) ;
2017+
2018+ // A depends on B and C, both B and C depend on D
2019+ let mut dependencies: HashMap < Arc < str > , HashSet < Arc < str > > > = HashMap :: new ( ) ;
2020+ dependencies. insert ( a. clone ( ) , HashSet :: from ( [ b. clone ( ) , c. clone ( ) ] ) ) ;
2021+ dependencies. insert ( b. clone ( ) , HashSet :: from ( [ d. clone ( ) ] ) ) ;
2022+ dependencies. insert ( c. clone ( ) , HashSet :: from ( [ d. clone ( ) ] ) ) ;
2023+ dependencies. insert ( d. clone ( ) , HashSet :: new ( ) ) ;
2024+
2025+ let result = topological_sort_components ( & dependencies) . unwrap ( ) ;
2026+
2027+ let a_pos = result. iter ( ) . position ( |x| x == & a) . unwrap ( ) ;
2028+ let b_pos = result. iter ( ) . position ( |x| x == & b) . unwrap ( ) ;
2029+ let c_pos = result. iter ( ) . position ( |x| x == & c) . unwrap ( ) ;
2030+ let d_pos = result. iter ( ) . position ( |x| x == & d) . unwrap ( ) ;
2031+
2032+ // D should come before B and C
2033+ assert ! ( d_pos < b_pos, "D should be processed before B" ) ;
2034+ assert ! ( d_pos < c_pos, "D should be processed before C" ) ;
2035+ // B and C should come before A
2036+ assert ! ( b_pos < a_pos, "B should be processed before A" ) ;
2037+ assert ! ( c_pos < a_pos, "C should be processed before A" ) ;
2038+ }
2039+
2040+ /// Tests topological sort with circular dependency detection
2041+ #[ test]
2042+ fn test_topological_sort_circular_dependency ( ) {
2043+ let a: Arc < str > = Arc :: from ( "component-a" ) ;
2044+ let b: Arc < str > = Arc :: from ( "component-b" ) ;
2045+ let c: Arc < str > = Arc :: from ( "component-c" ) ;
2046+
2047+ // Circular: A -> B -> C -> A
2048+ let mut dependencies: HashMap < Arc < str > , HashSet < Arc < str > > > = HashMap :: new ( ) ;
2049+ dependencies. insert ( a. clone ( ) , HashSet :: from ( [ b. clone ( ) ] ) ) ;
2050+ dependencies. insert ( b. clone ( ) , HashSet :: from ( [ c. clone ( ) ] ) ) ;
2051+ dependencies. insert ( c. clone ( ) , HashSet :: from ( [ a. clone ( ) ] ) ) ;
2052+
2053+ let result = topological_sort_components ( & dependencies) ;
2054+ assert ! (
2055+ result. is_err( ) ,
2056+ "Should detect circular dependency: {:?}" ,
2057+ result
2058+ ) ;
2059+ }
18412060}
0 commit comments