or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

compatibility-management.mddatastore-operations.mdgarbage-collection.mdhandle-management.mdindex.mdruntime-factories.mdstorage-utilities.mdsummary-management.mdtelemetry-utilities.md
tile.json

garbage-collection.mddocs/

Garbage Collection ❌ Internal API

Tools for building and managing garbage collection data structures to track object references in distributed Fluid applications. These utilities help identify unreachable objects and manage memory in collaborative scenarios.

Internal API Warning: All APIs in this document are internal to the Fluid Framework and are not exported for public use. These utilities are implementation details that may change without notice. They are documented here for framework contributors and advanced users who need to understand the internal workings.

Internal APIs ❌

The following APIs are not available for public use and are internal implementation details:

GC Data Builder ❌ Internal

A builder class for constructing garbage collection data by combining information from multiple nodes and managing outbound reference routes.

Status: Internal implementation detail - not exported

/**
 * Helper class to build the garbage collection data of a node by combining data from multiple nodes
 */
class GCDataBuilder implements IGarbageCollectionData {
  /** Get the current GC nodes and their outbound routes */
  get gcNodes(): Record<string, string[]>;
  
  /**
   * Add a single node with its outbound routes to the GC data
   * @param id - The node identifier
   * @param outboundRoutes - Array of routes that this node references
   */
  addNode(id: string, outboundRoutes: string[]): void;
  
  /**
   * Normalizes and prefixes node IDs before adding them to the GC data
   * @param prefixId - Prefix to add to all node IDs
   * @param gcNodes - Record of node IDs to their outbound routes
   */
  prefixAndAddNodes(prefixId: string, gcNodes: Record<string, string[]>): void;
  
  /**
   * Add multiple nodes at once to the GC data
   * @param gcNodes - Record of node IDs to their outbound routes
   */
  addNodes(gcNodes: Record<string, string[]>): void;
  
  /**
   * Adds the given outbound route to all GC nodes
   * @param outboundRoute - The route to add to all existing nodes
   */
  addRouteToAllNodes(outboundRoute: string): void;
  
  /**
   * Get the final garbage collection data structure
   * @returns Complete GC data with all nodes and routes
   */
  getGCData(): IGarbageCollectionData;
}

GC Route Unpacking ❌ Internal

Utility for extracting child node usage information from used routes data.

Status: Internal implementation detail - not exported

/**
 * Helper function that unpacks the used routes of children from a given node's used routes
 * @param usedRoutes - The used routes of a node
 * @returns A map of used routes of each children of the given node
 */
function unpackChildNodesUsedRoutes(usedRoutes: readonly string[]): Map<string, string[]>;

GC Data Processing ❌ Internal

Utilities for processing garbage collection data from attach messages and summary snapshots.

Status: Internal implementation detail - not exported

/**
 * Looks in the given attach message snapshot for the .gcdata blob and processes initial GC Data
 * @param snapshot - The snapshot tree to search in (may be undefined)
 * @param addedGCOutboundRoute - Callback invoked for each outbound route found
 * @returns True if it found/processed GC Data, false otherwise
 */
function processAttachMessageGCData(
  snapshot: ITree | undefined,
  addedGCOutboundRoute: (fromNodeId: string, toPath: string) => void
): boolean;

Migration Guidance

Since all APIs in this module are internal and not exported, there are no direct replacements available in the public API. If you need garbage collection functionality:

  1. GC Management: Use the public container runtime GC APIs and options
  2. Reference Tracking: Rely on the framework's built-in garbage collection system
  3. Memory Management: Use standard JavaScript memory management practices
  4. Object Lifecycle: Use the public data store and shared object APIs for proper lifecycle management

Example Usage (Internal Only - Not Available Publicly):

// ❌ These imports will NOT work - APIs are internal
// import { 
//   GCDataBuilder,
//   unpackChildNodesUsedRoutes,
//   processAttachMessageGCData
// } from "@fluidframework/runtime-utils";

// Building GC data for a component
const gcBuilder = new GCDataBuilder();

// Add individual nodes with their references
gcBuilder.addNode("datastore1", ["/root/datastore2", "/root/datastore3"]);
gcBuilder.addNode("datastore2", ["/root/shared-object"]);
gcBuilder.addNode("datastore3", []);

// Add child component GC data with prefix
const childGCData = {
  "child1": ["/parent/child2"],
  "child2": []
};
gcBuilder.prefixAndAddNodes("component1", childGCData);
// Results in: "component1/child1" -> ["/parent/child2"], "component1/child2" -> []

// Add a route that all nodes should reference (e.g., shared configuration)
gcBuilder.addRouteToAllNodes("/root/config");

// Get the final GC data
const gcData = gcBuilder.getGCData();
console.log("GC nodes:", gcData.gcNodes);

// Processing used routes
const usedRoutes = [
  "/child1/subcomponent",
  "/child1/data",
  "/child2/cache",
  "/child2/state"
];

const childUsedRoutes = unpackChildNodesUsedRoutes(usedRoutes);
console.log("Child used routes:", childUsedRoutes);
// Map { "child1" => ["/subcomponent", "/data"], "child2" => ["/cache", "/state"] }

// Processing GC data from attach messages
const attachSnapshot = await getAttachMessageSnapshot();
const foundGCData = processAttachMessageGCData(
  attachSnapshot,
  (fromNodeId: string, toPath: string) => {
    console.log(`Found GC route: ${fromNodeId} -> ${toPath}`);
    // Add to your GC tracking system
    gcTracker.addRoute(fromNodeId, toPath);
  }
);

