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

Cache and pMemoizeClear bug #55

Open
nikashitsa opened this issue Jun 20, 2023 · 8 comments
Open

Cache and pMemoizeClear bug #55

nikashitsa opened this issue Jun 20, 2023 · 8 comments

Comments

@nikashitsa
Copy link

Hi, great module!

I found interesting bug with pMemoizeClear and cache.
Here is example:

import ExpiryMap from 'expiry-map'; // [email protected]
import pMemoize, { pMemoizeClear } from 'p-memoize'; // [email protected]

const memoizedFn = pMemoize(async (str) => str, {
  cache: new ExpiryMap(10000),
});

await memoizedFn('something');

const [value] = await Promise.all([
  memoizedFn('something'),
  pMemoizeClear(memoizedFn),
]);

console.log(value); // undefined

It's connected with these two lines

p-memoize/index.ts

Lines 104 to 105 in 52fe605

if (cache && await cache.has(key)) {
return (await cache.get(key))!;

if I remove await in if condition, result will be something as expected.

I'm not sure how to fix this issue, perhaps you have better ideas than me 😉

@sindresorhus
Copy link
Owner

Probably related to #36

@nikashitsa
Copy link
Author

@sindresorhus This bug happens because between two await-s
if (cache && await cache.has(key)) {
and
return (await cache.get(key))!;
cache cleaning function was executed. In asynchronous app it could happen easily.
Also ExpiryMap could clean cache right after await cache.has(key) returns true.

@sindresorhus
Copy link
Owner

Yes. The only solution I can think of is to have some kind of lock to make sure that no other cache operation are executed during that time.

@sindresorhus
Copy link
Owner

sindresorhus commented Jun 20, 2023

I wonder if something like this would solve it:

if (cache && await cache.has(key)) {
	const [has, value] = await Promise.all([cache.has(key), cache.get(key)]);

	if (has) {
		return value;
	}
}

It would call has twice, but I don't think that's too bad.

@nikashitsa
Copy link
Author

Yep, this is working solution. has() and get() for synchronous cache are executed in [] one by one, so there is no chance for clearing cache function to execute.

For my project calling has twice is fine. But for those who use async remote cache, calling has one more time it will make one more network request (not sure how many such users).

@fregante
Copy link
Collaborator

I’m not sure this is a bug. You’re starting an async operation and immediately aborting it before it resolved.

The only issue I see is that this probably leads to a race condition between cached calls and new calls.

@nikashitsa
Copy link
Author

@fregante this bug for async app like web server with multiple parallel incoming requests.
Or in case if setInterval is executing pMemoizeClear every X minutes while other part of app is using memoizedFn for random requests.

@fregante
Copy link
Collaborator

fregante commented Jul 1, 2023

You want pending checks to be treated differently from completed checks. I’d classify this as a feature request rather than a bug.

If I call “clear cache” I expect the cache to be cleared. Imagine if a pending browser request reinstated the previous cookie after the user cleared the cache, finding themselves still logged in.

fetch as @foo           response sets @foo cookie
|- - - - - - - - - - — -|

          clear cache
          |- - - -|

                         fetch as @foo
                         |- - -

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants