apollographql / apollo-cache-persist

🎏 Simple persistence for all Apollo Cache implementations
MIT License
1.39k stars 118 forks source link

🐛 'Couldn't read row 0, col 0 from CursorWindow'; 'Error restoring cache' #92

Closed ulfgebhardt closed 3 years ago

ulfgebhardt commented 5 years ago

Using this Library with React-Native on Android following error can be thrown

03-22 20:01:34.367 5204-5259/? W/unknown:ReactNative: Couldn't read row 0, col 0 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.
                                                      java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.
                                                          at android.database.CursorWindow.nativeGetString(Native Method)
                                                          at android.database.CursorWindow.getString(CursorWindow.java:438)
                                                          at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:66)
                                                          at com.facebook.react.modules.storage.AsyncStorageModule$1.doInBackgroundGuarded(AsyncStorageModule.java:162)
                                                          at com.facebook.react.modules.storage.AsyncStorageModule$1.doInBackgroundGuarded(AsyncStorageModule.java:129)
                                                          at com.facebook.react.bridge.GuardedAsyncTask.doInBackground(GuardedAsyncTask.java:32)
                                                          at com.facebook.react.bridge.GuardedAsyncTask.doInBackground(GuardedAsyncTask.java:20)
                                                          at android.os.AsyncTask$2.call(AsyncTask.java:295)
                                                          at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                                                          at com.facebook.react.modules.storage.AsyncStorageModule$SerialExecutor$1.run(AsyncStorageModule.java:64)
                                                          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
                                                          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
                                                          at java.lang.Thread.run(Thread.java:818)
03-22 20:01:34.374 5204-5235/? E/ReactNativeJS: '[apollo-cache-persist]', 'Error restoring cache', { [Error: Couldn't read row 0, col 0 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.]
                                                  key: undefined,
                                                  line: 489,
                                                  column: 481,
                                                  sourceURL: 'index.android.bundle' }
03-22 20:01:34.384 5204-5236/? E/unknown:ReactNative: console.error: "[apollo-cache-persist]", "Error restoring cache", {"line":489,"column":481,"sourceURL":"index.android.bundle"}, stack:
                                                      emit@486:354
                                                      write@486:674
                                                      error@486:993
                                                      <unknown>@489:4202
                                                      f@489:1377
                                                      <unknown>@489:709
                                                      l@489:296
                                                      f@114:155
                                                      <unknown>@114:882
                                                      <unknown>@117:1815
                                                      y@117:657
                                                      C@117:1021
                                                      callImmediates@117:3216
                                                      value@45:3011
                                                      <unknown>@45:1402
                                                      value@45:2703
                                                      value@45:1372
                                                      value@45:1307

Even tho the Readme recognizes this Error the given Solution is not working - the error occurs regardless of a maxSize.

I'm seeing errors on Android.
Specifically, this error:

BaseError: Couldn't read row 0, col 0 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.

This is the result of a 2 MB per key limitation of AsyncStorage on Android. Set a smaller maxSize or switch to a filesystem-based storage provider, such as redux-persist-fs-storage.

The correct Solution is to wrap the whole thing in a try-catch:

// Restore cache if possible
  try {
    // Might throw Couldn't read row 0, col 0 from CursorWindow
    await persistor.restore();
  } catch (err) {
    // CachePersistor failed - purge
    await persistor.purge();
  }

Therefore I advise to document this or adjust the interface of persistor.restore() to not throw.

Ref: https://github.com/demokratie-live/democracy-client/blob/master/index.js#L213

bureyburey commented 4 years ago

Thought this might help. I've been also encountering this issue and trying file system based solutions such as react-native-fs-store and redux-persist-fs-storage were performing too slow.

the current solution i've come up with, was a custom wrapper around AsyncStorage that splits the large persisted object (key => 'apollo-cache-persist' by default)

this is the wrapper SplitAsyncStorage

keep in mind it is not thoroughly tested but it does seem to perform much better than the file system alternatives. you still may reach the 2MB limit in an extreme case that one of the inner values is large enough, tho the chances of it happening seems significantly lower.

wodCZ commented 4 years ago

Hi, just a tip here - try using react-native-mmkv-storage - it doesn't have the 2MB limitation and from my testing it performs better than filesystem alternatives.

I've updated the readme in https://github.com/apollographql/apollo-cache-persist/pull/350.

@bureyburey I suppose MMKV could be more performant than parsing and serializing the cache again in the wrapper.

bureyburey commented 4 years ago

@wodCZ I've actually tried it as soon as i've seen the #350 but couldn't make it work (the api is somewhat different from AsyncStorage). I tried making a wrapper around it that uses the getMapAsync and setMapAsync methods but ended up having the app crashing on startup.

Would to have any pointers regarding proper integration of it :)

wtrocki commented 4 years ago

@wodCZ this sounds very interesting. I will see if we can land minimal react native sample application with the best storage. I think no one cares about storages itself - people want solution for the platforms. TBH.. I would be more keen to ditch async-storage because it is actually done as two different packages (expo and community) which is total mess. Having performant, reliable storage picked for platform is good, but do not feel strong enough to propose one without driving it to production.

wtrocki commented 4 years ago

@bureyburey Just seen your message. If we get some simple app we can figure out wrapper and even publish it as separate package that will provide great experience for the react native (as web seems to be working fine. Then we can even refine overal storage interface so we support this two platforms properly.

bureyburey commented 4 years ago

@wtrocki seems Expo adopted @react-native-community/async-storage

https://docs.expo.io/versions/latest/sdk/async-storage/

wtrocki commented 4 years ago

@bureyburey Yep. Despite that I still get people using old versions saying that things do not work :D

bureyburey commented 4 years ago

@wtrocki sounds great! :)

wodCZ commented 4 years ago

@bureyburey

The following works just fine for me - a simple drop-in replacement. I've noticed AsyncStorage had some API changes, maybe I'm bringing some confusion here since I'm not using latest versions especially of apollo.

    "apollo-client": "^2.6.10",
    "apollo-cache-inmemory": "^1.6.6",
    "apollo-cache-persist": "^0.1.1",
    "react-native-mmkv-storage": "^0.3.7",
import {InMemoryCache} from 'apollo-cache-inmemory';
import {CachePersistor} from 'apollo-cache-persist';
import MMKVStorage from 'react-native-mmkv-storage';

export const apolloCache = new InMemoryCache();

const MMKV = new MMKVStorage.Loader().withInstanceID('apolloCache').initialize();

export const cachePersistor = new CachePersistor({
  trigger: 'background',
  cache: apolloCache,
  storage: MMKV,
  maxSize: false,
  debug: __DEV__,
});

@wtrocki We're stuck at Apollo v2 for foreseeable future. I will try to make a sample project with Apollo 3, but I'm a bit busy these days.

Notably I'd like to give a try to saving the cache as a native object/map (https://github.com/apollographql/apollo-cache-persist/pull/350#issuecomment-698304329), I believe skipping the need of JSON.stringify/parse would bring a nice performance boost, especially with large cache (we're currently at about 9mb JSON, Apollo cache can get pretty greedy 🙄).

bureyburey commented 4 years ago

@wodCZ many thanks! I will try this ASAP

EDIT Looking good 🤘 The docs does not include the getItem/setItem methods which made me try making a wrapper that failed miserably 😅

Working great now, many thanks 🍻

wtrocki commented 4 years ago

I think we have already sample APP PR from community member. I will look into adding things to it: https://github.com/apollographql/apollo-cache-persist/pull/357

jspizziri commented 3 years ago

It looks like this issue has been solved. Please let me know if it needs to be opened back up.