if (foundGCData) {
  console.log("Successfully processed GC data from attach message");
} else {
  console.log("No GC data found in attach message");
}

Advanced Garbage Collection Patterns

// Component-level GC management
class ComponentGCManager {
  private gcBuilder = new GCDataBuilder();
  private componentId: string;
  
  constructor(componentId: string) {
    this.componentId = componentId;
  }
  
  // Register a data store and its dependencies
  registerDataStore(
    dataStoreId: string, 
    dependencies: string[]
  ): void {
    const fullId = `${this.componentId}/${dataStoreId}`;
    this.gcBuilder.addNode(fullId, dependencies);
  }
  
  // Register a child component's GC data
  registerChildComponent(
    childId: string,
    childGCData: Record<string, string[]>
  ): void {
    this.gcBuilder.prefixAndAddNodes(
      `${this.componentId}/${childId}`,
      childGCData
    );
  }
  
  // Add a global dependency (e.g., shared services)
  addGlobalDependency(route: string): void {
    this.gcBuilder.addRouteToAllNodes(route);
  }
  
  // Get the complete GC data for this component
  getGCData(): IGarbageCollectionData {
    return this.gcBuilder.getGCData();
  }
  
  // Analyze usage patterns
  analyzeUsage(usedRoutes: readonly string[]): ComponentUsageAnalysis {
    const childUsage = unpackChildNodesUsedRoutes(usedRoutes);
    const gcData = this.getGCData();
    
    const unusedNodes: string[] = [];
    const activeNodes: string[] = [];
    
    for (const nodeId of Object.keys(gcData.gcNodes)) {
      const isUsed = usedRoutes.some(route => route.startsWith(`/${nodeId}`));
      if (isUsed) {
        activeNodes.push(nodeId);
      } else {
        unusedNodes.push(nodeId);
      }
    }
    
    return {
      childUsage,
      unusedNodes,
      activeNodes,
      totalNodes: Object.keys(gcData.gcNodes).length
    };
  }
}

// Container-level GC coordination
class ContainerGCCoordinator {
  private components = new Map<string, GCDataBuilder>();
  
  registerComponent(componentId: string): GCDataBuilder {
    const builder = new GCDataBuilder();
    this.components.set(componentId, builder);
    return builder;
  }
  
  // Collect GC data from all components
  collectContainerGCData(): IGarbageCollectionData {
    const containerBuilder = new GCDataBuilder();
    
    for (const [componentId, componentBuilder] of this.components) {
      const componentGCData = componentBuilder.getGCData();
      containerBuilder.prefixAndAddNodes(componentId, componentGCData.gcNodes);
    }
    
    return containerBuilder.getGCData();
  }
  
  // Process attach message for initial GC setup
  async processAttachMessage(attachSnapshot: ITree | undefined): Promise<void> {
    const routes: Array<{ from: string, to: string }> = [];
    
    const found = processAttachMessageGCData(
      attachSnapshot,
      (fromNodeId, toPath) => {
        routes.push({ from: fromNodeId, to: toPath });
      }
    );
    
    if (found) {
      // Apply the routes to appropriate components
      for (const route of routes) {
        const componentId = this.extractComponentId(route.from);
        const builder = this.components.get(componentId);
        
        if (builder) {
          const currentData = builder.getGCData();
          if (currentData.gcNodes[route.from]) {
            currentData.gcNodes[route.from].push(route.to);
          } else {
            builder.addNode(route.from, [route.to]);
          }
        }
      }
    }
  }
  
  // Analyze cross-component references
  analyzeCrossComponentReferences(): CrossReferenceAnalysis {
    const containerGCData = this.collectContainerGCData();
    const crossReferences: Array<{ from: string, to: string }> = [];
    
    for (const [nodeId, routes] of Object.entries(containerGCData.gcNodes)) {
      const nodeComponent = this.extractComponentId(nodeId);
      
      for (const route of routes) {
        const targetComponent = this.extractComponentId(route);
        
        if (nodeComponent !== targetComponent) {
          crossReferences.push({ from: nodeComponent, to: targetComponent });
        }
      }
    }
    
    return { crossReferences };
  }
  
  private extractComponentId(path: string): string {
    return path.split('/')[1] || 'root';
  }
}

// GC data persistence and loading
class GCDataPersistence {
  static serializeGCData(gcData: IGarbageCollectionData): string {
    return JSON.stringify(gcData.gcNodes);
  }
  
  static deserializeGCData(serialized: string): IGarbageCollectionData {
    const gcNodes = JSON.parse(serialized);
    return { gcNodes };
  }
  
  static async saveGCDataToSummary(
    gcData: IGarbageCollectionData,
    summaryBuilder: SummaryTreeBuilder
  ): Promise<void> {
    const serialized = this.serializeGCData(gcData);
    summaryBuilder.addBlob(".gcdata", serialized);
  }
  
  static async loadGCDataFromSnapshot(
    snapshot: ITree
  ): Promise<IGarbageCollectionData | undefined> {
    try {
      const gcBlob = await readBlobFromTree(snapshot, ".gcdata");
      const serialized = new TextDecoder().decode(gcBlob);
      return this.deserializeGCData(serialized);
    } catch {
      return undefined;
    }
  }
}

// Usage tracking and cleanup
interface ComponentUsageAnalysis {
  childUsage: Map<string, string[]>;
  unusedNodes: string[];
  activeNodes: string[];
  totalNodes: number;
}

interface CrossReferenceAnalysis {
  crossReferences: Array<{ from: string, to: string }>;
}