Skip to content

Instantly share code, notes, and snippets.

@Kureev
Last active April 13, 2023 22:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kureev/ab6cdd926dd6eaafd9da884309ae6685 to your computer and use it in GitHub Desktop.
Save Kureev/ab6cdd926dd6eaafd9da884309ae6685 to your computer and use it in GitHub Desktop.

Problem

I have an issue calling Obsidian.obtain. It seems that sometimes it throws exceeding max stack error. Fix it

Long story short: https://twitter.com/GcCarmeli/status/1645482647315988486

The idea was to embed the codebase and create a custom agent that will be able to fix the code using baby AI-inspired approach. Here are some high-level results. Whatever happens after this line is pure AI-generated output.

FIND RESULT:

  obtain<T extends ObjectGraph<P>, P = any>(
    Graph: Constructable<T>,
    props?: P,
  ): ServiceLocator<Omit<T, GraphInternals>> {
    return serviceLocatorFactory.fromGraph(Graph, props);
  }

ANALYSIS RESULT: Main Purpose of the Code: The main purpose of the Obsidian.obtain method is to create a ServiceLocator instance from the given ObjectGraph (Graph) and optional properties (props). It does this by calling the serviceLocatorFactory.fromGraph method with the Graph and props as arguments.

Analysis of the Code: The Obsidian.obtain method itself is quite simple and straightforward. It takes a Graph and optional props as input and returns a ServiceLocator instance. The actual logic for creating the ServiceLocator is delegated to the serviceLocatorFactory.fromGraph method. Since the issue reported is related to exceeding the max stack error, it is likely that the problem lies within the serviceLocatorFactory.fromGraph method rather than the Obsidian.obtain method itself.

Suggestion: To fix the issue of exceeding the max stack error, it is recommended to find the code for the serviceLocatorFactory.fromGraph method and analyze it. The issue might be related to recursion or some other logic within that method that causes the stack to overflow. Once the code for the serviceLocatorFactory.fromGraph method is found, it can be analyzed and modified to prevent the max stack error from occurring.

FIND RESULT:

class ServiceLocatorFactory {
  static fromGraph<T extends ObjectGraph<P>, P = any>(Graph: Constructable<T>, props?: P) {
    const resolved = graphRegistry.resolve(Graph, props);
    const wrapped = new Proxy(resolved, {
      get(_target: any, property: string, receiver: any) {
        return () => resolved.retrieve(property, receiver);
      },
    });
    return wrapped as unknown as ServiceLocatorType<T>;
  }
}

ANALYSIS RESULT: Main Purpose of the Code: The main purpose of the code is to create a ServiceLocator instance from a given ObjectGraph. The ServiceLocatorFactory class has a static method fromGraph that takes a Graph and optional properties as input, resolves the graph using the graphRegistry, and returns a wrapped ServiceLocator instance using a Proxy.

Analysis of the Code: The fromGraph method creates a Proxy object that intercepts the get operation on the resolved graph object. The Proxy's get method returns a function that calls the retrieve method on the resolved graph object with the given property and receiver. This is where the Obsidian.obtain call might be happening.

However, the fromGraph method itself does not seem to be the best place to fix the issue of exceeding max stack error. Instead, we should look into the retrieve method of the resolved graph object, as it might be the one causing the stack overflow issue.

Suggestion: Find the code for the retrieve method of the resolved graph object and analyze it to see if it is the right place to fix the exceeding max stack error issue.

FIND RESULT:

class PropertyRetriever {
  constructor(private graph: Graph) {}

  retrieve(property: string, receiver?: unknown): unknown | undefined {
    const mangledPropertyKey = providedPropertiesStore.getMangledProperty(this.graph, property);
    if (mangledPropertyKey && mangledPropertyKey in this.graph) {
      const proxiedGraph = new Proxy(this.graph, {
        get(graph: Graph, dependencyName: string) {
          return graph.retrieve(dependencyName);
        },
      });
      return Reflect.get(this.graph, mangledPropertyKey, receiver)(proxiedGraph);
    }

    const results = this.getFromSubgraphs(property, receiver);
    if (results.length === 1) return results[0];
    if (results.length > 1) {
      throw new Error(
        `Multiple subgraphs provide the property ${property}.`
        + 'You should probably provide a unique name to one of the providers: @Provide({name: \'uniqueName\')})',
      );
    }
    return undefined;
  }

