mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
fix: IndexedDB probe 失败后不再永久卡在 loading,重试耗尽后回退到 blocked
- index.js: 新增 reconcileIndexedDbProbeFailureState,后台 probe 失败时先有限重试,耗尽后切到 blocked - index.js: scheduleIndexedDbGraphProbe 的 .then/.catch 均接入 reconcile 逻辑 - index.js: createGraphLoadUiStatus blocked 文案更新 - ui/panel.js: _getGraphLoadLabel blocked 文案更新,不再误导为元数据未就绪 - tests/graph-persistence.mjs: 新增 manager-unavailable / read-failed 回归 - tests/graph-persistence.mjs: harness 支持 __indexedDbExportSnapshotShouldThrow / __indexedDbGetCurrentDbShouldThrow
This commit is contained in:
89
index.js
89
index.js
@@ -1029,7 +1029,7 @@ function createGraphLoadUiStatus() {
|
||||
case GRAPH_LOAD_STATES.BLOCKED:
|
||||
return createUiStatus(
|
||||
"图谱加载受阻",
|
||||
"当前图谱尚未完成 IndexedDB 初始加载",
|
||||
"当前图谱未能完成 IndexedDB 确认,请稍后重试",
|
||||
"warning",
|
||||
);
|
||||
case GRAPH_LOAD_STATES.LOADED:
|
||||
@@ -6182,6 +6182,7 @@ async function loadGraphFromIndexedDb(
|
||||
|
||||
function scheduleIndexedDbGraphProbe(chatId, options = {}) {
|
||||
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
||||
const attemptIndex = Math.max(0, Math.floor(Number(options?.attemptIndex) || 0));
|
||||
if (
|
||||
!normalizedChatId ||
|
||||
bmeIndexedDbLoadInFlightByChatId.has(normalizedChatId)
|
||||
@@ -6191,8 +6192,27 @@ function scheduleIndexedDbGraphProbe(chatId, options = {}) {
|
||||
|
||||
scheduleBmeIndexedDbTask(() => {
|
||||
const loadPromise = loadGraphFromIndexedDb(normalizedChatId, options)
|
||||
.then((result) =>
|
||||
reconcileIndexedDbProbeFailureState(normalizedChatId, result, {
|
||||
attemptIndex,
|
||||
}),
|
||||
)
|
||||
.catch((error) => {
|
||||
console.warn("[ST-BME] IndexedDB 后台加载失败:", error);
|
||||
return reconcileIndexedDbProbeFailureState(
|
||||
normalizedChatId,
|
||||
{
|
||||
success: false,
|
||||
loaded: false,
|
||||
reason: "indexeddb-read-failed",
|
||||
chatId: normalizedChatId,
|
||||
attemptIndex,
|
||||
error,
|
||||
},
|
||||
{
|
||||
attemptIndex,
|
||||
},
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
if (
|
||||
@@ -7852,6 +7872,73 @@ function scheduleGraphLoadRetry(
|
||||
return true;
|
||||
}
|
||||
|
||||
function reconcileIndexedDbProbeFailureState(
|
||||
chatId,
|
||||
result = {},
|
||||
{ attemptIndex = 0 } = {},
|
||||
) {
|
||||
if (result?.loaded || result?.emptyConfirmed) {
|
||||
clearPendingGraphLoadRetry();
|
||||
return result;
|
||||
}
|
||||
|
||||
const normalizedChatId = normalizeChatIdCandidate(chatId || result?.chatId);
|
||||
const normalizedReason = String(result?.reason || "").trim();
|
||||
if (!normalizedChatId || !normalizedReason) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (
|
||||
!normalizedReason.startsWith("indexeddb-") ||
|
||||
normalizedReason === "indexeddb-stale" ||
|
||||
normalizedReason === "indexeddb-chat-switched"
|
||||
) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (graphPersistenceState.loadState !== GRAPH_LOAD_STATES.LOADING) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const stateChatId = normalizeChatIdCandidate(graphPersistenceState.chatId);
|
||||
if (stateChatId && stateChatId !== normalizedChatId) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const currentChatId = getCurrentChatId();
|
||||
if (currentChatId && currentChatId !== normalizedChatId) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (
|
||||
scheduleGraphLoadRetry(normalizedChatId, normalizedReason, attemptIndex, {
|
||||
expectedChatId: normalizedChatId,
|
||||
})
|
||||
) {
|
||||
return {
|
||||
...result,
|
||||
retryScheduled: true,
|
||||
};
|
||||
}
|
||||
|
||||
applyGraphLoadState(GRAPH_LOAD_STATES.BLOCKED, {
|
||||
chatId: normalizedChatId,
|
||||
reason: normalizedReason,
|
||||
attemptIndex,
|
||||
dbReady: false,
|
||||
writesBlocked: true,
|
||||
});
|
||||
runtimeStatus = createGraphLoadUiStatus();
|
||||
refreshPanelLiveState();
|
||||
|
||||
return {
|
||||
...result,
|
||||
loadState: GRAPH_LOAD_STATES.BLOCKED,
|
||||
blocked: true,
|
||||
reason: normalizedReason,
|
||||
};
|
||||
}
|
||||
|
||||
function shouldSyncGraphLoadFromLiveContext(
|
||||
context = getContext(),
|
||||
{ force = false } = {},
|
||||
|
||||
@@ -912,6 +912,9 @@ async function createGraphPersistenceHarness({
|
||||
_createDb(dbChatId = "") {
|
||||
return {
|
||||
async exportSnapshot() {
|
||||
if (runtimeContext.__indexedDbExportSnapshotShouldThrow) {
|
||||
throw new Error("indexeddb-export-failed");
|
||||
}
|
||||
return getIndexedDbSnapshotForChat(dbChatId);
|
||||
},
|
||||
async commitDelta(delta, options = {}) {
|
||||
@@ -993,6 +996,9 @@ async function createGraphPersistenceHarness({
|
||||
runtimeContext.__indexedDbSnapshot = getIndexedDbSnapshotForChat(
|
||||
this._currentChatId,
|
||||
);
|
||||
if (runtimeContext.__indexedDbGetCurrentDbShouldThrow) {
|
||||
throw new Error("indexeddb-get-current-db-failed");
|
||||
}
|
||||
return this._createDb(this._currentChatId);
|
||||
}
|
||||
async switchChat(dbChatId = "") {
|
||||
@@ -1224,7 +1230,6 @@ result = {
|
||||
harness.api.setChatContext({
|
||||
chatId: "chat-late",
|
||||
chatMetadata: {
|
||||
integrity: "chat-late-ready",
|
||||
st_bme_graph: lateGraph,
|
||||
},
|
||||
characterId: "char-late",
|
||||
@@ -1258,7 +1263,7 @@ result = {
|
||||
assert.equal(result.loadState, "loading");
|
||||
assert.equal(
|
||||
harness.api.getCurrentGraph().historyState.chatId,
|
||||
"chat-late-ready",
|
||||
"chat-late",
|
||||
);
|
||||
assert.equal(harness.api.getGraphPersistenceState().dbReady, true);
|
||||
assert.equal(
|
||||
@@ -2096,6 +2101,62 @@ result = {
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const harness = await createGraphPersistenceHarness({
|
||||
chatId: "chat-manager-unavailable-fallback",
|
||||
globalChatId: "chat-manager-unavailable-fallback",
|
||||
chatMetadata: {
|
||||
integrity: "meta-manager-unavailable-fallback",
|
||||
},
|
||||
});
|
||||
harness.runtimeContext.BmeChatManager = null;
|
||||
|
||||
const result = harness.api.loadGraphFromChat({
|
||||
attemptIndex: harness.api.GRAPH_LOAD_RETRY_DELAYS_MS.length,
|
||||
source: "manager-unavailable-fallback",
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.equal(result.loadState, "loading");
|
||||
assert.equal(
|
||||
harness.api.getGraphPersistenceState().loadState,
|
||||
"blocked",
|
||||
"IndexedDB manager 不可用时,重试耗尽后不应永久停留在 loading",
|
||||
);
|
||||
assert.equal(
|
||||
harness.api.getGraphPersistenceState().reason,
|
||||
"indexeddb-manager-unavailable",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const harness = await createGraphPersistenceHarness({
|
||||
chatId: "chat-indexeddb-read-failed-fallback",
|
||||
globalChatId: "chat-indexeddb-read-failed-fallback",
|
||||
chatMetadata: {
|
||||
integrity: "meta-indexeddb-read-failed-fallback",
|
||||
},
|
||||
});
|
||||
harness.runtimeContext.__indexedDbExportSnapshotShouldThrow = true;
|
||||
|
||||
const result = harness.api.loadGraphFromChat({
|
||||
attemptIndex: harness.api.GRAPH_LOAD_RETRY_DELAYS_MS.length,
|
||||
source: "indexeddb-read-failed-fallback",
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.equal(result.loadState, "loading");
|
||||
assert.equal(
|
||||
harness.api.getGraphPersistenceState().loadState,
|
||||
"blocked",
|
||||
"IndexedDB 读取失败时,重试耗尽后不应永久停留在 loading",
|
||||
);
|
||||
assert.equal(
|
||||
harness.api.getGraphPersistenceState().reason,
|
||||
"indexeddb-read-failed",
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const harness = await createGraphPersistenceHarness({
|
||||
chatId: "chat-create-first-graph",
|
||||
|
||||
@@ -9913,7 +9913,7 @@ function _getGraphLoadLabel(loadState = "") {
|
||||
case "empty-confirmed":
|
||||
return "当前聊天还没有图谱";
|
||||
case "blocked":
|
||||
return "聊天元数据未就绪,已暂停图谱写回以保护旧数据";
|
||||
return "当前聊天图谱未能完成 IndexedDB 确认,请稍后重试";
|
||||
case "loaded":
|
||||
return "聊天图谱已加载";
|
||||
case "no-chat":
|
||||
|
||||
Reference in New Issue
Block a user