Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
78eef25
createPersistor [nfc]: Use a stateful `writeInProgress` flag.
chrisbobbe Jul 7, 2021
2773165
createPersistor: Simplify "paused" logic a bit.
chrisbobbe Jul 7, 2021
2d1cd38
createPersistor: Stop blending snapshots when we initiate writes.
chrisbobbe Jul 7, 2021
465d68d
createPersistor [nfc]: Rename `storesToProcess` to `updatedSubstates`.
chrisbobbe Jul 7, 2021
634ed0e
createPersistor: Rewrite a loop with `while` and `await`.
chrisbobbe Jul 7, 2021
3cc901b
createPersistor: Clean up a `while` loop, using a loop condition.
chrisbobbe Jul 7, 2021
b54737f
createPersistor: Include more code in an async IIFE.
chrisbobbe Jul 7, 2021
7faa494
createPersistor: Separate serializing from writing.
chrisbobbe Jul 7, 2021
6ac8628
createPersistor: Use `storage.multiSet` instead of many `.setItem`s.
chrisbobbe Jul 7, 2021
add4a71
createPersistor: Await `storage.multiSet` before marking state as wri…
chrisbobbe Jul 7, 2021
5aecb80
createPersistor [nfc]: Define `writeOnce`, with a jsdoc.
chrisbobbe Jul 7, 2021
43af58d
createPersistor [nfc]: Tidy up some code, and comment.
chrisbobbe Jul 7, 2021
e48a8eb
createPersistor: Move a yield from before to after serializing.
chrisbobbe Jul 7, 2021
db03650
createPersistor [nfc]: Tidy up some more code, and comment.
chrisbobbe Jul 7, 2021
cfd289b
createPersistor [nfc]: Comment some more.
chrisbobbe Jul 7, 2021
2694989
createPersistor: Stop ignoring state changes that happen during a write.
chrisbobbe Jul 7, 2021
6ce79a2
createPersistor [nfc]: Inline a complicated helper function we use once.
chrisbobbe Jul 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 83 additions & 41 deletions src/third/redux-persist/createPersistor.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,43 +30,83 @@ export default function createPersistor (store, config) {
const storage = config.storage;

// initialize stateful values
let lastState = {}
let lastWrittenState = {}
let paused = false
let storesToProcess = []
let timeIterator = null
let writeInProgress = false

store.subscribe(() => {
if (paused) return

const state = store.getState()

Object.keys(state).forEach((key) => {
if (!passWhitelistBlacklist(key)) return
if (lastState[key] === state[key]) return
if (storesToProcess.indexOf(key) !== -1) return
storesToProcess.push(key)
})

const len = storesToProcess.length

// time iterator (read: debounce)
if (timeIterator === null) {
timeIterator = setInterval(() => {
if ((paused && len === storesToProcess.length) || storesToProcess.length === 0) {
clearInterval(timeIterator)
timeIterator = null
return
}
if (paused || writeInProgress) return;

write();
})

async function write() {
// Take the lock.
writeInProgress = true;
// Then yield so the `subscribe` callback can promptly return.
await new Promise(r => setTimeout(r, 0));

try {
let state = undefined;
// eslint-disable-next-line no-cond-assign
while ((state = store.getState()) !== lastWrittenState) {
await writeOnce(state);
}
} finally {
// Release the lock, so the next `subscribe` callback will start the
// loop again.
writeInProgress = false;
}
}

const key = storesToProcess.shift()
const storageKey = createStorageKey(key)
const endState = store.getState()[key]
storage.setItem(storageKey, serializer(endState)).catch(warnIfSetError(key))
}, 0)
/**
* Update the storage to the given state.
*
* The storage is assumed to already reflect `lastWrittenState`.
* On completion, sets `lastWrittenState` to `state`.
*/
async function writeOnce(state) {
// Atomically collect the subtrees that need to be written out.
const updatedSubstates = [];
for (const key of Object.keys(state)) {
if (!passWhitelistBlacklist(key)) {
continue;
}
if (state[key] === lastWrittenState[key]) {
continue;
}
updatedSubstates.push([key, state[key]]);
}

lastState = state
})
// Serialize those subtrees, with yields after each one.
const writes = []
for (const [key, substate] of updatedSubstates) {
writes.push([key, serializer(substate)])
await new Promise(r => setTimeout(r, 0));
}

if (writes.length > 0) { // `multiSet` doesn't like an empty array
// Write them all out, in one `storage.multiSet` operation.
try {
// Warning: not guaranteed to be done in a transaction.
await storage.multiSet(
writes.map(([key, serializedSubstate]) => [createStorageKey(key), serializedSubstate])
)
} catch (e) {
logging.warn(
e,
{
message: 'An error was encountered while trying to persist this set of keys',
keys: writes.map(([key, _]) => key)
}
);
throw e
}
}

// Record success.
lastWrittenState = state
}

function passWhitelistBlacklist (key) {
if (whitelist && whitelist.indexOf(key) === -1) return false
Expand Down Expand Up @@ -104,16 +144,18 @@ export default function createPersistor (store, config) {
resume: () => { paused = false },
purge: (keys) => purgeStoredState({storage, keyPrefix}, keys),

// Only used in `persistStore`, to force `lastState` to update
// with the results of `REHYDRATE` even when the persistor is
// paused.
_resetLastState: () => { lastState = store.getState() }
}
}

function warnIfSetError (key) {
return function setError (err) {
if (err) { logging.warn(err, { message: 'Error storing data for key:', key }) }
/**
* Set `lastWrittenState` to the current `store.getState()`.
*
* Only to be used in `persistStore`, to force `lastWrittenState` to
* update with the results of `REHYDRATE` even when the persistor is
* paused.
*
* If this is going to be called, it should be before any writes have
* begun. Otherwise it may not be effective; see
* https://github.com/zulip/zulip-mobile/pull/4694#discussion_r691739007.
*/
_resetLastWrittenState: () => { lastWrittenState = store.getState() }
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/third/redux-persist/persistStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ export default function persistStore (store, config = {}, onComplete) {
//
// So, fix that by still resetting `lastState` with the
// result of `REHYDRATE` when the persistor is paused; we
// can do that because we've exposed `_resetLastState` on
// can do that because we've exposed `_resetLastWrittenState` on
// the persistor.
persistor._resetLastState()
persistor._resetLastWrittenState()
}
} finally {
complete(err, restoredState)
Expand Down