  private getFromSubgraphs(property: string, receiver: unknown): unknown[] {
    const subgraphs = graphRegistry.getSubgraphs(this.graph);
    return subgraphs
      .map((subgraph: Graph) => subgraph.retrieve(property, receiver))
      .filter((result) => result !== undefined);
  }
}

ANALYSIS RESULT: Main purpose of the code: The main purpose of the PropertyRetriever class is to retrieve properties from a given graph and its subgraphs. The retrieve method is responsible for finding the property in the graph and returning its value.

Analysis of the retrieve method: The retrieve method first tries to find the property in the current graph using the providedPropertiesStore.getMangledProperty method. If the property is found, it creates a Proxy for the graph and returns the value of the property using Reflect.get.

If the property is not found in the current graph, the method tries to find it in the subgraphs using the getFromSubgraphs method. If the property is found in exactly one subgraph, it returns the value. If the property is found in multiple subgraphs, it throws an error suggesting to provide a unique name for one of the providers.

Possible cause of the exceeding max stack error: The error might be caused by a circular dependency between the graphs and their subgraphs. When the retrieve method is called recursively on the subgraphs, it might lead to an infinite loop, causing the exceeding max stack error.

Suggested change: To fix the issue, we can add a Set to keep track of the visited graphs and pass it down the recursive calls. This way, we can avoid visiting the same graph multiple times and prevent the infinite loop.

Here's the modified retrieve method:

retrieve(property: string, receiver?: unknown, visitedGraphs: Set<Graph> = new Set()): unknown | undefined {
  if (visitedGraphs.has(this.graph)) {
    return undefined;
  }
  visitedGraphs.add(this.graph);

  const mangledPropertyKey = providedPropertiesStore.getMangledProperty(this.graph, property);
  if (mangledPropertyKey && mangledPropertyKey in this.graph) {
    const proxiedGraph = new Proxy(this.graph, {
      get(graph: Graph, dependencyName: string) {
        return graph.retrieve(dependencyName, undefined, visitedGraphs);
      },
    });
    return Reflect.get(this.graph, mangledPropertyKey, receiver)(proxiedGraph);
  }

  const results = this.getFromSubgraphs(property, receiver, visitedGraphs);
  if (results.length === 1) return results[0];
  if (results.length > 1) {
    throw new Error(
      `Multiple subgraphs provide the property ${property}.`
      + 'You should probably provide a unique name to one of the providers: @Provide({name: \'uniqueName\')})',
    );
  }
  return undefined;
}

private getFromSubgraphs(property: string, receiver: unknown, visitedGraphs: Set<Graph>): unknown[] {
  const subgraphs = graphRegistry.getSubgraphs(this.graph);
  return subgraphs
    .map((subgraph: Graph) => subgraph.retrieve(property, receiver, visitedGraphs))
    .filter((result) => result !== undefined);
}

This change should fix the exceeding max stack error by preventing infinite loops in the graph traversal. Here's the modified retrieve method that should fix the exceeding max stack error:

retrieve(property: string, receiver?: unknown, visitedGraphs: Set<Graph> = new Set()): unknown | undefined {
  if (visitedGraphs.has(this.graph)) {
    return undefined;
  }
  visitedGraphs.add(this.graph);

  const mangledPropertyKey = providedPropertiesStore.getMangledProperty(this.graph, property);
  if (mangledPropertyKey && mangledPropertyKey in this.graph) {
    const proxiedGraph = new Proxy(this.graph, {
      get(graph: Graph, dependencyName: string) {
        return graph.retrieve(dependencyName, undefined, visitedGraphs);
      },
    });
    return Reflect.get(this.graph, mangledPropertyKey, receiver)(proxiedGraph);
  }

  const results = this.getFromSubgraphs(property, receiver, visitedGraphs);
  if (results.length === 1) return results[0];
  if (results.length > 1) {
    throw new Error(
      `Multiple subgraphs provide the property ${property}.`
      + 'You should probably provide a unique name to one of the providers: @Provide({name: \'uniqueName\')})',
    );
  }
  return undefined;
}

private getFromSubgraphs(property: string, receiver: unknown, visitedGraphs: Set<Graph>): unknown[] {
  const subgraphs = graphRegistry.getSubgraphs(this.graph);
  return subgraphs
    .map((subgraph: Graph) => subgraph.retrieve(property, receiver, visitedGraphs))
    .filter((result) => result !== undefined);
}

This change should fix the exceeding max stack error by preventing infinite loops in the graph traversal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment