fix(electron): close worker database before deleting sessions

This commit is contained in:
digua committed 2026-08-06 15:17:44 +08:00
1 parent b130e43e73
commit 1a774592ff
4 files changed
+22 -3

No files matched your search

+5
View File
@@ -44,6 +44,7 @@ import { assertDesktopDataDirCompatible, getDesktopAppVersion } from '../runtime
import { resolveDesktopNativeBinding } from '../runtime/native-sqlite'
import { resolveModelDownloadProxyUrl } from '../network/proxy'
import { getDefaultUserDataDir, getDownloadsDir, getUserDataDir } from '../paths/locations'
import { closeDatabase as closeWorkerDatabase } from '../worker/workerManager'
import { configureInternalHttpServer } from './http'
export interface InternalEndpoint {
@@ -148,6 +149,10 @@ export async function startInternalServer(
dbManager: newDbManager,
sessionAdapter,
pathProvider,
beforeDeleteSession: async (sessionId) => {
// The Desktop worker caches readonly SQLite handles; Windows cannot unlink an open database file.
await closeWorkerDatabase(sessionId)
},
runtimeIdentity: runtime,
nativeBinding,
getVersion: () => getDesktopAppVersion(app.getVersion()),
@@ -8,6 +8,9 @@ export interface RuntimeRouteContext {
pathProvider: PathProvider
getVersion: () => string
/** Close platform-owned database handles before deleting a session. */
beforeDeleteSession?: (sessionId: string) => void | Promise<void>
runtimeIdentity?: RuntimeIdentity
/** Native binding path for better-sqlite3 (CLI native copy / Electron-ABI desktop copy). */
nativeBinding?: string
@@ -569,11 +569,20 @@ describe('registerSharedRoutes smoke tests', () => {
assert.equal(meta.name, 'Renamed Chat')
})
it('DELETE /_web/sessions/:id delegates to the session adapter', async () => {
it('DELETE /_web/sessions/:id closes platform handles before deleting the session', async () => {
const db = createSessionDb()
const dbs = new Map<string, DatabaseAdapter>([['chat-1', db]])
const lifecycle: string[] = []
const routeApp = Fastify()
registerSessionRoutes(routeApp, createTestContext(dbs))
const ctx = createTestContext(dbs)
ctx.beforeDeleteSession = async (sessionId) => {
lifecycle.push(`close:${sessionId}`)
}
ctx.sessionAdapter.deleteSessionFile = (sessionId) => {
lifecycle.push(`delete:${sessionId}`)
return lifecycle[0] === `close:${sessionId}` && dbs.delete(sessionId)
}
registerSessionRoutes(routeApp, ctx)
await routeApp.ready()
const resp = await routeApp.inject({ method: 'DELETE', url: '/_web/sessions/chat-1' })
@@ -584,6 +593,7 @@ describe('registerSharedRoutes smoke tests', () => {
assert.equal(resp.statusCode, 200)
assert.deepEqual(resp.json(), { success: true })
assert.equal(dbs.has('chat-1'), false)
assert.deepEqual(lifecycle, ['close:chat-1', 'delete:chat-1'])
})
it('owner profile routes select, apply and dismiss across sessions', async () => {
@@ -4,7 +4,7 @@ import type { RuntimeRouteContext } from '../../context/runtime'
import type { ServiceRouteContext } from '../../context/services'
import { sessionService, ownerProfileService, PreferencesManager } from '@openchatlab/node-runtime'
type SessionRouteContext = Pick<RuntimeRouteContext, 'sessionAdapter' | 'pathProvider'> &
type SessionRouteContext = Pick<RuntimeRouteContext, 'sessionAdapter' | 'pathProvider' | 'beforeDeleteSession'> &
Pick<ServiceRouteContext, 'preferencesManager'> &
Pick<AiRouteContext, 'aiChatManager'>
@@ -41,6 +41,7 @@ export function registerSessionRoutes(server: FastifyInstance, ctx: SessionRoute
server.delete<{ Params: { id: string } }>('/_web/sessions/:id', async (request, reply) => {
const { id } = request.params
try {
await ctx.beforeDeleteSession?.(id)
const deleted = sessionService.deleteSession(adapter, id)
if (!deleted) {
return reply.code(404).send({ success: false, error: 'File not found' })