destr icon indicating copy to clipboard operation
destr copied to clipboard

Memory leak in `destr` package, Nuxt 3 production usage

Open mrauhu opened this issue 2 years ago • 8 comments

Hello, @pi0.

I found a memory leak, that affects Nuxt 3 framework, because the destr package is used as default parseResponse option in the ohmyfetch, that used in the Nuxt 3 useFetch() function as $fetch.

Memory leak

Flamegraph

image

Table

image

Memory usage

image

Temporary fix for Nuxt 3

  1. Create a local TypeScript file (lib/use-fetch.ts) with copy of useFetch() function.

  2. Add custom parseResponse() function:

    import type { FetchOptions } from 'ohmyfetch';
    
    /**
     * Parse response
     * @description Replace of `destr()` function to prevent memory leak
     * @param responseText
     */
    const parseResponse: FetchOptions['parseResponse'] = (responseText) => {
      // Send empty string if zero length
      if (responseText.length === 0) {
        return '';
      }
    
      try {
        return JSON.parse(responseText);
      } catch (_e) {
        return responseText;
      }
    };
    
  3. Replace _fetchOptions constant in the copy of useFetch() function:

      const _fetchOptions = {
        ...opts,
        parseResponse,
        cache: typeof opts.cache === 'boolean' ? undefined : opts.cache
      }
    
  4. Use patched useFetch() function instead of build-in.

Because, right now there is no way to provide custom parseResponse() function as option of the useFetch() without error.

Best wishes, Sergey.

mrauhu avatar Jun 29 '22 12:06 mrauhu

Hi @mrauhu thanks for making the issue and providing a workaround but are you sure the memory leak originates from destr and memory is not back after a while with GC? Can you please provide a minimal reproduction? 🙏🏼 (without nuxt but a direct Node.js repo using destr)

pi0 avatar Jun 29 '22 15:06 pi0

Any update on this?

pi0 avatar Jul 06 '22 18:07 pi0

Any update on this?

@pi0 unfortunately, I can't create a reproduction in the isolated environment only for destr package.

Changes after replacing destr() to JSON.parse() function for the production server, a week of observations:

$fetch parseResponse option Max memory usage, MB
destr() 509
JSON.parse() 112

Memory usage

image

Flamegraph

image

Table

image

mrauhu avatar Jul 06 '22 20:07 mrauhu

Higher memory usage is likely the overhead of regex checkers over a heavy payload. What happens after max memory usage? Do you auto restart? Also what is prod Node.js version?

pi0 avatar Jul 06 '22 21:07 pi0

Higher memory usage is likely the overhead of regex checkers over a heavy payload.

API responses is nested tree JSON documents, 50—100 KB size.

What happens after max memory usage?

Restart of a container.

Do you auto restart?

Yes.

Also what is prod Node.js version?

Node.js 16.14.2, a Docker image based on the https://github.com/astefanutti/scratch-node.

mrauhu avatar Jul 06 '22 21:07 mrauhu

@mrauhu with which application did you examine the memory leak with?

dargmuesli avatar Jul 06 '22 22:07 dargmuesli

@dargmuesli it's part of the Datadog APM.

The dd-trace-js package was used for tracing. Under the hood it's using the @datadog/pprof package, fork of pprof.

The Datadog Continuous Profiler used for visualization.

mrauhu avatar Jul 06 '22 23:07 mrauhu

@mrauhu Might I ask how you implemented the dd-trace-js in your application? Did you use a module or a server plugin? Thanks!

mcstover avatar Sep 06 '22 19:09 mcstover

Hi. Kindly this issue still needs a minimal reproduction. Please ping to reopen if you still believe there is a possible memory leak in destr itself.

pi0 avatar Jun 12 '23 12:06 pi0