fix: eliminate racing saveData() calls during message restore
When onOpen() restored saved messages, each appendMessage() call fired a fire-and-forget saveMessages() → saveData(). With N saved messages, N overlapping saveData() writes raced against each other. If a stale write landed last, the persisted data could be corrupted or appear empty on next restart. Fix: add _restoring flag. Skip saveMessages() inside appendMessage() while restoring. Do a single clean save after the restore loop. Also wraps the restore loop in try/catch so a rendering failure in one message doesn't silently kill the entire restore.
This commit is contained in:
parent
0cebcf6084
commit
fc5c9c01bf
2 changed files with 29 additions and 11 deletions
12
main.js
12
main.js
File diff suppressed because one or more lines are too long
|
|
@ -35,6 +35,10 @@ export class ChatView extends ItemView {
|
||||||
private textarea: HTMLTextAreaElement | null = null;
|
private textarea: HTMLTextAreaElement | null = null;
|
||||||
private sendButton: HTMLButtonElement | null = null;
|
private sendButton: HTMLButtonElement | null = null;
|
||||||
|
|
||||||
|
/** True during initial message restore in onOpen() — suppress
|
||||||
|
* saveMessages() calls to avoid N racing saveData() writes. */
|
||||||
|
private _restoring: boolean = false;
|
||||||
|
|
||||||
/** Context captured on chat focus — avoids the timing bug where
|
/** Context captured on chat focus — avoids the timing bug where
|
||||||
* activeEditor is null by the time handleSend() runs. */
|
* activeEditor is null by the time handleSend() runs. */
|
||||||
private capturedNote: {
|
private capturedNote: {
|
||||||
|
|
@ -176,13 +180,24 @@ export class ChatView extends ItemView {
|
||||||
this.statusBar = container.createDiv("chat-status-bar");
|
this.statusBar = container.createDiv("chat-status-bar");
|
||||||
this.updateStatus(this.chatState.model, null);
|
this.updateStatus(this.chatState.model, null);
|
||||||
|
|
||||||
// Restore saved messages from previous session
|
// Restore saved messages from previous session.
|
||||||
|
// Set _restoring flag so appendMessage skips fire-and-forget saves —
|
||||||
|
// we do a single save at the end to avoid N racing saveData() calls.
|
||||||
if (this.persistence) {
|
if (this.persistence) {
|
||||||
const saved = this.persistence.loadMessages();
|
const saved = this.persistence.loadMessages();
|
||||||
if (saved.length > 0) {
|
if (saved.length > 0) {
|
||||||
for (const msg of saved) {
|
this._restoring = true;
|
||||||
await this.appendMessage(msg.role, msg.content);
|
try {
|
||||||
|
for (const msg of saved) {
|
||||||
|
await this.appendMessage(msg.role, msg.content);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Hermes] Failed to restore messages:", err);
|
||||||
|
} finally {
|
||||||
|
this._restoring = false;
|
||||||
}
|
}
|
||||||
|
// Single save to persist the restored state cleanly
|
||||||
|
await this.persistence.saveMessages(this.chatState.messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -228,8 +243,11 @@ export class ChatView extends ItemView {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
this.chatState.messages.push(msg);
|
this.chatState.messages.push(msg);
|
||||||
// Fire-and-forget save — don't block rendering
|
// Fire-and-forget save — don't block rendering.
|
||||||
this.persistence?.saveMessages(this.chatState.messages);
|
// Suppressed during restore (too many racing saveData writes).
|
||||||
|
if (!this._restoring) {
|
||||||
|
this.persistence?.saveMessages(this.chatState.messages);
|
||||||
|
}
|
||||||
|
|
||||||
const wrapper = this.messagesContainer.createDiv("chat-message");
|
const wrapper = this.messagesContainer.createDiv("chat-message");
|
||||||
wrapper.addClass(`chat-message-${role}`);
|
wrapper.addClass(`chat-message-${role}`);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue