mswjs / data

Data modeling and relation library for testing JavaScript applications.
https://npm.im/@mswjs/data
MIT License
829 stars 52 forks source link

"Warning: Possible EventEmitter memory leak detected" occured when define many models #112

Closed ma-hiro closed 3 years ago

ma-hiro commented 3 years ago

First, thanks for this awesome library. I'm using this library in my project and it's very useful.

I have defined a number of models using factory(), but when it exceeds 11, a warning occurs in chrome. Is there a way to prevent this?

Describe

If I define 11 or more models using factory(), the following warning will be displayed.

events.js:46 MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 delete listeners added.
Use emitter.setMaxListeners() to increase limit

This is displayed when you define the model, and even more when you call model.create(). (As for the event, only a warning is displayed, and the mock function works normally)

Example project

https://github.com/ma-hiro/sample-msw-data

When I launch the app and access http://localhost:3000, I get a lot of warnings.

This code is the cause of the warning. If I try removing some models, the warning disappears.

It can be suppressed by setting the EventEmitter's maxListener, but the EventEmmiter in this library is probably cannot be referenced externally. Therefore, we are tentatively dealing with the following.

import { EventEmitter } from 'events';
EventEmitter.defaultMaxListeners = 11; // Number of models you want to define or 0(infinity)

However, since this setting affects all EventEmitters, I would like to use setMaxListeners() to set it only for specific Emitters.

juusot commented 3 years ago

@ma-hiro I can confirm. I experienced the same issue in my project.

I tried different ways of going around this problem, but I couldn't find a solution.

kettanaito commented 3 years ago

Hey, @ma-hiro. Thank you for reporting this!

I'll look into the reproduction repository you've posted and update this thread. I don't have any ETA as to when this is going to be fixed. Anybody is welcome to investigate alongside me, as it's most likely an issue of inefficiently added event listeners or a missing listeners cleanup.

JoseRFelix commented 3 years ago

@juusot I encountered this same issue. Since it only occurs when instantiating 10 Event Emitter listeners, we can avoid this by creating a factory for every 10 schemas.

I created a quick abstraction for this:

import { factory } from "@mswjs/data"
import { FactoryAPI } from "@mswjs/data/lib/glossary"

schemas = { /* Your schemas go here */ }

function chunkArray<T>(array: T[], size: number): T[][] {
  const result = []
  const arrayCopy = [...array]

  while (arrayCopy.length > 0) {
    result.push(arrayCopy.splice(0, size))
  }

  return result
}

function getTestDb() {
  const FACTORY_CHUNK_SIZE = 10
  const INITIAL_DB = {}

  // For each ten items add a new factory. This is required to avoid browser event emitter limits
  const chunkedSchemas = chunkArray(Object.entries(schemas), FACTORY_CHUNK_SIZE)

  const db = chunkedSchemas.reduce((acc, schema) => {
    const dbFactory = factory(Object.fromEntries(schema))
    return { ...acc, ...dbFactory }
  }, INITIAL_DB as FactoryAPI<typeof schemas>)

  return db
}
kettanaito commented 3 years ago

Investigation

https://github.com/mswjs/data/blob/bce762c1f456fed8e19876e61491fdb25dacb15c/src/extensions/sync.ts#L76-L78

I believe there is no issue per se, just that the event emitter finds it suspicious that 33 listeners were added to it (11 for each event: create, update, delete) and warns us that may be the issue.

I'll look into the logic, perhaps we can optimize it, or perhaps the solution is to set the maximum number of listeners on the db.events since the number of models is fixed and cannot be changed after the factory() call.

There's certainly room for optimization, as there may be 3 listeners total added to the entire Database instance to handle all create/update/delete operations, instead of attaching listeners to each model.

kettanaito commented 3 years ago

The issue has been resolved in #115. Published in 0.5.1. Please update and let me know that it's gone. Thanks.

ma-hiro commented 3 years ago

Updating to 0.5.1 solved my problem. Thank you for your quick and awesome support!