Skip to content

Parser repeatedly traverses shared schema graphs during circular-reference resolution #1230

Description

@sean2077

Description

resolveCircularRefs uses visited as a recursion-stack guard and deletes each object after returning from it. This prevents recursion loops, but it also means that a dereferenced object shared by many parents is traversed again for every path. A diamond-shaped schema graph therefore grows exponentially during this custom operation even though the in-memory graph itself is small.

In a real-world AsyncAPI 3 document with heavily reused envelope schemas, validation completed in a few seconds but parsing/generation remained CPU-bound for more than 10 minutes and used roughly 700 MB of memory. A CPU profile attributed about 73% of samples to recursive calls in resolve-circular-refs.ts.

Minimal reproduction

Create one leaf object with an enumerable getter, then wrap the same object twice at each of 12 levels:

let visits = 0;
const leaf = {};
Object.defineProperty(leaf, 'value', {
  enumerable: true,
  get() {
    visits += 1;
    return 'value';
  },
});

let graph = leaf;
for (let level = 0; level < 12; level += 1) {
  graph = { left: graph, right: graph };
}

Passing a document whose json() method returns this graph to resolveCircularRefs reads the leaf 4096 times on current master; it only needs to be processed once.

Expected behavior

The traversal should retain its active recursion-stack guard, while separately memoizing objects only after their complete subtree has been processed. Objects containing a $ref that cannot yet be resolved for the current inventory path must not be marked complete, because a later path may provide the required resolution context.

Version

  • @asyncapi/parser 3.6.3
  • current master at aded935e6a5e2d420acb9ae4a6b5532cc30bbf45

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions