feat: add worker threads, persistent Python sidecar, graceful shutdown, and architectural improvements

- Graceful shutdown: SIGTERM/SIGINT handlers drain HTTP, stop workers, close DB
- Thumbnail caching: disk-cached thumbnails with immutable Cache-Control headers
- Worker thread pool: Piscina offloads Sharp processing off the main event loop
- Persistent Python dispatcher: pre-imports ML libraries, eliminates cold-start latency
- Tool page registry: declarative tool-to-component mapping replaces 750-line switch
- File store cleanup: remove dead derived fields, stable files array reference
- Job persistence: progress written to SQLite jobs table, stale jobs recovered on startup
This commit is contained in:
Siddharth Kumar Sah
2026-03-29 17:23:41 +08:00
parent 88729e255d
commit 1cbdfa1590
20 changed files with 1575 additions and 530 deletions
+22 -2
View File
@@ -17,7 +17,14 @@ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import sharp from "sharp";
import { db, schema, sqlite } from "../db/index.js";
import { auditLog } from "../lib/audit.js";
import { deleteStoredFile, getStoredFilePath, saveFile } from "../lib/file-storage.js";
import {
deleteStoredFile,
deleteThumbnail,
getCachedThumbnail,
getStoredFilePath,
saveFile,
saveThumbnail,
} from "../lib/file-storage.js";
import { validateImageBuffer } from "../lib/file-validation.js";
import { sanitizeFilename } from "../lib/filename.js";
import { getAuthUser } from "../plugins/auth.js";
@@ -342,6 +349,15 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
return reply.status(404).send({ error: "File not found" });
}
// Serve from disk cache if available
const cached = await getCachedThumbnail(file.storedName);
if (cached) {
return reply
.header("Content-Type", "image/jpeg")
.header("Cache-Control", "public, max-age=86400, immutable")
.send(cached);
}
const filePath = getStoredFilePath(file.storedName);
try {
@@ -350,9 +366,12 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
.jpeg({ quality: 80 })
.toBuffer();
// Cache to disk (non-blocking, don't fail the request)
saveThumbnail(file.storedName, thumbnail).catch(() => {});
return reply
.header("Content-Type", "image/jpeg")
.header("Cache-Control", "public, max-age=86400")
.header("Cache-Control", "public, max-age=86400, immutable")
.send(thumbnail);
} catch {
return reply.status(422).send({ error: "Could not generate thumbnail" });
@@ -412,6 +431,7 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
for (const row of chainRows) {
await deleteStoredFile(row.stored_name);
await deleteThumbnail(row.stored_name);
db.delete(schema.userFiles).where(eq(schema.userFiles.id, row.id)).run();
deletedCount++;
}