support deleting plus ghosting

This commit is contained in:
Andre Staltz 2023-10-26 13:00:40 +03:00
parent 597f3a3bce
commit c7dc78b859
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
4 changed files with 119 additions and 6 deletions

View File

@ -76,25 +76,37 @@ function initGC(peer, config) {
function cleanup(cb) {
assertDBExists(peer)
assertGoalsExists(peer)
debug('cleanup goalless started')
debug('cleanup-per-purpose started')
const done = multicb({ pluck: 1 })
let waiting = false
for (const rec of peer.db.records()) {
if (!rec.msg) continue
const purpose = peer.goals.getRecordPurpose(rec)
const { id: msgID, msg } = rec
const [purpose, details] = peer.goals.getMsgPurpose(msgID, msg)
if (purpose === 'none') {
peer.db.del(rec.id, done())
peer.db.del(msgID, done())
waiting = true
} else if (purpose === 'ghost') {
const { tangleID, span } = details
const cb = done()
// TODO: Could one msg be a ghostable in MANY tangles? Or just one?
peer.db.ghosts.add({ tangleID, msgID, span }, (err) => {
// prettier-ignore
if (err) return cb(new Error('gc failed to add ghost', { cause: err }))
peer.db.del(msgID, cb)
})
waiting = true
} else if (purpose === 'trail') {
peer.db.erase(rec.id, done())
peer.db.erase(msgID, done())
waiting = true
}
}
/** @param {Error=} err */
function whenEnded(err) {
// prettier-ignore
if (err) debug('cleanup goalless ended with an error %s', err.message ?? err)
else debug('cleanup goalless ended')
if (err) debug('cleanup-per-purpose ended with an error %s', err.message ?? err)
else debug('cleanup-per-purpose ended')
cb()
}
if (waiting) done(whenEnded)

View File

@ -35,6 +35,7 @@
"ppppp-db": "github:staltz/ppppp-db",
"ppppp-goals": "github:staltz/ppppp-goals",
"ppppp-keypair": "github:staltz/ppppp-keypair",
"ppppp-record": "github:staltz/ppppp-record",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3",
"rimraf": "^4.4.0",

View File

@ -0,0 +1,99 @@
const test = require('node:test')
const assert = require('node:assert')
const p = require('node:util').promisify
const { createPeer } = require('./util')
function getFields(msgs) {
return msgs
.map((msg) => msg.data?.update)
.filter((x) => !!x)
.map((x) => x.age ?? x.name)
}
function isErased(msg) {
return !msg.data
}
function isDeleted(msg) {
return !msg
}
function isPresent(msg) {
return !!msg.data.update
}
test('record ghosts', async (t) => {
const alice = createPeer({
name: 'alice',
gc: { maxLogBytes: 100 * 1024 * 1024 },
record: { ghostSpan: 2 },
})
await alice.db.loaded()
// Alice creates her own account
const aliceID = await p(alice.db.account.create)({
domain: 'account',
_nonce: 'alice',
})
// Alice constructs a record
await p(alice.record.load)(aliceID)
await p(alice.record.update)('profile', { name: 'alice' })
await p(alice.record.update)('profile', { age: 24 })
await p(alice.record.update)('profile', { name: 'Alice' })
await p(alice.record.update)('profile', { age: 25 })
await p(alice.record.update)('profile', { name: 'ALICE' })
const recordID = alice.record.getFeedID('profile')
let mootID
let msgID1
let msgID2
let msgID3
let msgID4
let msgID5
for (const rec of alice.db.records()) {
if (rec.msg.metadata.dataSize === 0) mootID = rec.id
if (rec.msg.data?.update?.name === 'alice') msgID1 = rec.id
if (rec.msg.data?.update?.age === 24) msgID2 = rec.id
if (rec.msg.data?.update?.name === 'Alice') msgID3 = rec.id
if (rec.msg.data?.update?.age === 25) msgID4 = rec.id
if (rec.msg.data?.update?.name === 'ALICE') msgID5 = rec.id
}
// Assert situation before GC
assert.deepEqual(
getFields([...alice.db.msgs()]),
['alice', 24, 'Alice', 25, 'ALICE'],
'has all record msgs'
)
assert.ok(isErased(alice.db.get(mootID)), 'moot by def erased')
assert.ok(isPresent(alice.db.get(msgID1)), 'msg1 exists')
assert.ok(isPresent(alice.db.get(msgID2)), 'msg2 exists')
assert.ok(isPresent(alice.db.get(msgID3)), 'msg3 exists')
assert.ok(isPresent(alice.db.get(msgID4)), 'msg4 exists')
assert.ok(isPresent(alice.db.get(msgID5)), 'msg5 exists')
// Perform garbage collection
alice.goals.set(aliceID, 'all')
alice.goals.set(recordID, 'record')
await p(alice.gc.forceImmediately)()
// Assert situation after GC
assert.deepEqual(
getFields([...alice.db.msgs()]),
[25, 'ALICE'],
'alice has only field root msgs'
)
assert.ok(isErased(alice.db.get(mootID)), 'moot by def erased')
assert.ok(isDeleted(alice.db.get(msgID1)), 'msg1 deleted')
assert.ok(isDeleted(alice.db.get(msgID2)), 'msg2 deleted') // ghost!
assert.ok(isErased(alice.db.get(msgID3)), 'msg3 erased')
assert.ok(isPresent(alice.db.get(msgID4)), 'msg4 exists')
assert.ok(isPresent(alice.db.get(msgID5)), 'msg5 exists')
assert.deepEqual(alice.db.ghosts.get(recordID), [msgID2])
await p(alice.close)(true)
})

View File

@ -18,6 +18,7 @@ function createPeer(opts) {
.use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack'))
.use(require('ppppp-db'))
.use(require('ppppp-record'))
.use(require('ppppp-goals'))
.use(require('ssb-box'))
.use(require('../lib'))