Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak when using cache feature. #792

Closed
clement-buchart opened this issue May 3, 2019 · 7 comments · Fixed by #921
Closed

Memory leak when using cache feature. #792

clement-buchart opened this issue May 3, 2019 · 7 comments · Fixed by #921
Labels
bug Something does not work as it should external The issue related to an external project ✭ help wanted ✭

Comments

@clement-buchart
Copy link

Hello,

We are facing a memory leak in our production environment, using got and it's cache feature (with a KeyvRedis storage backend).

A heap dump revealed that most memory is retained by multiple error event listener on the single KeyvRedis instance.
Further analysis showed that for each request, a new CacheableRequest is instantiated :

if (options.cache) {
	const cacheableRequest = new CacheableRequest(fn.request, options.cache);
	const cacheRequest = cacheableRequest(options, handleResponse);

	cacheRequest.once('error', error => {
		if (error instanceof CacheableRequest.RequestError) {
			emitError(new RequestError(error, options));
		} else {
			emitError(new CacheError(error, options));
		}
	});

	cacheRequest.once('request', handleRequest);
}

Then, for each CacheableRequest, a new Keyv is instantiated :

this.cache = new Keyv({
	uri: typeof cacheAdapter === 'string' && cacheAdapter,
	store: typeof cacheAdapter !== 'string' && cacheAdapter,
	namespace: 'cacheable-request'
});

KeyvRedis being an EventEmitter, a eventListener is registered.

if (typeof this.opts.store.on === 'function') {
	this.opts.store.on('error', err => this.emit('error', err));
}

The full context of the request being retained by this event listener.

Is there something wrong with the way we use got ? Is there a way to prevent CacheableRequest to be instantiated for each request ?

We tried using got.create, but that didn't work.

Thanks.

@szmarczak szmarczak added external The issue related to an external project bug Something does not work as it should ✭ help wanted ✭ labels May 3, 2019
@szmarczak
Copy link
Collaborator

We already are aware of this, but this is not so simple to solve. It's possible to remove the initialization of CacheableRequest instance. If to take the request function out of the constructor and move it to the options instead, the only thing left to fix would be the Keyv initialization. There are two solutions here:

  1. Force users to make keyv instances on their own.
  2. Generate the instance on got.extend or got.create (the better one).

@szmarczak
Copy link
Collaborator

This is how it's done for cacheable-lookup:

if (options.dnsCache) {
const cacheableLookup = new CacheableLookup({cacheAdapter: options.dnsCache});
options.lookup = cacheableLookup.lookup;
delete options.dnsCache;
}

@szmarczak
Copy link
Collaborator

We also need to note in the README that dnsCache is used only when normalizing and it will translate to the lookup option.

@sindresorhus
Copy link
Owner

Seems like this can be closed now that jaredwray/cacheable#30 is closed?

@szmarczak
Copy link
Collaborator

Yeah... I think so. We could still improve this by pregenerating the instance on got.create()/got.extend() and regenerate if someone provides custom request or cache option. What do you think?

@AaronFriel
Copy link

@sindresorhus Could you release a 9.6.1 that uses cacheable-request 6.1.0?

@jburghardt
Copy link

A 9.6.1 release would be appreciated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something does not work as it should external The issue related to an external project ✭ help wanted ✭
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants