fix: ensure proper closure of zip reader and file handles

This commit is contained in:
monoid 2025-10-01 00:15:20 +09:00
parent c5d15240bc
commit 26b55be260
2 changed files with 47 additions and 14 deletions

View file

@ -29,7 +29,7 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
if (page < 0 || page >= entries.length) {
set.status = 404;
zip.reader.close();
await zip.reader.close();
return null;
}
@ -45,7 +45,7 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
const cachedDate = new Date(ifModifiedSince);
if (!Number.isNaN(cachedDate.valueOf()) && lastModified <= cachedDate) {
set.status = 304;
zip.reader.close();
await zip.reader.close();
return null;
}
}
@ -57,6 +57,14 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
},
});
let zipClosed = false;
const closeZip = async () => {
if (!zipClosed) {
zipClosed = true;
await zip.reader.close();
}
};
readStream.pipeTo(new WritableStream({
write(chunk) {
nodeReadable.push(chunk);
@ -64,15 +72,21 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
close() {
nodeReadable.push(null);
},
abort(err) {
nodeReadable.destroy(err);
},
})).catch((err) => {
nodeReadable.destroy(err);
});
nodeReadable.on("close", () => {
zip.reader.close();
closeZip().catch(console.error);
});
nodeReadable.on("error", () => {
zip.reader.close();
closeZip().catch(console.error);
});
nodeReadable.on("end", () => {
closeZip().catch(console.error);
});
const ext = entry.filename.split(".").pop()?.toLowerCase() ?? "jpeg";
@ -84,7 +98,7 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
set.status = 200;
return nodeReadable;
} catch (error) {
zip.reader.close();
await zip.reader.close();
throw error;
}
}

View file

@ -1,11 +1,11 @@
import { type FileHandle, open } from "node:fs/promises";
import { orderBy } from "natural-orderby";
import { ZipReader, Reader, type Entry, ZipReaderConstructorOptions } from "@zip.js/zip.js";
import EventEmitter from "node:events";
class FileReader extends Reader<string> {
private fd?: FileHandle;
private path: string;
private closed = false;
constructor(path: string) {
super(path);
@ -14,21 +14,29 @@ class FileReader extends Reader<string> {
async init(): Promise<void> {
await super.init?.();
if (this.closed) return;
const fd = await open(this.path, "r");
const stat = await fd.stat();
this.fd = fd;
this.size = stat.size;
// not implemented yet
(this.fd as unknown as EventEmitter).on("close", () => {
this.fd?.close();
this.fd = undefined;
});
}
async close(): Promise<void> {
await this.fd?.close();
if (this.closed) return;
this.closed = true;
if (this.fd) {
await this.fd.close();
this.fd = undefined;
}
}
async readUint8Array(index: number, length: number): Promise<Uint8Array> {
if (this.closed) {
throw new Error("FileReader is closed");
}
try {
const buffer = new Uint8Array(length);
if (this.fd === undefined) {
@ -49,18 +57,29 @@ class FileReader extends Reader<string> {
return buffer;
} catch (error) {
console.error("read error", error);
// 에러 발생 시 파일 핸들 정리
await this.close();
throw error;
}
}
}
class FileZipReader extends ZipReader<FileHandle> {
private closed = false;
constructor(private reader: FileReader, options?: ZipReaderConstructorOptions) {
super(reader, options);
}
override async close(): Promise<void> {
super.close();
await this.reader.close();
if (this.closed) return;
this.closed = true;
try {
await super.close();
} finally {
await this.reader.close();
}
}
}