swagger-api / swagger-ui

Swagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.
https://swagger.io
Apache License 2.0
26.33k stars 8.92k forks source link

UI crashes on complex OpenAPI documents #9513

Open pavelkornev opened 7 months ago

pavelkornev commented 7 months ago

Q&A (please complete the following information)

Content & configuration

When rendering a complex OpenAPI document, UI comes unresponsive first with the following browser dialog, and eventually crashes later: swagger-ui-1 swagger-ui-0 swagger-ui-2

Example Swagger/OpenAPI definition: https://api.sap.com/odata/1.0/catalog.svc/APIContent.APIs('SAP_ICSM_StudyService')/$value?type=json

Describe the bug you're encountering

To reproduce...

Steps to reproduce the behavior:

  1. Go to https://editor-next.swagger.io/
  2. Paste content from this link: https://api.sap.com/odata/1.0/catalog.svc/APIContent.APIs('SAP_ICSM_StudyService')/$value?type=json
  3. Try to expand first operation "/BlindingGroups"

Expected behavior

UI doesn't crash.

Coder-Manuel commented 7 months ago

Am also experiencing the same, Any fix/walk-around ?

UPDATE: 13th Feb 2024 Walk-around is to downgrade to v7.1.0

char0n commented 7 months ago

I can confirm. This is very reproducible.

glowcloud commented 7 months ago

Investigating this issue within Swagger Client, I found out that the resolve mechanism executes for a long time due to the complexity of the specification with the nested references. The specification should eventually be resolved but the operations take too long. Because of this SwaggerUI will freeze.

I tried breaking out of the traversal early by creating and setting a maximum count of it to a 1000 for each run of the plugins. I broke out of the execution here https://github.com/swagger-api/swagger-js/blob/f00b527fd71f46ccf1a4ae14853edc5452160b04/src/specmap/index.js#L108 and here https://github.com/swagger-api/swagger-js/blob/f00b527fd71f46ccf1a4ae14853edc5452160b04/src/specmap/index.js#L137 if the limit was reached. This allowed the specification to be partially resolved and loaded in SwaggerUI, although rendering slowly. Unfortunately, as we don’t resolve everything, components looked like this:

ex_path ex_component

The missing values are unresolved, for example scenario from the second image is:

{
  allOf: [
    {
      '$ref': '#/components/schemas/com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.Scenarios-create'
    }
  ],
  nullable: true
}

and kitTypeAssignments:

{
    type: 'array',
    items: {
      '$ref': '#/components/schemas/com.sap.ctsm.backend.core.api.study.v1.StudyAPIv1.KitTypeTreatmentAssignments-create'
    }
  }

Perhaps we could render these unresolved definitions with reference strings but resolving everything is not possible at the moment.

glowcloud commented 6 months ago

The crashing issue was addressed in https://github.com/swagger-api/swagger-js/pull/3380, released as https://github.com/swagger-api/swagger-js/releases/tag/v3.25.3.

As it turned out, the complex specification was being eventually resolved, but the operations took too long due to the nested definitions. To fix this, we limited the maximum traversal depth for each run of the plugins. This allows us to get a partially resolved specification, which has to be resolved further in Swagger UI. A fix was also added for the limit of the plugins dispatch runs, which now correctly tracks each plugin separately.

glowcloud commented 6 months ago

Rendering schemas not resolved by Swagger Client addressed in https://github.com/swagger-api/swagger-ui/pull/9629.

pavelkornev commented 4 months ago

This still fails on the original example given in the issue description.

Steps:

  1. Go to https://editor-next.swagger.io/
  2. Paste content from this link: https://api.sap.com/odata/1.0/catalog.svc/APIContent.APIs('SAP_ICSM_StudyService')/$value?type=json
  3. Expand first operation "/BlindingGroups"
  4. Collapse the expanded operation "/BlindingGroups"
  5. Expand second operation "/BlindingGroups({ID})" -> crash!

Please reopen the issue!

