1
0
Fork 0

Fix existingPartZero assertion error

This commit is contained in:
Cadence Ember 2024-11-02 20:35:52 +13:00
parent 15e5b17b0d
commit 07d6eb3c12
3 changed files with 47 additions and 18 deletions

View file

@ -12,6 +12,7 @@ function debugRetrigger(message) {
} }
} }
const paused = new Set()
const emitter = new EventEmitter() const emitter = new EventEmitter()
/** /**
@ -25,13 +26,15 @@ const emitter = new EventEmitter()
* @returns {boolean} false if the event was found and the function will be ignored, true if the event was not found and the function will be retriggered * @returns {boolean} false if the event was found and the function will be ignored, true if the event was not found and the function will be retriggered
*/ */
function eventNotFoundThenRetrigger(messageID, fn, ...rest) { function eventNotFoundThenRetrigger(messageID, fn, ...rest) {
const eventID = select("event_message", "event_id", {message_id: messageID}).pluck().get() if (!paused.has(messageID)) {
if (eventID) { const eventID = select("event_message", "event_id", {message_id: messageID}).pluck().get()
debugRetrigger(`[retrigger] OK mid <-> eid = ${messageID} <-> ${eventID}`) if (eventID) {
return false // event was found so don't retrigger debugRetrigger(`[retrigger] OK mid <-> eid = ${messageID} <-> ${eventID}`)
return false // event was found so don't retrigger
}
} }
debugRetrigger(`[retrigger] WAIT mid <-> eid = ${messageID} <-> ${eventID}`) debugRetrigger(`[retrigger] WAIT mid = ${messageID}`)
emitter.once(messageID, () => { emitter.once(messageID, () => {
debugRetrigger(`[retrigger] TRIGGER mid = ${messageID}`) debugRetrigger(`[retrigger] TRIGGER mid = ${messageID}`)
fn(...rest) fn(...rest)
@ -46,6 +49,25 @@ function eventNotFoundThenRetrigger(messageID, fn, ...rest) {
return true // event was not found, then retrigger return true // event was not found, then retrigger
} }
/**
* Anything calling retrigger during the callback will be paused and retriggered after the callback resolves.
* @template T
* @param {string} messageID
* @param {Promise<T>} promise
* @returns {Promise<T>}
*/
async function pauseChanges(messageID, promise) {
try {
debugRetrigger(`[retrigger] PAUSE mid = ${messageID}`)
paused.add(messageID)
return await promise
} finally {
debugRetrigger(`[retrigger] RESUME mid = ${messageID}`)
paused.delete(messageID)
messageFinishedBridging(messageID)
}
}
/** /**
* Triggers any pending operations that were waiting on the corresponding event ID. * Triggers any pending operations that were waiting on the corresponding event ID.
* @param {string} messageID * @param {string} messageID
@ -59,3 +81,4 @@ function messageFinishedBridging(messageID) {
module.exports.eventNotFoundThenRetrigger = eventNotFoundThenRetrigger module.exports.eventNotFoundThenRetrigger = eventNotFoundThenRetrigger
module.exports.messageFinishedBridging = messageFinishedBridging module.exports.messageFinishedBridging = messageFinishedBridging
module.exports.pauseChanges = pauseChanges

View file

@ -122,35 +122,41 @@ async function editToChanges(message, guild, api) {
eventsToReplace = eventsToReplace.filter(eventCanBeEdited) eventsToReplace = eventsToReplace.filter(eventCanBeEdited)
// We want to maintain exactly one part = 0 and one reaction_part = 0 database row at all times. // We want to maintain exactly one part = 0 and one reaction_part = 0 database row at all times.
// This would be disrupted if existing events that are (reaction_)part = 0 will be redacted.
// If that is the case, pick a different existing or newly sent event to be (reaction_)part = 0.
/** @type {({column: string, eventID: string, value?: number} | {column: string, nextEvent: true})[]} */ /** @type {({column: string, eventID: string, value?: number} | {column: string, nextEvent: true})[]} */
const promotions = [] const promotions = []
for (const column of ["part", "reaction_part"]) { for (const column of ["part", "reaction_part"]) {
const candidatesForParts = unchangedEvents.concat(eventsToReplace) const candidatesForParts = unchangedEvents.concat(eventsToReplace)
// If no events with part = 0 exist (or will exist), we need to do some management. // If no events with part = 0 exist (or will exist), we need to do some management.
if (!candidatesForParts.some(e => e.old[column] === 0)) { if (!candidatesForParts.some(e => e.old[column] === 0)) {
// Try to find an existing event to promote. Bigger order is better.
if (candidatesForParts.length) { if (candidatesForParts.length) {
// We can choose an existing event to promote. Bigger order is better.
const order = e => 2*+(e.event_type === "m.room.message") + 1*+(e.old.event_subtype === "m.text") const order = e => 2*+(e.event_type === "m.room.message") + 1*+(e.old.event_subtype === "m.text")
candidatesForParts.sort((a, b) => order(b) - order(a)) candidatesForParts.sort((a, b) => order(b) - order(a))
if (column === "part") { if (column === "part") {
promotions.push({column, eventID: candidatesForParts[0].old.event_id}) // part should be the first one promotions.push({column, eventID: candidatesForParts[0].old.event_id}) // part should be the first one
} else if (eventsToSend.length) {
promotions.push({column, nextEvent: true}) // reaction_part should be the last one
} else { } else {
promotions.push({column, eventID: candidatesForParts[candidatesForParts.length - 1].old.event_id}) // reaction_part should be the last one promotions.push({column, eventID: candidatesForParts[candidatesForParts.length - 1].old.event_id}) // reaction_part should be the last one
} }
} else { }
// No existing events to promote, but new events are being sent. Whatever gets sent will be the next part = 0. // Or, if there are no existing events to promote and new events will be sent, whatever gets sent will be the next part = 0.
else {
promotions.push({column, nextEvent: true}) promotions.push({column, nextEvent: true})
} }
} }
// If adding events, try to keep reactions attached to the bottom of the group (unless reactions have already been added) }
if (eventsToSend.length && !promotions.length) {
const existingReaction = select("reaction", "message_id", {message_id: message.id}).pluck().get() // If adding events, try to keep reactions attached to the bottom of the group (unless reactions have already been added)
if (!existingReaction) { if (eventsToSend.length && !promotions.length) {
const existingPartZero = candidatesForParts.find(p => p.old.reaction_part === 0) const existingReaction = select("reaction", "message_id", {message_id: message.id}).pluck().get()
assert(existingPartZero) // will exist because a reaction_part=0 always exists and no events are being removed if (!existingReaction) {
promotions.push({column: "reaction_part", eventID: existingPartZero.old.event_id, value: 1}) // update the current reaction_part to 1 const existingPartZero = unchangedEvents.concat(eventsToReplace).find(p => p.old.reaction_part === 0)
promotions.push({column: "reaction_part", nextEvent: true}) // the newly created event will have reaction_part = 0 assert(existingPartZero) // will exist because a reaction_part=0 always exists and no events are being removed
} promotions.push({column: "reaction_part", eventID: existingPartZero.old.event_id, value: 1}) // update the current reaction_part to 1
promotions.push({column: "reaction_part", nextEvent: true}) // the newly created event will have reaction_part = 0
} }
} }

View file

@ -304,7 +304,7 @@ module.exports = {
assert(guild) assert(guild)
// @ts-ignore // @ts-ignore
await editMessage.editMessage(message, guild, row) await retrigger.pauseChanges(message.id, editMessage.editMessage(message, guild, row))
}, },
/** /**