From e340e2b8764968d22a22bd67769676b9f2f1a2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Volf?= Date: Fri, 24 Nov 2023 11:30:37 +0100 Subject: [PATCH] fix: prevent race condition in KeyValueStore.getAutoSavedValue() (#2193) This was painful to discover the hard way :( Code to test this: ```js const store = await Actor.openKeyValueStore("test", { forceCloud: true }); const key = "something"; const res = await Promise.all([ store.getAutoSavedValue(key, { foo: [] }), new Promise((r) => setTimeout(r, Math.random() * 1000)).then(() => store.getAutoSavedValue(key, { foo: [] }) ), ]); res[0].foo.push("test"); console.log(res); ``` Run this a few times and at some point you will get two different values in the console.log. (Note that it's using `forceCloud` to make `this.GetValue()` run for a little longer.) --- packages/core/src/storages/key_value_store.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/src/storages/key_value_store.ts b/packages/core/src/storages/key_value_store.ts index 6d013a856ac4..ac01963aa9e9 100644 --- a/packages/core/src/storages/key_value_store.ts +++ b/packages/core/src/storages/key_value_store.ts @@ -227,6 +227,15 @@ export class KeyValueStore { } const value = await this.getValue(key, defaultValue); + + // The await above could have run in parallel with another call to this function. If the other call finished more quickly, + // the value will in cache at this point, and returning the new fetched value would introduce two different instances of + // the auto-saved object, and only the latter one would be persisted. + // Therefore we re-check the cache here, and if such race condition happened, we drop the fetched value and return the cached one. + if (this.cache.has(key)) { + return this.cache.get(key) as T; + } + this.cache.set(key, value!); this.ensurePersistStateEvent();