fix: resolve MySQL connection state desync after disconnect/reconnect

- Use synchronous pool removal in desktop disconnect_db to prevent
  race condition when user quickly disconnects and reconnects
- Rename recordMetadataLoadError to recordConnectionLostError and
  export it for cross-store reuse
- Sync query execution errors back to connection state via
  recordConnectionLostError in queryStore catch blocks
- Add backend check_connection_health method (dbx-core) with Tauri
  command and web route for proactive pool verification
- Add frontend checkConnectionHealth API (tauri.ts/http.ts/api.ts)
- Enhance ensureConnected to verify backend pool health before
  assuming connection is valid, auto-reconnect if stale
This commit is contained in:
fagao committed 2026-06-22 20:40:40 +08:00
1 parent 4346192713
commit e2f68de455
10 files changed
+92 -24

No files matched your search

+33 -22
View File
@@ -260,7 +260,7 @@ export const useConnectionStore = defineStore("connection", () => {
return message;
}
function recordMetadataLoadError(connectionId: string, error: unknown) {
function recordConnectionLostError(connectionId: string, error: unknown) {
if (shouldMarkDisconnected(error)) {
connectedIds.value.delete(connectionId);
if (activeConnectionId.value === connectionId) activeConnectionId.value = null;
@@ -902,7 +902,17 @@ export const useConnectionStore = defineStore("connection", () => {
}
async function ensureConnected(connectionId: string) {
if (connectedIds.value.has(connectionId)) return;
if (connectedIds.value.has(connectionId)) {
// Optimistic: verify backend pool is actually healthy
try {
await api.checkConnectionHealth(connectionId);
return;
} catch {
// Backend pool is dead — remove from connectedIds and reconnect
connectedIds.value.delete(connectionId);
if (activeConnectionId.value === connectionId) activeConnectionId.value = null;
}
}
let config = getConfig(connectionId);
if (!config) {
await initFromDisk();
@@ -1020,7 +1030,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1063,7 +1073,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1097,7 +1107,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1127,7 +1137,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1191,7 +1201,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1223,7 +1233,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1255,7 +1265,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1283,7 +1293,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1322,7 +1332,7 @@ export const useConnectionStore = defineStore("connection", () => {
await savePersistedTreeChildren(cacheKey, children);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1358,7 +1368,7 @@ export const useConnectionStore = defineStore("connection", () => {
await savePersistedTreeChildren(cacheKey, children);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1391,7 +1401,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1426,7 +1436,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1468,7 +1478,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(node.connectionId, e);
recordConnectionLostError(node.connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1524,7 +1534,7 @@ export const useConnectionStore = defineStore("connection", () => {
await savePersistedTreeChildren(cacheKey, children);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1584,7 +1594,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(node.connectionId, e);
recordConnectionLostError(node.connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1623,7 +1633,7 @@ export const useConnectionStore = defineStore("connection", () => {
await savePersistedTreeChildren(objectGroupCacheKey(parent), nextChildren);
parent.isExpanded = true;
} catch (e) {
recordMetadataLoadError(parent.connectionId, e);
recordConnectionLostError(parent.connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1755,7 +1765,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1792,7 +1802,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1833,7 +1843,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -1870,7 +1880,7 @@ export const useConnectionStore = defineStore("connection", () => {
);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
recordConnectionLostError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
@@ -3039,6 +3049,7 @@ export const useConnectionStore = defineStore("connection", () => {
setConnectionError,
clearConnectionError,
recordConnectionError,
recordConnectionLostError,
sidebarLayout,
getConfig,
isTreeNodePinned,
+8 -1
View File
@@ -1638,6 +1638,8 @@ export const useQueryStore = defineStore("query", () => {
}
} catch (e: any) {
console.error("[DBX][executeTabSql:error]", { traceId, elapsed: elapsed(), error: e });
// Sync connection state if the error indicates a lost connection
useConnectionStore().recordConnectionLostError(tab.connectionId, e);
const current = tabs.value.find((t) => t.id === id);
if (current?.executionId === executionId) {
current.result = toErrorResult(e);
@@ -1787,6 +1789,8 @@ export const useQueryStore = defineStore("query", () => {
}
return canceled;
} catch (e: any) {
// Sync connection state if the error indicates a lost connection
if (tab) useConnectionStore().recordConnectionLostError(tab.connectionId, e);
const current = tabs.value.find((t) => t.id === id);
if (current && current.executionId === executionId) {
current.isCancelling = false;
@@ -1833,12 +1837,15 @@ export const useQueryStore = defineStore("query", () => {
function notifyConnectionMayBeLost() {
const stuck = tabs.value.filter((t) => t.isExecuting);
if (stuck.length > 0) {
const connStore = useConnectionStore();
stuck.forEach((tab) => {
tab.isExecuting = false;
tab.isCancelling = false;
tab.queryExecutionStartedAt = undefined;
tab.executionId = undefined;
tab.result = toErrorResult(new Error(t("editor.connectionMayBeLost")));
const error = new Error(t("editor.connectionMayBeLost"));
tab.result = toErrorResult(error);
connStore.recordConnectionLostError(tab.connectionId, error);
});
}
}