char0n commented 4 months ago

Hi @pavelkornev,

There are number of issues that SwaggerUI is having with https://api.sap.com/odata/1.0/catalog.svc/APIContent.APIs('SAP_ICSM_StudyService')/$value?type=json

Size

The original definition is almost 2MB in size. This alone makes it challenging for SwaggerUI to deal with it.

Complexity

The complexity of this definition is extreme. By complexity I mean how Schema Objects are referenced and used. SwaggerUI uses SwaggerClient to dereference the definition in a special way - it tries to eliminate all the cycles (circular references) so that SwaggerUI can expect that no cycles are present when rendering.

Cycles (circular references)

To eliminate cycles, we use SwaggerClient legacy mechanism for OpenAPI 2.0 and 3.0.x. We use ApiDOM for OpenAPI 3.1.0. Given that you definition is in OpenAPI 3.0.x, SwaggerClient uses legacy mechanism which cannot cope with tracking the references on such a complex definition; we've addressed that in https://github.com/swagger-api/swagger-ui/commit/7300e6c04ec9248f0d7c5c4e0674572847fcd213 where we stop the dereferencing at some point in forced way.

Now ApiDOM can correctly dereference your definition and eliminate all cycles, but it takes around 25 seconds to do so (because of the definition complexity). Without the cycle detection (circular=ignore), ApiDOM can dereference the definition under 1 second.

ApiDOM in future will take over all the dereferencing in SwaggerClient, so it needs to utilize a solution similiar in https://github.com/swagger-api/swagger-ui/commit/7300e6c04ec9248f0d7c5c4e0674572847fcd213. In ApiDOM it's possible to interrupt the traversal in safe way without exposing the incompletely dereferenced definition for cycle occurence.

Rendering

SwaggerUI unfortunately uses Immutable.js for it's redux store. This means that every piece of JavaScript data that hits the redux store (and state respectively) needs to be serialized into immutable structure and then when it hits the component is (somethimes) deserialized againt to JavaScript value.

Now when you click on to expand the operation, all it's data get dereferenced (+ cycle elimination) which creates extremely large data in browser memory. This data needs to be then serialized into immutable structure. And this is where the browser crash happens - trying to serialize the deferenced operation into redux store.

Even when we would factor our the immutable from the equation, we still need to generate example values for your JSON schemas. That would be slow as well, again given the definition complexity.


This is just analysis of what is currently causing the performance issue on this definition (and others with similiar complexity). It's not one thing, it's a combination of multiple issues.

char0n commented 4 months ago

I've did additional measurements about Immutable.js serialization speed and memory footprint on this definition. I've used Node.js a following APIs for the measurement:

node:perf_hooks
performance.now()

Using pure JavaScript (without Immutable.js):

Memory consumption:

Before: 88,788,992 bytes (approximately 88.79 MB)
After: 155,521,024 bytes (approximately 155.52 MB)
Change: Increased by 66.73 MB

Serialization time:

0 ms

Using Immutable.js:

Memory consumption:

Before: 90,148,864 bytes (approximately 90.15 MB)
After: 1,044,697,088 bytes (approximately 1.04 GB)
Change: Increased by approximately 954.55 MB

Serialization time:

3163 ms
pavelkornev commented 4 months ago

@char0n would you consider dropping Immutable.js if it causes such a big memory consumption?

char0n commented 4 months ago

@char0n would you consider dropping Immutable.js if it causes such a big memory consumption?

Yes it's a long term plan. But it basically means rewriting the entire SwaggerUI. SwaggerUI basically consists of selectors, reducers, actions and components and Immutable.js is the interface that all of these bind to.

After we've removed Immutable.js we still have problem of dereferencing lasting 25 seconds on your definition. We can solve that by hard stopping the dereferencing at certain level.

Then the next issue is probably going to be example generation from JSON Schemas. Given the extreme schema complexity, this problem can be totally avoided by defining one of the JSON Schema keywords: default, example, examples in all your JSON Schemas.