redux-persist: Seeing outofmemory errors on ios in asyncstorage code

Hi, using react-native-persist 5.10.0 and react-native 0.59.9, I am seeing several crashes lately in our production app like:

Error · Out of memory
[native code]stringify	
node_modules/redux-persist/lib/createPersistoid.js:90:57writeStagedState	
node_modules/redux-persist/lib/createPersistoid.js:78:6processNextKey	
node_modules/react-native/Libraries/Core/Timers/JSTimers.js:152:6_callTimer	
node_modules/react-native/Libraries/Core/Timers/JSTimers.js:414:17callTimers	
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:366:47value	
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:106:26	
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:314:8value	
node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:105:17value

Could this be due to trying to store too much data in AsyncStorage? I am thinking of trying out https://github.com/bhanuc/react-native-fs-store at the RN level to drop-in this replacement for AsyncStorage … do you know if redux-persist would work with it?

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 9
  • Comments: 24

Most upvoted comments

I’m still chasing this … I updated everything to RN 0.61 (because aysnc storage was updated) and the symptoms changed (redux persist is no longer in the stack trace) but it does seem still related because I am updating state with every keystroke to a persisted reducer. My working theory is while doing this memory is consumed and not released fast enough (XCode profiling bears this out, although there does not appear to be a leak, just a surge in memory usage).

Just tried updating to redux-persist 6.0.0 and adding a throttle of 3000 to that reducer and explicitly flushing it when the state is cleared (to avoid it coming back due to throttle delay later). Testing now … it takes a while because this is not reliably reproducible.

Hope this helps; I will let you know what happens.

Same issue here. It seems like this problem happens when a device is really stressed by some large payload.

I think JSON.stringify used inside defaultSerialize function here https://github.com/rt2zz/redux-persist/blob/master/src/createPersistoid.js#L140

function defaultSerialize(data) {
  return JSON.stringify(data)
}

is the cause. When the data to serialize is too big, it fails.

My current workaround is to use kind of “best effort” strategy to serialize. It will not store new data or remove the existing data, just to avoid the app from crashing.

// try to serialize but quit if it was too much.
const serialize = (data) => {
  try{
    return JSON.stringify(data)
  }catch(err){
    captureHandledException(err) // for sentry
    return JSON.stringify({})
  }
}

const persistConfig = {
  key: 'root',
  version: 1,
  storage: AsyncStorage,
  serialize,
}

I use redux-persist 6.0.0, Expo SDK36, AsyncStorage from react-native.

I also check the stored data when initializing screen and remove excessive data, because this error was happening when there was too much data in the store.

Since this error is quite hard to replicate, so I haven’t tested this solution in the production yet. But I will update when I found out it fixed it or not.

I hope someone will come up with better solution though. Any suggestion is helpful.

@dchersey @summerkiflain just to follow up here - out of curiosity, I have tried swapping out redux-persist-expo-filesystem with SqlLite storage (redux-persist-sqlite-storage), and I am still seeing this error, so I know it’s not specific to the storage mechanism now. I’m on Expo SDK37 and redux-persist 5.10.0.

Facing same issue, but I am also using redux-persist-expo-filesystem as storage engine, I thought it did not had any size limitations. anyone having an idea whats it about or how to deal with it?

Thanks for the clues, @vovkasm. Here’s a workaround that removes the extra layer of serialization in stagedState by doing all the serialization in the storage layer instead:

import AsyncStorage from '@react-native-community/async-storage';

const storage = {
  ...AsyncStorage,
  getItem: async (key: string) => {
    const value = await AsyncStorage.getItem(key);
    if (value === null) {
      return null;
    }
    return JSON.parse(value);
  },
  setItem: async (key: string, value: mixed) =>
    AsyncStorage.setItem(key, JSON.stringify(value)),
};

const persistConfig = {
  key: 'root',
  version: 1,
  storage,
  serialize: false,
  deserialize: false,
};

I’m hoping this will reduce the out-of-memory errors I see in production.

I think this is a complex problem that cant be fixed in one place. But I can identify at least one place that effectively double memory consumption at persist times.

Here:

  1. let stagedState = {} This is an object that contains already serialized slices of state for each top-level key. They are not cleared, only set.
  2. stagedState[key] = serialize(endState) So after first write, application already have 2 copies of data - real data and serialized in stagedState, than it create 3rd copy by call serialize again and oops, OutOfMemory.

We in our application have state which persistent part contained in 4-5Mb json file. And sometimes we seen these crashes.

@summerkiflain when you set serialize & deserialize to false then you have to handle case in which existing data which is already deserialize

export const storage = {
    setItem: async (key, value) => {
      return set(key, value);
    },
    getItem: async key => {
      let value = await get(key);
      
      //for backward compatibility
      if (typeof value === 'string') {
        value = JSON.parse(value);
      }
  
      return value;
    },
    removeItem: async key => {
      await remove(key);
    },
};