mirror of https://codeberg.org/pzp/pzp-dict.git
load() is idempotent and safe in race conditions
This commit is contained in:
parent
dac32c30f1
commit
d44a047834
90
lib/index.js
90
lib/index.js
|
@ -81,7 +81,7 @@ function initDict(peer, config) {
|
|||
if (ghostSpan < 1) throw new Error('config.dict.ghostSpan must be >= 0')
|
||||
|
||||
//#region state
|
||||
let accountID = /** @type {string | null} */ (null)
|
||||
let loadedAccountID = /** @type {string | null} */ (null)
|
||||
let loadPromise = /** @type {Promise<void> | null} */ (null)
|
||||
let cancelOnRecordAdded = /** @type {CallableFunction | null} */ (null)
|
||||
const tangles = /** @type {Map<Subdomain, MsgV4.Tangle>} */ (new Map())
|
||||
|
@ -164,10 +164,10 @@ function initDict(peer, config) {
|
|||
*/
|
||||
function isValidDictMoot(msg) {
|
||||
if (!msg) return false
|
||||
if (msg.metadata.account !== accountID) return false
|
||||
if (msg.metadata.account !== loadedAccountID) return false
|
||||
const domain = msg.metadata.domain
|
||||
if (!domain.startsWith(PREFIX)) return false
|
||||
return MsgV4.isMoot(msg, accountID, domain)
|
||||
return MsgV4.isMoot(msg, loadedAccountID, domain)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,7 +178,7 @@ function initDict(peer, config) {
|
|||
function isValidDictMsg(msg) {
|
||||
if (!msg) return false
|
||||
if (!msg.data) return false
|
||||
if (msg.metadata.account !== accountID) return false
|
||||
if (msg.metadata.account !== loadedAccountID) return false
|
||||
if (!msg.metadata.domain.startsWith(PREFIX)) return false
|
||||
if (!msg.data.update) return false
|
||||
if (typeof msg.data.update !== 'object') return false
|
||||
|
@ -235,7 +235,7 @@ function initDict(peer, config) {
|
|||
* @param {Msg} msg
|
||||
*/
|
||||
function maybeLearnAboutDict(msgID, msg) {
|
||||
if (msg.metadata.account !== accountID) return
|
||||
if (msg.metadata.account !== loadedAccountID) return
|
||||
if (isValidDictMoot(msg)) {
|
||||
learnDictMoot(msgID, msg)
|
||||
return
|
||||
|
@ -262,10 +262,11 @@ function initDict(peer, config) {
|
|||
*/
|
||||
function _squeezePotential(subdomain) {
|
||||
assertDBPlugin(peer)
|
||||
if (!accountID) throw new Error('Cannot squeeze potential before loading')
|
||||
// prettier-ignore
|
||||
if (!loadedAccountID) throw new Error('Cannot squeeze potential before loading')
|
||||
// TODO: improve this so that the squeezePotential is the size of the
|
||||
// tangle suffix built as a slice from the fieldRoots
|
||||
const mootID = MsgV4.getMootID(accountID, fromSubdomain(subdomain))
|
||||
const mootID = MsgV4.getMootID(loadedAccountID, fromSubdomain(subdomain))
|
||||
const tangle = peer.db.getTangle(mootID)
|
||||
const maxDepth = tangle.maxDepth
|
||||
const fieldRoots = _getFieldRoots(subdomain)
|
||||
|
@ -286,7 +287,7 @@ function initDict(peer, config) {
|
|||
*/
|
||||
function forceUpdate(subdomain, update, cb) {
|
||||
assertDBPlugin(peer)
|
||||
if (!accountID) throw new Error('Cannot force update before loading')
|
||||
if (!loadedAccountID) throw new Error('Cannot force update before loading')
|
||||
const domain = fromSubdomain(subdomain)
|
||||
|
||||
// Populate supersedes
|
||||
|
@ -297,7 +298,7 @@ function initDict(peer, config) {
|
|||
}
|
||||
|
||||
peer.db.feed.publish(
|
||||
{ account: accountID, domain, data: { update, supersedes } },
|
||||
{ account: loadedAccountID, domain, data: { update, supersedes } },
|
||||
(err, rec) => {
|
||||
// prettier-ignore
|
||||
if (err) return cb(new Error('Failed to create msg when force-updating Dict', { cause: err }))
|
||||
|
@ -310,28 +311,42 @@ function initDict(peer, config) {
|
|||
|
||||
//#region public methods
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {string} accountID
|
||||
* @param {CB<void>} cb
|
||||
*/
|
||||
function load(id, cb) {
|
||||
function load(accountID, cb) {
|
||||
assertDBPlugin(peer)
|
||||
accountID = id
|
||||
if (accountID === loadedAccountID) {
|
||||
loaded(cb)
|
||||
return
|
||||
}
|
||||
if (loadedAccountID !== null) {
|
||||
// prettier-ignore
|
||||
cb(new Error(`Cannot load Dict for account "${accountID}" because Dict for account "${loadedAccountID}" is already loaded`))
|
||||
return
|
||||
}
|
||||
loadedAccountID = accountID
|
||||
loadPromise = new Promise((resolve, reject) => {
|
||||
for (const rec of peer.db.records()) {
|
||||
if (!rec.msg) continue
|
||||
maybeLearnAboutDict(rec.id, rec.msg)
|
||||
}
|
||||
cancelOnRecordAdded = peer.db.onRecordAdded(
|
||||
(/** @type {RecPresent} */ rec) => {
|
||||
try {
|
||||
maybeLearnAboutDict(rec.id, rec.msg)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
// microtask is needed to ensure that loadPromise is assigned BEFORE this
|
||||
// body is executed (which in turn does inversion of control when `cb` or
|
||||
// `resolve` is called)
|
||||
queueMicrotask(() => {
|
||||
for (const rec of peer.db.records()) {
|
||||
if (!rec.msg) continue
|
||||
maybeLearnAboutDict(rec.id, rec.msg)
|
||||
}
|
||||
)
|
||||
resolve()
|
||||
cb()
|
||||
cancelOnRecordAdded = peer.db.onRecordAdded(
|
||||
(/** @type {RecPresent} */ rec) => {
|
||||
try {
|
||||
maybeLearnAboutDict(rec.id, rec.msg)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
resolve()
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -339,7 +354,8 @@ function initDict(peer, config) {
|
|||
* @param {string} subdomain
|
||||
*/
|
||||
function _getFieldRoots(subdomain) {
|
||||
if (!accountID) throw new Error('Cannot getFieldRoots() before loading')
|
||||
// prettier-ignore
|
||||
if (!loadedAccountID) throw new Error('Cannot getFieldRoots() before loading')
|
||||
return fieldRoots.getAll(subdomain)
|
||||
}
|
||||
|
||||
|
@ -403,7 +419,7 @@ function initDict(peer, config) {
|
|||
const mootID = MsgV4.getMootID(id, domain)
|
||||
const tangle = peer.db.getTangle(mootID)
|
||||
if (!tangle || tangle.size === 0) {
|
||||
if (id === accountID) return {}
|
||||
if (id === loadedAccountID) return {}
|
||||
else return null
|
||||
}
|
||||
const msgIDs = tangle.topoSort()
|
||||
|
@ -424,10 +440,10 @@ function initDict(peer, config) {
|
|||
* @returns {string}
|
||||
*/
|
||||
function getFeedID(subdomain) {
|
||||
if (!accountID) throw new Error('Cannot getFeedID() before loading')
|
||||
if (!loadedAccountID) throw new Error('Cannot getFeedID() before loading')
|
||||
assertDBPlugin(peer)
|
||||
const domain = fromSubdomain(subdomain)
|
||||
return MsgV4.getMootID(accountID, domain)
|
||||
return MsgV4.getMootID(loadedAccountID, domain)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -474,11 +490,12 @@ function initDict(peer, config) {
|
|||
* @param {CB<boolean>} cb
|
||||
*/
|
||||
function update(subdomain, update, cb) {
|
||||
if (!accountID) return cb(new Error('Cannot update before loading'))
|
||||
if (!loadedAccountID) return cb(new Error('Cannot update before loading'))
|
||||
|
||||
loaded(() => {
|
||||
if (!accountID) return cb(new Error('Expected account to be loaded'))
|
||||
const dict = read(accountID, subdomain)
|
||||
// prettier-ignore
|
||||
if (!loadedAccountID) return cb(new Error('Expected account to be loaded'))
|
||||
const dict = read(loadedAccountID, subdomain)
|
||||
// prettier-ignore
|
||||
if (!dict) return cb(new Error(`Cannot update non-existent dict "${subdomain}`))
|
||||
|
||||
|
@ -499,13 +516,14 @@ function initDict(peer, config) {
|
|||
* @param {CB<boolean>} cb
|
||||
*/
|
||||
function squeeze(subdomain, cb) {
|
||||
if (!accountID) return cb(new Error('Cannot squeeze before loading'))
|
||||
if (!loadedAccountID) return cb(new Error('Cannot squeeze before loading'))
|
||||
const potential = _squeezePotential(subdomain)
|
||||
if (potential < 1) return cb(null, false)
|
||||
|
||||
loaded(() => {
|
||||
if (!accountID) return cb(new Error('Expected account to be loaded'))
|
||||
const dict = read(accountID, subdomain)
|
||||
// prettier-ignore
|
||||
if (!loadedAccountID) return cb(new Error('Expected account to be loaded'))
|
||||
const dict = read(loadedAccountID, subdomain)
|
||||
// prettier-ignore
|
||||
if (!dict) return cb(new Error(`Cannot squeeze non-existent Dict "${subdomain}"`))
|
||||
forceUpdate(subdomain, dict, (err, _forceUpdated) => {
|
||||
|
|
|
@ -42,6 +42,7 @@ test('setup', async (t) => {
|
|||
_nonce: 'alice',
|
||||
})
|
||||
await p(peer.dict.load)(aliceID)
|
||||
await p(peer.dict.load)(aliceID) // on purpose test that re-load is idempotent
|
||||
|
||||
peer.dict.setGhostSpan(4)
|
||||
assert.equal(peer.dict.getGhostSpan(), 4, 'getGhostSpan')
|
||||
|
|
Loading…
Reference in New Issue